30
loading...
This website collects cookies to deliver better user experience
/paypal/createOrder
to create a new Paypal Order. The OrderID and a status of PENDING is stored in SQLite through Prismapaypal\captureOrder
to capture the payment and then update the status to PAID.pnpx create-next-app --typescript
pnpm i @paypal/checkout-server-sdk prisma @prisma/client prisma @paypal/react-paypal-js axios react-query
baseUrl: "./"
in tsconfig.json
to make imports easy to read.prisma/schema.prisma
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "sqlite"
url = "file:./dev.db"
}
model Payment {
id Int @id @default(autoincrement())
orderID String
status String
}
pnpm prisma migrate dev
pnpm prisma generate
lib/prisma.ts
import {PrismaClient} from '@prisma/client'
// Prevent multiple instances of Prisma Client in development
declare const global: typeof globalThis & {prisma?: PrismaClient}
const prisma = global.prisma || new PrismaClient()
if (process.env.NODE_ENV === 'development') global.prisma = prisma
export default prisma
.env
file and add your PayPal Client and SecretNEXT_PUBLIC_PAYPAL_CLIENT_ID=
PAYPAL_CLIENT_SECRET=
PAYPAL_CLIENT_ID=
lib/paypal.ts
import checkoutNodeJssdk from '@paypal/checkout-server-sdk'
const configureEnvironment = function () {
const clientId = process.env.PAYPAL_CLIENT_ID
const clientSecret = process.env.PAYPAL_CLIENT_SECRET
return process.env.NODE_ENV === 'production'
? new checkoutNodeJssdk.core.LiveEnvironment(clientId, clientSecret)
: new checkoutNodeJssdk.core.SandboxEnvironment(clientId, clientSecret)
}
const client = function () {
return new checkoutNodeJssdk.core.PayPalHttpClient(configureEnvironment())
}
export default client
/api/paypal/createOrder.ts
import prisma from 'lib/prisma'
import type { NextApiRequest, NextApiResponse } from 'next'
import client from 'lib/paypal'
import paypal from '@paypal/checkout-server-sdk'
export default async function handle(
req: NextApiRequest,
res: NextApiResponse,
) {
const PaypalClient = client()
//This code is lifted from https://github.com/paypal/Checkout-NodeJS-SDK
const request = new paypal.orders.OrdersCreateRequest()
request.headers['prefer'] = 'return=representation'
request.requestBody({
intent: 'CAPTURE',
purchase_units: [
{
amount: {
currency_code: 'PHP',
value: '100.00',
},
},
],
})
const response = await PaypalClient.execute(request)
if (response.statusCode !== 201) {
res.status(500)
}
//Once order is created store the data using Prisma
await prisma.payment.create({
data: {
orderID: response.result.id,
status: 'PENDING',
},
})
res.json({ orderID: response.result.id })
}
api/paypal/captureOrder.ts
import type { NextApiRequest, NextApiResponse } from 'next'
import client from 'lib/paypal'
import paypal from '@paypal/checkout-server-sdk'
import prisma from 'lib/prisma'
export default async function handle(
req: NextApiRequest,
res: NextApiResponse,
) {
//Capture order to complete payment
const { orderID } = req.body
const PaypalClient = client()
const request = new paypal.orders.OrdersCaptureRequest(orderID)
request.requestBody({})
const response = await PaypalClient.execute(request)
if (!response) {
res.status(500)
}
// Update payment to PAID status once completed
await prisma.payment.updateMany({
where: {
orderID,
},
data: {
status: 'PAID',
},
})
res.json({ ...response.result })
}
_app.tsx
import '../styles/globals.css'
import type {AppProps} from 'next/app'
import {QueryClient, QueryClientProvider} from 'react-query'
const queryClient = new QueryClient()
function MyApp({Component, pageProps}: AppProps) {
return (
<QueryClientProvider client={queryClient}>
<Component {...pageProps} />
</QueryClientProvider>
)
}
export default MyApp
react-query
for ease of making async callsindex.tsx
import axios, { AxiosError } from 'axios'
import Head from 'next/head'
import Image from 'next/image'
import { useMutation } from 'react-query'
import styles from '../styles/Home.module.css'
import {
PayPalScriptProvider,
PayPalButtons,
FUNDING,
} from '@paypal/react-paypal-js'
export default function Home() {
const createMutation = useMutation<{ data: any }, AxiosError, any, Response>(
(): any => axios.post('/api/paypal/createOrder'),
)
const captureMutation = useMutation<string, AxiosError, any, Response>(
(data): any => axios.post('/api/paypal/captureOrder', data),
)
const createPayPalOrder = async (): Promise<string> => {
const response = await createMutation.mutateAsync({})
return response.data.orderID
}
const onApprove = async (data: OnApproveData): Promise<void> => {
return captureMutation.mutate({ orderID: data.orderID })
}
return (
<div className={styles.container}>
<Head>
<title>Create Next App</title>
<meta name="description" content="Generated by create next app" />
<link rel="icon" href="/favicon.ico" />
</Head>
<main className={styles.main}>
{captureMutation.data && (
<div>{JSON.stringify(captureMutation.data)}</div>
)}
<PayPalScriptProvider
options={{
'client-id': process.env.NEXT_PUBLIC_PAYPAL_CLIENT_ID as string,
currency: 'PHP',
}}
>
<PayPalButtons
style={{
color: 'gold',
shape: 'rect',
label: 'pay',
height: 50,
}}
fundingSource={FUNDING.PAYPAL}
createOrder={createPayPalOrder}
onApprove={onApprove}
/>
</PayPalScriptProvider>
</main>
<footer className={styles.footer}>
<a
href="https://vercel.com?utm_source=create-next-app&utm_medium=default-template&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
Powered by{' '}
<span className={styles.logo}>
<Image src="/vercel.svg" alt="Vercel Logo" width={72} height={16} />
</span>
</a>
</footer>
</div>
)
}
declare global {
interface Window {
paypal: any
}
}
interface OnApproveData {
billingToken?: string | null
facilitatorAccessToken: string
orderID: string
payerID?: string | null
paymentID?: string | null
subscriptionID?: string | null
authCode?: string | null
}
createPaypalOrder
and onApprove
functions. Both functions are passed as props to the PayPal Button. createPaypalOrder
is initially called on click of the PayPal button which triggers creation of the PayPal Order. The mutation returns the orderID that will be consumed by the onApprove function to capture the payment. After successful payment, the result is JSON stringified and displayed in the browser.