Skip to content

Commit

Permalink
Session; better components
Browse files Browse the repository at this point in the history
  • Loading branch information
danrowden committed Aug 16, 2023
1 parent ff1614a commit b6f0a76
Show file tree
Hide file tree
Showing 12 changed files with 548 additions and 128 deletions.
46 changes: 46 additions & 0 deletions app/(app)/billing/change-plan/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { getSession } from "@/lib/auth";
import type { Metadata } from 'next';
import prisma from "@/lib/prisma";
import Link from 'next/link';
import { PlansComponent } from '@/components/manage';
import { getPlans, getSubscription } from '@/lib/data';
import { redirect } from 'next/navigation';


export const metadata: Metadata = {
title: 'Billing'
}


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

const plans = await getPlans();

const sub = await getSubscription(session?.user.id);

if (!sub) {
redirect('/billing')
}

return (
<div className="container mx-auto max-w-lg">

<Link href="/billing/" className="text-gray-500 text-sm mb-6">&larr; Back to billing</Link>

<h1 className="text-xl font-bold mb-3 text-center">Change plan</h1>

{sub.status == 'on_trial' && (
<div className="my-8 p-4 text-sm text-amber-800 rounded-md border border-amber-200 bg-amber-50">
You are currently on a free trial. You will not be charged when changing plans during a trial.
</div>
)}

<PlansComponent plans={plans} sub={sub} />

<script src="https://app.lemonsqueezy.com/js/lemon.js" defer></script>

</div>
)
}

60 changes: 8 additions & 52 deletions app/(app)/billing/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ 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 { SubscriptionComponent } from '@/components/subscription';
import { getPlans, getSubscription } from '@/lib/data';
import LemonSqueezy from '@lemonsqueezy/lemonsqueezy.js'

const ls = new LemonSqueezy(process.env.LEMONSQUEEZY_API_KEY);
Expand All @@ -12,76 +13,31 @@ export const metadata: Metadata = {
title: 'Billing'
}

async function getPlans() {
// Fetch data from external API
const plans = await prisma.plan.findMany({
where: {
NOT: {
status: 'draft'
}
}
});

return plans
}


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

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

const plans = await getPlans()

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
const sub = await getSubscription(session?.user.id)

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

return (
<div className="container mx-auto max-w-lg">
<h1 className="text-xl font-bold mb-3">Billing</h1>

{subscription ? (
<>
<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} sub={subscription} />
<h1 className="text-xl font-bold mb-3">Billing</h1>

<hr className="my-8" />
{sub ? (

<SubscriptionComponent sub={sub} plans={plans} />

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

<>
<p>Please sign up to a paid plan.</p>

<Plans plans={plans} />

<p className="mt-8 text-gray-400 text-sm text-center">
Payments are processed securely by Lemon Squeezy.
</p>
</>
)}

Expand Down
11 changes: 6 additions & 5 deletions app/(app)/billing/webhook/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@ async function processEvent(event) {

const obj = event.body['data']

console.log(event.eventName)
console.log(obj)
// console.log('======== WEBHOOK ===')
// console.log(event.eventName)
// console.log(obj)

if ( event.eventName.startsWith('subscription_payment_') ) {
// Save subscription invoices; obj is a "Subscription invoice"
Expand All @@ -33,8 +34,6 @@ async function processEvent(event) {
},
})

console.log(plan)

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

var updateData = {
Expand All @@ -54,6 +53,7 @@ async function processEvent(event) {

const createData = updateData
createData.lemonSqueezyId = lemonSqueezyId
createData.price = plan.price

try {

Expand All @@ -66,7 +66,8 @@ async function processEvent(event) {
create: createData
})

await prisma.event.update({
// Mark event as processed
await prisma.webhookEvent.update({
where: {
id: event.id
},
Expand Down
7 changes: 3 additions & 4 deletions app/api/checkouts/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export async function POST(request: Request) {
const res = await request.json()

if ( !res.variantId ) {
return Response.json({'error': true, 'message': 'No variant ID was provided.'}, {status: 400})
return Response.json({ error: true, message: 'No variant ID was provided.' }, { status: 400 })
}

// Customise the checkout experience
Expand All @@ -22,9 +22,9 @@ export async function POST(request: Request) {
'button_color': '#fde68a'
},
'checkout_data': {
'email': session.user.email, // Displays in the checkout form eg session.user.email with NextAuth.js
'email': session.user.email, // Displays in the checkout form
'custom': {
'user_id': session.user.id // Sent in the background; visible in webhooks and API calls eg session.user.id with NextAuth.js
'user_id': session.user.id // Sent in the background; visible in webhooks and API calls
}
},
'product_options': {
Expand All @@ -35,7 +35,6 @@ export async function POST(request: Request) {
'receipt_thank_you_note': 'Thank you for signing up to Lemonstand!'
}
}
console.log(attributes)

try {
const checkout = await ls.createCheckout({
Expand Down
84 changes: 84 additions & 0 deletions app/api/subscriptions/[id]/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { getSession } from "@/lib/auth";
import { getSubscription } from '@/lib/data';
import LemonSqueezy from '@lemonsqueezy/lemonsqueezy.js'

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


export async function GET(request, { params }) {
// Get subscription update billing link
try {
const subscription = await ls.getSubscription({ id: params.id })
return Response.json({ error: false, subscription: {
update_billing_url: subscription['data']['attributes']['urls']['update_payment_method']
} }, { status: 200 })
} catch(e) {
return Response.json({ error: true, message: e.message }, { status: 400 })
}
}


export async function POST(request, { params }) {
const session = await getSession();

const res = await request.json()

var subscription;


if (res.variantId && res.productId) {

// Update plan

try {
subscription = await ls.updateSubscription({
id: params.id,
productId: res.productId,
variantId: res.variantId,
})
} catch(e) {
return Response.json({ error: true, message: e.message }, { status: 400 })
}

} else if (res.action == 'resume') {

// Resume

try {
subscription = await ls.resumeSubscription({ id: params.id })
} catch(e) {
return Response.json({ error: true, message: e.message }, { status: 400 })
}

} else if (res.action == 'cancel') {

// Cancel

try {
await ls.cancelSubscription({ id: params.id })
// TODO get proper result from cancelSubscription()
subscription = {
data: {
attributes: {
status: 'cancelled',

}
}
}
} catch(e) {
return Response.json({ error: true, message: e.message }, { status: 400 })
}

} else {

// Missing data in request

return Response.json({ error: true, message: 'Correct data not found.' }, { status: 400 })

}

// Return values needed to refresh state in UI
// DB will be updated in the background with webhooks
return Response.json({ error: false, subscription: subscription['data']['attributes'] }, { status: 200 })

}
2 changes: 1 addition & 1 deletion app/providers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { Toaster } from "sonner";
export function Providers({ children }: { children: React.ReactNode }) {
return (
<SessionProvider>
<Toaster />
<Toaster richColors position="top-center" />
{children}
</SessionProvider>
);
Expand Down
Loading

0 comments on commit b6f0a76

Please sign in to comment.