Skip to content

Commit

Permalink
Fixing the billing page
Browse files Browse the repository at this point in the history
  • Loading branch information
danrowden committed Aug 15, 2023
1 parent 2f5c394 commit ff1614a
Show file tree
Hide file tree
Showing 11 changed files with 140 additions and 50 deletions.
26 changes: 18 additions & 8 deletions app/(app)/billing/page.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { getSession } from "@/lib/auth";
import type { Metadata } from 'next';
import prisma from "@/lib/prisma";

import Plans from '@/components/plan';

import UpdateBillingLink from '@/components/update-billing'
import LemonSqueezy from '@lemonsqueezy/lemonsqueezy.js'

const ls = new LemonSqueezy(process.env.LEMONSQUEEZY_API_KEY);


Expand All @@ -27,20 +27,28 @@ async function getPlans() {


async function getSubscription(userId) {
return await prisma.subscription.findUnique({
return await prisma.subscription.findFirst({
where: {
userId: userId
},
include: {
plan: true
}
})
}


export default async function Billing() {
const session = await getSession();

const plans = await getPlans()

const subscription = null //await getSubscription(session.user.id)
const subscription = await getSubscription(session?.user.id)

const renewalDate = subscription?.renewsAt ? new Date(subscription.renewsAt).toLocaleString('en-US', {
month: 'short',
day: "2-digit",
year: 'numeric'
}) : null

// Uses Test mode in "Demo app" (prod server)

Expand All @@ -50,17 +58,19 @@ export default async function Billing() {

{subscription ? (
<>
<p className="">Plan information</p>
<p className="mb-2">Your current plan: {subscription.plan.name} ({subscription.plan.variantName}) {subscription.plan.interval}ly.</p>
<p className="mb-2">Your next renewal will be on {renewalDate}.</p>

<hr className="my-8" />

<h2 className="font-bold">Change plan</h2>

<Plans plans={plans} subscription={subscription} />
<Plans plans={plans} sub={subscription} />

<hr className="my-8" />

<p className="mt-8 text-sm text-gray-500">Cancel link etc</p>
<p><UpdateBillingLink subscription={subscription} /></p>
<p className="text-sm text-gray-500">Cancel your subscription</p>
</>
) : (

Expand Down
7 changes: 4 additions & 3 deletions app/(app)/billing/refresh-plans/page.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import prisma from "@/lib/prisma";
import { LemonSqueezy } from "../lemonsqueezy";
import LemonSqueezy from '@lemonsqueezy/lemonsqueezy.js'

const ls = new LemonSqueezy(process.env.LEMONSQUEEZY_API_KEY);


async function getPlans() {
// Fetch data from Lemon Squeezy

const params = {include: 'product', 'page[size]': 50}
const params = {include: 'product', 'perPage': 50}

var hasNextPage = true
var page = 1
Expand All @@ -23,7 +23,7 @@ async function getPlans() {

if (resp['meta']['page']['lastPage'] > page) {
page += 1
params['page[number]'] = page
params['page'] = page
} else {
hasNextPage = false
}
Expand Down Expand Up @@ -66,6 +66,7 @@ async function getPlans() {
variant = variant['attributes']

try {
console.log('Adding/updating variant ' + variantId)
await prisma.plan.upsert({
where: {
variantId: variantId
Expand Down
4 changes: 1 addition & 3 deletions app/(app)/billing/webhook/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,6 @@ async function processEvent(event) {

var lemonSqueezyId = parseInt(obj['id'])

// TODO: Link this to a real user object with customData['user_id']

var updateData = {
orderId: data['order_id'],
name: data['user_name'],
Expand All @@ -51,7 +49,7 @@ async function processEvent(event) {
userId: customData['user_id']
}
if ( event.eventName == 'subscription_created' ) {
updateData.price = plan['price']
updateData.price = plan['price'] // save original price to be able to display in UI
}

const createData = updateData
Expand Down
6 changes: 3 additions & 3 deletions app/(app)/login/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@ const handleSubmit = (event) => {

export default function SignIn() {
return (
<div>
<h1>Sign in with a magic link</h1>
<div className="max-w-xl mx-auto px-4">
<h1 className="mb-4">Sign in with a magic link</h1>
<form method="post" onSubmit={handleSubmit}>
<label>
Email address
<input type="email" id="email" name="email" />
</label>
<button type="submit">Sign in with Email</button>
<button type="submit" className="rounded-md bg-grey-900 text-grey-50">Sign in with Email</button>
</form>
</div>
)
Expand Down
13 changes: 7 additions & 6 deletions app/api/checkouts/route.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { getSession } from "@/lib/auth";

import LemonSqueezy from '@lemonsqueezy/lemonsqueezy.js'

const ls = new LemonSqueezy(process.env.LEMONSQUEEZY_API_KEY);


Expand Down Expand Up @@ -38,11 +38,12 @@ export async function POST(request: Request) {
console.log(attributes)

try {
const checkout = await ls.createCheckout(
process.env.LEMONSQUEEZY_STORE_ID,
res.variantId,
attributes
)
const checkout = await ls.createCheckout({
storeId: process.env.LEMONSQUEEZY_STORE_ID,
variantId: res.variantId,
attributes
})

return Response.json({'error': false, 'url': checkout['data']['attributes']['url']}, {status: 200})
} catch(e) {
return Response.json({'error': true, 'message': e.message}, {status: 400})
Expand Down
18 changes: 16 additions & 2 deletions app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,23 @@
import { getSession } from "@/lib/auth";
import Link from 'next/link'

export default function Home() {
export default async function Home() {
const session = await getSession();
const email = session?.user.email || null
return (

<div className="text-center py-4">
<Link href="/billing">Go to the Billing page &rarr;</Link>

{session ? (
<>
<p class="mb-4">Welcome, {email}</p>
<Link href="/billing">Go to the Billing page &rarr;</Link>
</>
) : (
<Link href="/login">Sign in</Link>
)}

</div>

)
}
50 changes: 27 additions & 23 deletions components/plan-button.tsx
Original file line number Diff line number Diff line change
@@ -1,72 +1,76 @@
'use client';

import { useState } from 'react';
import { useSession } from "next-auth/react"
import { Loader2 } from 'lucide-react';


export default function PlanButton({ plan, subscription }) {
const { data: session, status } = useSession()

const [isMutating, setIsMutating] = useState(false)

async function createCheckout(e, variantId) {

e.preventDefault()

setIsMutating(true)

/* Create a checkout */
const res = await fetch('/api/checkouts', {
method: 'POST',
body: JSON.stringify({
variantId: variantId
})
})

const checkout = await res.json();

if (checkout.error) {
alert(checkout.message)
} else {
LemonSqueezy.Url.Open(checkout['url'])
}

setIsMutating(false)

}

async function changePlan(e, variantId, productId) {

const changePlan = async (e, subscription, plan) => {
e.preventDefault()
alert('change plan')
if (confirm(`Please confirm you want to change to the ${plan.name} (${plan.variantName} ${plan.interval}ly) plan. \
For upgrades you will be charged a prorated amount.`)) {
alert('change plan to ' + plan.variantId)

/* Change plan */

}
}

return (
<>
{subscription && subscription.plan ? (
<span className="block text-center py-2 px-5 bg-gray-200 rounded-full font-bold">
<span className="leading-[28px] inline-block">Your current plan</span>
</span>
{!subscription ? (
<a
href="#"
onClick={(e) => createCheckout(e, plan.variantId)}
className="block text-center py-2 px-5 bg-amber-200 rounded-full font-bold text-amber-800 shadow-md shadow-gray-300/30 select-none"
disabled={isMutating}
>
<Loader2 className={"animate-spin inline-block relative top-[-1px] mr-2" + (!isMutating ? ' hidden' : '')} />
<span className="leading-[28px] inline-block">Sign up</span>
</a>
) : (
<>
{subscription ? (
{subscription?.planId == plan.id ? (
<span className="block text-center py-2 px-5 bg-gray-200 rounded-full font-bold shadow-md shadow-gray-300/30 select-none">
<span className="leading-[28px] inline-block">Your current plan</span>
</span>
) : (
<a
href="#"
onClick={(e) => changePlan(e, plan.variantId, plan.productId)}
onClick={(e) => changePlan(e, subscription, plan)}
className="block text-center py-2 px-5 bg-amber-200 rounded-full font-bold text-amber-800 shadow-md shadow-gray-300/30 select-none"
disabled={isMutating}
>
<Loader2 className={"animate-spin inline-block relative top-[-1px] mr-2" + (!isMutating ? ' hidden' : '')} />
<span className="leading-[28px] inline-block">Change to this plan</span>
</a>
) : (
<a
href="#"
onClick={(e) => createCheckout(e, plan.variantId)}
className="block text-center py-2 px-5 bg-amber-200 rounded-full font-bold text-amber-800 shadow-md shadow-gray-300/30 select-none"
disabled={isMutating}
>
<Loader2 className={"animate-spin inline-block relative top-[-1px] mr-2" + (!isMutating ? ' hidden' : '')} />
<span className="leading-[28px] inline-block">Sign up</span>
</a>
)}
</>
)}
Expand Down
17 changes: 15 additions & 2 deletions components/plan.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,13 @@ function IntervalSwitcher({ intervalValue, changeInterval }) {


function Plan({ plan, subscription, intervalValue }) {
console.log(subscription)
return (
<div
className={
'flex flex-col p-4 rounded-md border-solid border-2 border-gray-200'
+ (plan.interval !== intervalValue ? ' hidden' : '')
+ (subscription && subscription.plan == plan ? ' opacity-50' : '')
+ (subscription?.planId == plan.id ? ' opacity-50' : '')
}
>
<div className="grow">
Expand All @@ -68,9 +69,21 @@ function Plan({ plan, subscription, intervalValue }) {
}


export default function Plans({ plans, subscription }) {
export default function Plans({ plans, sub }) {

const [intervalValue, setIntervalValue] = useState('month')
const [subscription, setSubscription] = useState(() => {
if (sub) {
return {
id: sub.lemonSqueezyId,
planId: sub.plan?.id,
productId: sub.plan?.productId,
variantId: sub.plan?.variantId,
}
} else {
return {}
}
})

return (
<>
Expand Down
39 changes: 39 additions & 0 deletions components/update-billing.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
'use client';

import { useState } from 'react';
import { Loader2 } from 'lucide-react';
import LemonSqueezy from '@lemonsqueezy/lemonsqueezy.js'

const ls = new LemonSqueezy(process.env.LEMONSQUEEZY_API_KEY);


export default function UpdateBillingLink({ subscription }) {

const [isMutating, setIsMutating] = useState(false)

async function openUpdateModal(e) {

e.preventDefault()

setIsMutating(true)

/* Fetch the subscription */
try {
const res = await ls.getSubscription({ id: subscription.lemonSqueezyId })
// TODO: Use Lemon.js
window.location = res['url']['update_payment_method']
} catch (err) {
alert(err['errors'][0]['detail'])
}

setIsMutating(false)

}

return (
<a href="" className="mb-2 text-sm text-gray-500" onClick={openUpdateModal}>
Update your payment method
<Loader2 className={"animate-spin inline-block relative top-[-1px] ml-2 w-8" + (!isMutating ? ' invisible' : 'visible')} />
</a>
)
}
8 changes: 8 additions & 0 deletions prisma/migrations/20230814134816_/migration.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/*
Warnings:
- Added the required column `userId` to the `Subscription` table without a default value. This is not possible if the table is not empty.
*/
-- AlterTable
ALTER TABLE "Subscription" ADD COLUMN "userId" TEXT NOT NULL DEFAULT 1;
2 changes: 2 additions & 0 deletions prisma/migrations/20230814140008_/migration.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "Subscription" ALTER COLUMN "userId" DROP DEFAULT;

0 comments on commit ff1614a

Please sign in to comment.