-
Notifications
You must be signed in to change notification settings - Fork 8
feat: Next.js example with Webhooks, Customer Portal and Checkout creation #9
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
Shivam-Batham
wants to merge
12
commits into
polarsource:main
Choose a base branch
from
Shivam-Batham:main
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
7622485
installation done
Shivam-Batham 9becf2e
ts-node added
Shivam-Batham e9601cb
Webhooks handler, Customer Portal handler and Checkout handler done
Shivam-Batham caa16cd
Readme updated
Shivam-Batham df8baa6
added build-time script
Shivam-Batham fa42f06
fixed
Shivam-Batham 0feacb9
polar mode fixed
Shivam-Batham 7255060
fixed: readme, validation-script, api, title
Shivam-Batham 17c302a
fix: env.example, readme, polar, validate-script
Shivam-Batham e6d5496
Formatted using prettier
Shivam-Batham f85cc90
env variable name matched
Shivam-Batham 612c24a
successUrl added for both dev/prod
Shivam-Batham File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
# https://docs.polar.sh/integrate/oat | ||
POLAR_ACCESS_TOKEN= | ||
|
||
# https://docs.polar.sh/integrate/webhooks/endpoints#setup-webhooks | ||
POLAR_WEBHOOK_SECRET= | ||
|
||
# use the above same approach to get the sandbox credentials. | ||
SANDBOX_POLAR_ACCESS_TOKEN= | ||
|
||
SANDBOX_POLAR_WEBHOOK_SECRET= | ||
|
||
# Polar server mode - production or sandbox | ||
POLAR_MODE=production | ||
|
||
# client url - this is the URL the customer would be led to if they purchase something. | ||
DEV_SUCCESS_URL= | ||
PROD_SUCCESS_URL= |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. | ||
|
||
# dependencies | ||
/node_modules | ||
/.pnp | ||
.pnp.* | ||
.yarn/* | ||
!.yarn/patches | ||
!.yarn/plugins | ||
!.yarn/releases | ||
!.yarn/versions | ||
|
||
# testing | ||
/coverage | ||
|
||
# next.js | ||
/.next/ | ||
/out/ | ||
|
||
# production | ||
/build | ||
|
||
# misc | ||
.DS_Store | ||
*.pem | ||
|
||
# debug | ||
npm-debug.log* | ||
yarn-debug.log* | ||
yarn-error.log* | ||
.pnpm-debug.log* | ||
|
||
# env files (can opt-in for committing if needed) | ||
.env* | ||
|
||
# vercel | ||
.vercel | ||
|
||
# typescript | ||
*.tsbuildinfo | ||
next-env.d.ts |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
node_modules | ||
.env | ||
README.md |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
{ | ||
"semi": false, | ||
"printWidth": 180, | ||
"singleQuote": true | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
<img width="255" height="75" alt="image" src="https://github.com/user-attachments/assets/5f6f176d-661a-45ed-b661-b4d8383e63c6" /> | ||
|
||
# Getting started with Polar and Next.js | ||
|
||
## Clone the repository | ||
|
||
```bash | ||
npx degit polarsource/examples/with-nextjs ./with-nextjs | ||
``` | ||
|
||
## How to use | ||
|
||
Run the command below to copy the .env.example file : | ||
|
||
```bash | ||
cp .env.example .env | ||
``` | ||
|
||
Checkout env.example & doc to get the Polar credentials | ||
|
||
```bash | ||
POLAR_ACCESS_TOKEN | ||
POLAR_WEBHOOK_SECRET | ||
``` | ||
|
||
Run the command below to install project dependencies : | ||
|
||
```bash | ||
npm install | ||
``` | ||
|
||
Run the Next.js application using the following command : | ||
|
||
```bash | ||
npm run dev | ||
``` |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
import { polar, successUrl } from '@/app/polar' | ||
import { NextRequest, NextResponse } from 'next/server' | ||
|
||
export async function POST(req: NextRequest) { | ||
try { | ||
const body = await req.json() | ||
const { productId } = body | ||
|
||
if (!productId) { | ||
return NextResponse.json({ message: 'productId is required.' }, { status: 400 }) | ||
} | ||
|
||
const checkout = await polar.checkouts.create({ | ||
products: [productId], | ||
successUrl: successUrl, | ||
}) | ||
|
||
return NextResponse.json( | ||
{ | ||
checkout: checkout, | ||
message: 'Checkout successful.', | ||
}, | ||
{ status: 200 }, | ||
) | ||
} catch (error) { | ||
console.error('❌ Error in checkout API:', error) | ||
return NextResponse.json({ message: 'Internal server error' }, { status: 500 }) | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
import { polar } from '@/app/polar' | ||
import { NextRequest, NextResponse } from 'next/server' | ||
|
||
export async function createCustomer(email: string) { | ||
try { | ||
// existing user fetch logic | ||
// in this example we are not using database, so you can only get the portal-once because 2nd time we dont have any logic to fecth customerId. | ||
|
||
const customer = await polar.customers.create({ | ||
email: email, | ||
}) | ||
return { customer_id: customer?.id, message: 'customer created succesfully.' } | ||
} catch (error) { | ||
return { customer_id: null, message: 'customer creation unsuccesfull.' } | ||
} | ||
} | ||
|
||
export async function POST(req: NextRequest) { | ||
try { | ||
const { searchParams } = req.nextUrl | ||
|
||
let email: string | null | undefined | ||
|
||
const body = await req.json().catch(() => ({})) | ||
email = body?.email | ||
|
||
if (!email) { | ||
email = searchParams.get('email') | ||
} | ||
|
||
if (!email) { | ||
return NextResponse.json({ message: 'Email is required' }, { status: 400 }) | ||
} | ||
|
||
const { customer_id, message }: any = await createCustomer(email) | ||
if (!customer_id) { | ||
return NextResponse.json({ message, customerId: customer_id }, { status: 400 }) | ||
} | ||
const { customerPortalUrl } = await polar.customerSessions.create({ | ||
customerId: customer_id, | ||
}) | ||
|
||
return NextResponse.json({ customerPortalUrl, customerId: customer_id, message }, { status: 201 }) | ||
} catch (error) { | ||
console.log('Error in customer portal.', error) | ||
return NextResponse.json({ message: 'Error in customer portal.' }, { status: 500 }) | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
import { NextResponse } from 'next/server' | ||
import { polar } from '@/app/polar' | ||
|
||
export async function GET() { | ||
try { | ||
const products = await polar.products.list({}) | ||
return NextResponse.json(products.result.items ?? [], { status: 200 }) | ||
} catch (error: any) { | ||
return NextResponse.json({ error: error.message ?? 'Failed to fetch products' }, { status: 500 }) | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
import { Webhooks } from '@polar-sh/nextjs' | ||
import { NextResponse } from 'next/server' | ||
|
||
export const POST = async () => { | ||
const webhookSecret = process.env.POLAR_MODE === 'production' ? process.env.POLAR_WEBHOOK_SECRET : process.env.SANDBOX_POLAR_WEBHOOK_SECRET | ||
if (!webhookSecret) { | ||
return NextResponse.json( | ||
{ | ||
message: `${process.env.POLAR_MODE === 'production' ? 'POLAR_WEBHOOK_SECRET' : 'SANDBOX_POLAR_WEBHOOK_SECRET'} token is not found.`, | ||
}, | ||
{ status: 400 }, | ||
) | ||
} | ||
Webhooks({ | ||
webhookSecret: webhookSecret, | ||
onPayload: async (payload) => { | ||
// Handle the payload | ||
// No need to return an acknowledge response | ||
}, | ||
onOrderPaid: async (event) => {}, | ||
}) | ||
|
||
return NextResponse.json({}, { status: 200 }) | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
'use client' | ||
|
||
import { useRouter } from 'next/navigation' | ||
import { useEffect } from 'react' | ||
|
||
export default function ConfirmationPage() { | ||
const router = useRouter() | ||
useEffect(() => { | ||
setTimeout(() => router.replace('/'), 3000) | ||
}, []) | ||
return ( | ||
<div className="p-6 w-[40%] m-auto"> | ||
<h1 className="text-2xl font-bold">Thank you for your purchase 🎉</h1> | ||
</div> | ||
) | ||
} |
Binary file not shown.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,122 @@ | ||
@import 'tailwindcss'; | ||
@import 'tw-animate-css'; | ||
|
||
@custom-variant dark (&:is(.dark *)); | ||
|
||
@theme inline { | ||
--color-background: var(--background); | ||
--color-foreground: var(--foreground); | ||
--font-sans: var(--font-geist-sans); | ||
--font-mono: var(--font-geist-mono); | ||
--color-sidebar-ring: var(--sidebar-ring); | ||
--color-sidebar-border: var(--sidebar-border); | ||
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground); | ||
--color-sidebar-accent: var(--sidebar-accent); | ||
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground); | ||
--color-sidebar-primary: var(--sidebar-primary); | ||
--color-sidebar-foreground: var(--sidebar-foreground); | ||
--color-sidebar: var(--sidebar); | ||
--color-chart-5: var(--chart-5); | ||
--color-chart-4: var(--chart-4); | ||
--color-chart-3: var(--chart-3); | ||
--color-chart-2: var(--chart-2); | ||
--color-chart-1: var(--chart-1); | ||
--color-ring: var(--ring); | ||
--color-input: var(--input); | ||
--color-border: var(--border); | ||
--color-destructive: var(--destructive); | ||
--color-accent-foreground: var(--accent-foreground); | ||
--color-accent: var(--accent); | ||
--color-muted-foreground: var(--muted-foreground); | ||
--color-muted: var(--muted); | ||
--color-secondary-foreground: var(--secondary-foreground); | ||
--color-secondary: var(--secondary); | ||
--color-primary-foreground: var(--primary-foreground); | ||
--color-primary: var(--primary); | ||
--color-popover-foreground: var(--popover-foreground); | ||
--color-popover: var(--popover); | ||
--color-card-foreground: var(--card-foreground); | ||
--color-card: var(--card); | ||
--radius-sm: calc(var(--radius) - 4px); | ||
--radius-md: calc(var(--radius) - 2px); | ||
--radius-lg: var(--radius); | ||
--radius-xl: calc(var(--radius) + 4px); | ||
} | ||
|
||
:root { | ||
--radius: 0.625rem; | ||
--background: oklch(1 0 0); | ||
--foreground: oklch(0.145 0 0); | ||
--card: oklch(1 0 0); | ||
--card-foreground: oklch(0.145 0 0); | ||
--popover: oklch(1 0 0); | ||
--popover-foreground: oklch(0.145 0 0); | ||
--primary: oklch(0.205 0 0); | ||
--primary-foreground: oklch(0.985 0 0); | ||
--secondary: oklch(0.97 0 0); | ||
--secondary-foreground: oklch(0.205 0 0); | ||
--muted: oklch(0.97 0 0); | ||
--muted-foreground: oklch(0.556 0 0); | ||
--accent: oklch(0.97 0 0); | ||
--accent-foreground: oklch(0.205 0 0); | ||
--destructive: oklch(0.577 0.245 27.325); | ||
--border: oklch(0.922 0 0); | ||
--input: oklch(0.922 0 0); | ||
--ring: oklch(0.708 0 0); | ||
--chart-1: oklch(0.646 0.222 41.116); | ||
--chart-2: oklch(0.6 0.118 184.704); | ||
--chart-3: oklch(0.398 0.07 227.392); | ||
--chart-4: oklch(0.828 0.189 84.429); | ||
--chart-5: oklch(0.769 0.188 70.08); | ||
--sidebar: oklch(0.985 0 0); | ||
--sidebar-foreground: oklch(0.145 0 0); | ||
--sidebar-primary: oklch(0.205 0 0); | ||
--sidebar-primary-foreground: oklch(0.985 0 0); | ||
--sidebar-accent: oklch(0.97 0 0); | ||
--sidebar-accent-foreground: oklch(0.205 0 0); | ||
--sidebar-border: oklch(0.922 0 0); | ||
--sidebar-ring: oklch(0.708 0 0); | ||
} | ||
|
||
.dark { | ||
--background: oklch(0.145 0 0); | ||
--foreground: oklch(0.985 0 0); | ||
--card: oklch(0.205 0 0); | ||
--card-foreground: oklch(0.985 0 0); | ||
--popover: oklch(0.205 0 0); | ||
--popover-foreground: oklch(0.985 0 0); | ||
--primary: oklch(0.922 0 0); | ||
--primary-foreground: oklch(0.205 0 0); | ||
--secondary: oklch(0.269 0 0); | ||
--secondary-foreground: oklch(0.985 0 0); | ||
--muted: oklch(0.269 0 0); | ||
--muted-foreground: oklch(0.708 0 0); | ||
--accent: oklch(0.269 0 0); | ||
--accent-foreground: oklch(0.985 0 0); | ||
--destructive: oklch(0.704 0.191 22.216); | ||
--border: oklch(1 0 0 / 10%); | ||
--input: oklch(1 0 0 / 15%); | ||
--ring: oklch(0.556 0 0); | ||
--chart-1: oklch(0.488 0.243 264.376); | ||
--chart-2: oklch(0.696 0.17 162.48); | ||
--chart-3: oklch(0.769 0.188 70.08); | ||
--chart-4: oklch(0.627 0.265 303.9); | ||
--chart-5: oklch(0.645 0.246 16.439); | ||
--sidebar: oklch(0.205 0 0); | ||
--sidebar-foreground: oklch(0.985 0 0); | ||
--sidebar-primary: oklch(0.488 0.243 264.376); | ||
--sidebar-primary-foreground: oklch(0.985 0 0); | ||
--sidebar-accent: oklch(0.269 0 0); | ||
--sidebar-accent-foreground: oklch(0.985 0 0); | ||
--sidebar-border: oklch(1 0 0 / 10%); | ||
--sidebar-ring: oklch(0.556 0 0); | ||
} | ||
|
||
@layer base { | ||
* { | ||
@apply border-border outline-ring/50; | ||
} | ||
body { | ||
@apply bg-background text-foreground; | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
import type { Metadata } from 'next' | ||
import { Geist, Geist_Mono } from 'next/font/google' | ||
import './globals.css' | ||
|
||
const geistSans = Geist({ | ||
variable: '--font-geist-sans', | ||
subsets: ['latin'], | ||
}) | ||
|
||
const geistMono = Geist_Mono({ | ||
variable: '--font-geist-mono', | ||
subsets: ['latin'], | ||
}) | ||
|
||
export const metadata: Metadata = { | ||
title: 'Polar with Next.js', | ||
description: 'Example of Polar with Next.js', | ||
} | ||
|
||
export default function RootLayout({ | ||
children, | ||
}: Readonly<{ | ||
children: React.ReactNode | ||
}>) { | ||
return ( | ||
<html lang="en"> | ||
<body className={`${geistSans.variable} ${geistMono.variable} antialiased`}>{children}</body> | ||
</html> | ||
) | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.