├── .env.example
├── .eslintrc.json
├── .gitignore
├── README.md
├── app
├── api
│ └── create-payment-intent
│ │ └── route.ts
├── favicon.ico
├── globals.css
├── layout.tsx
├── page.tsx
└── payment-success
│ └── page.tsx
├── components
└── CheckoutPage.tsx
├── lib
└── convertToSubcurrency.ts
├── next.config.mjs
├── package-lock.json
├── package.json
├── postcss.config.mjs
├── public
├── next.svg
└── vercel.svg
├── tailwind.config.ts
└── tsconfig.json
/.env.example:
--------------------------------------------------------------------------------
1 | ACME_CONTACT='xxx@gmail.com'
2 | ACME_DIRECTORY_URL='https://anchor.dev/xxx/localhost/xxx/xxx/xxx'
3 | ACME_KID='xxx'
4 | ACME_HMAC_KEY='xxx'
5 | HTTPS_PORT='xxx'
6 | SERVER_NAMES='xxx.lcl.host,xxx.localhost'
7 |
8 | NEXT_PUBLIC_STRIPE_PUBLIC_KEY=pk_test_xxx
9 | STRIPE_SECRET_KEY=sk_test_xxx
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "next/core-web-vitals"
3 | }
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 | .yarn/install-state.gz
8 |
9 | # testing
10 | /coverage
11 |
12 | # next.js
13 | /.next/
14 | /out/
15 |
16 | # production
17 | /build
18 |
19 | # misc
20 | .DS_Store
21 | *.pem
22 |
23 | # debug
24 | npm-debug.log*
25 | yarn-debug.log*
26 | yarn-error.log*
27 |
28 | # local env files
29 | .env*.local
30 |
31 | # vercel
32 | .vercel
33 |
34 | # typescript
35 | *.tsbuildinfo
36 | next-env.d.ts
37 |
38 | # Anchor files
39 | tmp
40 |
41 | certificates
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
2 |
3 | ## Getting Started
4 |
5 | First, run the development server:
6 |
7 | ```bash
8 | npm run dev
9 | # or
10 | yarn dev
11 | # or
12 | pnpm dev
13 | # or
14 | bun dev
15 | ```
16 |
17 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
18 |
19 | You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
20 |
21 | This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.
22 |
23 | ## Learn More
24 |
25 | To learn more about Next.js, take a look at the following resources:
26 |
27 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
28 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
29 |
30 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
31 |
32 | ## Deploy on Vercel
33 |
34 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
35 |
36 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
37 |
--------------------------------------------------------------------------------
/app/api/create-payment-intent/route.ts:
--------------------------------------------------------------------------------
1 | import { NextRequest, NextResponse } from "next/server";
2 | const stripe = require("stripe")(process.env.STRIPE_SECRET_KEY);
3 |
4 | export async function POST(request: NextRequest) {
5 | try {
6 | const { amount } = await request.json();
7 |
8 | const paymentIntent = await stripe.paymentIntents.create({
9 | amount: amount,
10 | currency: "usd",
11 | automatic_payment_methods: { enabled: true },
12 | });
13 |
14 | return NextResponse.json({ clientSecret: paymentIntent.client_secret });
15 | } catch (error) {
16 | console.error("Internal Error:", error);
17 | // Handle other errors (e.g., network issues, parsing errors)
18 | return NextResponse.json(
19 | { error: `Internal Server Error: ${error}` },
20 | { status: 500 }
21 | );
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/app/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sonnysangha/stripe-payment-elements-with-https-nextjs-14-demo/e50a30ff52a83019baabdb7ade009f8d6aaba2e8/app/favicon.ico
--------------------------------------------------------------------------------
/app/globals.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
--------------------------------------------------------------------------------
/app/layout.tsx:
--------------------------------------------------------------------------------
1 | import "./globals.css";
2 |
3 | export default function RootLayout({
4 | children,
5 | }: Readonly<{
6 | children: React.ReactNode;
7 | }>) {
8 | return (
9 |
10 |
{children}
11 |
12 | );
13 | }
14 |
--------------------------------------------------------------------------------
/app/page.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import CheckoutPage from "@/components/CheckoutPage";
4 | import convertToSubcurrency from "@/lib/convertToSubcurrency";
5 | import { Elements } from "@stripe/react-stripe-js";
6 | import { loadStripe } from "@stripe/stripe-js";
7 |
8 | if (process.env.NEXT_PUBLIC_STRIPE_PUBLIC_KEY === undefined) {
9 | throw new Error("NEXT_PUBLIC_STRIPE_PUBLIC_KEY is not defined");
10 | }
11 | const stripePromise = loadStripe(process.env.NEXT_PUBLIC_STRIPE_PUBLIC_KEY);
12 |
13 | export default function Home() {
14 | const amount = 49.99;
15 |
16 | return (
17 |
18 |
19 |
Sonny
20 |
21 | has requested
22 | ${amount}
23 |
24 |
25 |
26 |
34 |
35 |
36 |
37 | );
38 | }
39 |
--------------------------------------------------------------------------------
/app/payment-success/page.tsx:
--------------------------------------------------------------------------------
1 | export default function PaymentSuccess({
2 | searchParams: { amount },
3 | }: {
4 | searchParams: { amount: string };
5 | }) {
6 | return (
7 |
8 |
9 |
Thank you!
10 |
You successfully sent
11 |
12 |
13 | ${amount}
14 |
15 |
16 |
17 | );
18 | }
19 |
--------------------------------------------------------------------------------
/components/CheckoutPage.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import React, { useEffect, useState } from "react";
4 | import {
5 | useStripe,
6 | useElements,
7 | PaymentElement,
8 | } from "@stripe/react-stripe-js";
9 | import convertToSubcurrency from "@/lib/convertToSubcurrency";
10 |
11 | const CheckoutPage = ({ amount }: { amount: number }) => {
12 | const stripe = useStripe();
13 | const elements = useElements();
14 | const [errorMessage, setErrorMessage] = useState();
15 | const [clientSecret, setClientSecret] = useState("");
16 | const [loading, setLoading] = useState(false);
17 |
18 | useEffect(() => {
19 | fetch("/api/create-payment-intent", {
20 | method: "POST",
21 | headers: {
22 | "Content-Type": "application/json",
23 | },
24 | body: JSON.stringify({ amount: convertToSubcurrency(amount) }),
25 | })
26 | .then((res) => res.json())
27 | .then((data) => setClientSecret(data.clientSecret));
28 | }, [amount]);
29 |
30 | const handleSubmit = async (event: React.FormEvent) => {
31 | event.preventDefault();
32 | setLoading(true);
33 |
34 | if (!stripe || !elements) {
35 | return;
36 | }
37 |
38 | const { error: submitError } = await elements.submit();
39 |
40 | if (submitError) {
41 | setErrorMessage(submitError.message);
42 | setLoading(false);
43 | return;
44 | }
45 |
46 | const { error } = await stripe.confirmPayment({
47 | elements,
48 | clientSecret,
49 | confirmParams: {
50 | return_url: `http://www.localhost:3000/payment-success?amount=${amount}`,
51 | },
52 | });
53 |
54 | if (error) {
55 | // This point is only reached if there's an immediate error when
56 | // confirming the payment. Show the error to your customer (for example, payment details incomplete)
57 | setErrorMessage(error.message);
58 | } else {
59 | // The payment UI automatically closes with a success animation.
60 | // Your customer is redirected to your `return_url`.
61 | }
62 |
63 | setLoading(false);
64 | };
65 |
66 | if (!clientSecret || !stripe || !elements) {
67 | return (
68 |
69 |
73 |
74 | Loading...
75 |
76 |
77 |
78 | );
79 | }
80 |
81 | return (
82 |
94 | );
95 | };
96 |
97 | export default CheckoutPage;
98 |
--------------------------------------------------------------------------------
/lib/convertToSubcurrency.ts:
--------------------------------------------------------------------------------
1 | function convertToSubcurrency(amount: number, factor = 100) {
2 | return Math.round(amount * factor);
3 | }
4 |
5 | export default convertToSubcurrency;
6 |
--------------------------------------------------------------------------------
/next.config.mjs:
--------------------------------------------------------------------------------
1 | // next.config.mjs
2 |
3 | import autoCert from "anchor-pki/auto-cert/integrations/next";
4 |
5 | const withAutoCert = autoCert({
6 | enabledEnv: "development",
7 | });
8 |
9 | const nextConfig = {};
10 |
11 | export default withAutoCert(nextConfig);
12 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "anchor-demo",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev",
7 | "build": "next build",
8 | "start": "next start",
9 | "lint": "next lint"
10 | },
11 | "dependencies": {
12 | "@stripe/react-stripe-js": "^2.7.1",
13 | "@stripe/stripe-js": "^3.5.0",
14 | "anchor-pki": "^0.3.2",
15 | "next": "^14.2.4",
16 | "react": "^18",
17 | "react-dom": "^18",
18 | "stripe": "^15.11.0"
19 | },
20 | "devDependencies": {
21 | "@types/node": "^20",
22 | "@types/react": "^18",
23 | "@types/react-dom": "^18",
24 | "eslint": "^8",
25 | "eslint-config-next": "14.2.4",
26 | "postcss": "^8",
27 | "tailwindcss": "^3.4.1",
28 | "typescript": "^5"
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/postcss.config.mjs:
--------------------------------------------------------------------------------
1 | /** @type {import('postcss-load-config').Config} */
2 | const config = {
3 | plugins: {
4 | tailwindcss: {},
5 | },
6 | };
7 |
8 | export default config;
9 |
--------------------------------------------------------------------------------
/public/next.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/vercel.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tailwind.config.ts:
--------------------------------------------------------------------------------
1 | import type { Config } from "tailwindcss";
2 |
3 | const config: Config = {
4 | content: [
5 | "./pages/**/*.{js,ts,jsx,tsx,mdx}",
6 | "./components/**/*.{js,ts,jsx,tsx,mdx}",
7 | "./app/**/*.{js,ts,jsx,tsx,mdx}",
8 | ],
9 | theme: {
10 | extend: {
11 | backgroundImage: {
12 | "gradient-radial": "radial-gradient(var(--tw-gradient-stops))",
13 | "gradient-conic":
14 | "conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))",
15 | },
16 | },
17 | },
18 | plugins: [],
19 | };
20 | export default config;
21 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "lib": ["dom", "dom.iterable", "esnext"],
4 | "allowJs": true,
5 | "skipLibCheck": true,
6 | "strict": true,
7 | "noEmit": true,
8 | "esModuleInterop": true,
9 | "module": "esnext",
10 | "moduleResolution": "bundler",
11 | "resolveJsonModule": true,
12 | "isolatedModules": true,
13 | "jsx": "preserve",
14 | "incremental": true,
15 | "plugins": [
16 | {
17 | "name": "next"
18 | }
19 | ],
20 | "paths": {
21 | "@/*": ["./*"]
22 | }
23 | },
24 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
25 | "exclude": ["node_modules"]
26 | }
27 |
--------------------------------------------------------------------------------