diff --git a/examples/with-stripe-typescript/README.md b/examples/with-stripe-typescript/README.md index 77da54231b2aa..03daf75c95090 100644 --- a/examples/with-stripe-typescript/README.md +++ b/examples/with-stripe-typescript/README.md @@ -73,7 +73,7 @@ Copy the `.env.local.example` file into a file named `.env.local` in the root di cp .env.local.example .env.local ``` -You will need a Stripe account ([register](https://dashboard.stripe.com/register)) to run this sample. Go to the Stripe [developer dashboard](https://stripe.com/docs/development#api-keys) to find your API keys and replace them in the `.env.local` file. +You will need a Stripe account ([register](https://dashboard.stripe.com/register)) to run this sample. Go to the Stripe [developer dashboard](https://dashboard.stripe.com/apikeys) to find your API keys and replace them in the `.env.local` file. ```bash NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY= diff --git a/examples/with-stripe-typescript/app/actions/stripe.ts b/examples/with-stripe-typescript/app/actions/stripe.ts index 8f0955d9fa0b9..43acbc47318eb 100644 --- a/examples/with-stripe-typescript/app/actions/stripe.ts +++ b/examples/with-stripe-typescript/app/actions/stripe.ts @@ -2,14 +2,21 @@ import type { Stripe } from "stripe"; -import { redirect } from "next/navigation"; import { headers } from "next/headers"; import { CURRENCY } from "@/config"; import { formatAmountForStripe } from "@/utils/stripe-helpers"; import { stripe } from "@/lib/stripe"; -export async function createCheckoutSession(data: FormData): Promise { +export async function createCheckoutSession( + data: FormData, +): Promise<{ client_secret: string | null; url: string | null }> { + const ui_mode = data.get( + "uiMode", + ) as Stripe.Checkout.SessionCreateParams.UiMode; + + const origin: string = headers().get("origin") as string; + const checkoutSession: Stripe.Checkout.Session = await stripe.checkout.sessions.create({ mode: "payment", @@ -29,13 +36,20 @@ export async function createCheckoutSession(data: FormData): Promise { }, }, ], - success_url: `${headers().get( - "origin", - )}/donate-with-checkout/result?session_id={CHECKOUT_SESSION_ID}`, - cancel_url: `${headers().get("origin")}/donate-with-checkout`, + ...(ui_mode === "hosted" && { + success_url: `${origin}/donate-with-checkout/result?session_id={CHECKOUT_SESSION_ID}`, + cancel_url: `${origin}/donate-with-checkout`, + }), + ...(ui_mode === "embedded" && { + return_url: `${origin}/donate-with-embedded-checkout/result?session_id={CHECKOUT_SESSION_ID}`, + }), + ui_mode, }); - redirect(checkoutSession.url as string); + return { + client_secret: checkoutSession.client_secret, + url: checkoutSession.url, + }; } export async function createPaymentIntent( diff --git a/examples/with-stripe-typescript/app/components/CheckoutForm.tsx b/examples/with-stripe-typescript/app/components/CheckoutForm.tsx index e459461416ef5..bf2fc54b64cf7 100644 --- a/examples/with-stripe-typescript/app/components/CheckoutForm.tsx +++ b/examples/with-stripe-typescript/app/components/CheckoutForm.tsx @@ -1,5 +1,7 @@ "use client"; +import type Stripe from "stripe"; + import React, { useState } from "react"; import CustomDonationInput from "@/components/CustomDonationInput"; @@ -8,12 +10,22 @@ import StripeTestCards from "@/components/StripeTestCards"; import { formatAmountForDisplay } from "@/utils/stripe-helpers"; import * as config from "@/config"; import { createCheckoutSession } from "@/actions/stripe"; +import getStripe from "@/utils/get-stripejs"; +import { + EmbeddedCheckout, + EmbeddedCheckoutProvider, +} from "@stripe/react-stripe-js"; + +interface CheckoutFormProps { + uiMode: Stripe.Checkout.SessionCreateParams.UiMode; +} -export default function CheckoutForm(): JSX.Element { +export default function CheckoutForm(props: CheckoutFormProps): JSX.Element { const [loading] = useState(false); const [input, setInput] = useState<{ customDonation: number }>({ customDonation: Math.round(config.MAX_AMOUNT / config.AMOUNT_STEP), }); + const [clientSecret, setClientSecret] = useState(null); const handleInputChange: React.ChangeEventHandler = ( e, @@ -23,26 +35,48 @@ export default function CheckoutForm(): JSX.Element { [e.currentTarget.name]: e.currentTarget.value, }); + const formAction = async (data: FormData): Promise => { + const uiMode = data.get( + "uiMode", + ) as Stripe.Checkout.SessionCreateParams.UiMode; + const { client_secret, url } = await createCheckoutSession(data); + + if (uiMode === "embedded") return setClientSecret(client_secret); + + window.location.assign(url as string); + }; + return ( -
- - - - + <> +
+ + + + + + {clientSecret ? ( + + + + ) : null} + ); } diff --git a/examples/with-stripe-typescript/app/donate-with-checkout/page.tsx b/examples/with-stripe-typescript/app/donate-with-checkout/page.tsx index 312dbbbf20a8c..fb5ad615e7c5c 100644 --- a/examples/with-stripe-typescript/app/donate-with-checkout/page.tsx +++ b/examples/with-stripe-typescript/app/donate-with-checkout/page.tsx @@ -3,15 +3,15 @@ import type { Metadata } from "next"; import CheckoutForm from "@/components/CheckoutForm"; export const metadata: Metadata = { - title: "Donate with Checkout | Next.js + TypeScript Example", + title: "Donate with hosted Checkout | Next.js + TypeScript Example", }; export default function DonatePage(): JSX.Element { return (
-

Donate with Checkout

+

Donate with hosted Checkout

Donate to our project 💖

- +
); } diff --git a/examples/with-stripe-typescript/app/donate-with-checkout/result/error.tsx b/examples/with-stripe-typescript/app/donate-with-checkout/result/error.tsx index 0d7fd41bd3178..3b5a579090fb4 100644 --- a/examples/with-stripe-typescript/app/donate-with-checkout/result/error.tsx +++ b/examples/with-stripe-typescript/app/donate-with-checkout/result/error.tsx @@ -1,5 +1,3 @@ -"use client"; - export default function Error({ error }: { error: Error }) { return

{error.message}

; } diff --git a/examples/with-stripe-typescript/app/donate-with-elements/result/error.tsx b/examples/with-stripe-typescript/app/donate-with-elements/result/error.tsx index 0d7fd41bd3178..3b5a579090fb4 100644 --- a/examples/with-stripe-typescript/app/donate-with-elements/result/error.tsx +++ b/examples/with-stripe-typescript/app/donate-with-elements/result/error.tsx @@ -1,5 +1,3 @@ -"use client"; - export default function Error({ error }: { error: Error }) { return

{error.message}

; } diff --git a/examples/with-stripe-typescript/app/donate-with-embedded-checkout/page.tsx b/examples/with-stripe-typescript/app/donate-with-embedded-checkout/page.tsx new file mode 100644 index 0000000000000..b33bf21b7f11b --- /dev/null +++ b/examples/with-stripe-typescript/app/donate-with-embedded-checkout/page.tsx @@ -0,0 +1,17 @@ +import type { Metadata } from "next"; + +import CheckoutForm from "@/components/CheckoutForm"; + +export const metadata: Metadata = { + title: "Donate with embedded Checkout | Next.js + TypeScript Example", +}; + +export default function DonatePage(): JSX.Element { + return ( +
+

Donate with embedded Checkout

+

Donate to our project 💖

+ +
+ ); +} diff --git a/examples/with-stripe-typescript/app/donate-with-embedded-checkout/result/error.tsx b/examples/with-stripe-typescript/app/donate-with-embedded-checkout/result/error.tsx new file mode 100644 index 0000000000000..3b5a579090fb4 --- /dev/null +++ b/examples/with-stripe-typescript/app/donate-with-embedded-checkout/result/error.tsx @@ -0,0 +1,3 @@ +export default function Error({ error }: { error: Error }) { + return

{error.message}

; +} diff --git a/examples/with-stripe-typescript/app/donate-with-embedded-checkout/result/layout.tsx b/examples/with-stripe-typescript/app/donate-with-embedded-checkout/result/layout.tsx new file mode 100644 index 0000000000000..1c6c096c03b17 --- /dev/null +++ b/examples/with-stripe-typescript/app/donate-with-embedded-checkout/result/layout.tsx @@ -0,0 +1,18 @@ +import type { Metadata } from "next"; + +export const metadata: Metadata = { + title: "Checkout Session Result", +}; + +export default function ResultLayout({ + children, +}: { + children: React.ReactNode; +}): JSX.Element { + return ( +
+

Checkout Session Result

+ {children} +
+ ); +} diff --git a/examples/with-stripe-typescript/app/donate-with-embedded-checkout/result/page.tsx b/examples/with-stripe-typescript/app/donate-with-embedded-checkout/result/page.tsx new file mode 100644 index 0000000000000..fc360bc77890a --- /dev/null +++ b/examples/with-stripe-typescript/app/donate-with-embedded-checkout/result/page.tsx @@ -0,0 +1,28 @@ +import type { Stripe } from "stripe"; + +import PrintObject from "@/components/PrintObject"; +import { stripe } from "@/lib/stripe"; + +export default async function ResultPage({ + searchParams, +}: { + searchParams: { session_id: string }; +}): Promise { + if (!searchParams.session_id) + throw new Error("Please provide a valid session_id (`cs_test_...`)"); + + const checkoutSession: Stripe.Checkout.Session = + await stripe.checkout.sessions.retrieve(searchParams.session_id, { + expand: ["line_items", "payment_intent"], + }); + + const paymentIntent = checkoutSession.payment_intent as Stripe.PaymentIntent; + + return ( + <> +

Status: {paymentIntent.status}

+

Checkout Session response:

+ + + ); +} diff --git a/examples/with-stripe-typescript/app/page.tsx b/examples/with-stripe-typescript/app/page.tsx index f9335b3c83d06..61c2c2acaf2d5 100644 --- a/examples/with-stripe-typescript/app/page.tsx +++ b/examples/with-stripe-typescript/app/page.tsx @@ -9,12 +9,21 @@ export const metadata: Metadata = { export default function IndexPage(): JSX.Element { return (
    +
  • + +

    Donate with embedded Checkout

    + + +
  • -

    Donate with Checkout

    +

    Donate with hosted Checkout

  • diff --git a/examples/with-stripe-typescript/package.json b/examples/with-stripe-typescript/package.json index deca451495d89..7847d1faf19de 100644 --- a/examples/with-stripe-typescript/package.json +++ b/examples/with-stripe-typescript/package.json @@ -6,13 +6,13 @@ "start": "next start" }, "dependencies": { - "@stripe/react-stripe-js": "2.1.1", - "@stripe/stripe-js": "1.54.1", + "@stripe/react-stripe-js": "2.4.0", + "@stripe/stripe-js": "2.2.2", "next": "latest", "react": "18.2.0", "react-dom": "18.2.0", "server-only": "0.0.1", - "stripe": "12.14.0" + "stripe": "14.8.0" }, "devDependencies": { "@types/node": "20.4.6", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c7acb0bc1845c..87db34c61c2c4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -747,7 +747,7 @@ importers: packages/eslint-config-next: dependencies: '@next/eslint-plugin-next': - specifier: 14.1.1-canary.6 + specifier: 14.1.1-canary.7 version: link:../eslint-plugin-next '@rushstack/eslint-patch': specifier: ^1.3.3 @@ -809,7 +809,7 @@ importers: packages/next: dependencies: '@next/env': - specifier: 14.1.1-canary.6 + specifier: 14.1.1-canary.7 version: link:../next-env '@swc/helpers': specifier: 0.5.2 @@ -933,19 +933,19 @@ importers: specifier: 1.1.0 version: 1.1.0 '@next/polyfill-module': - specifier: 14.1.1-canary.6 + specifier: 14.1.1-canary.7 version: link:../next-polyfill-module '@next/polyfill-nomodule': - specifier: 14.1.1-canary.6 + specifier: 14.1.1-canary.7 version: link:../next-polyfill-nomodule '@next/react-dev-overlay': - specifier: 14.1.1-canary.6 + specifier: 14.1.1-canary.7 version: link:../react-dev-overlay '@next/react-refresh-utils': - specifier: 14.1.1-canary.6 + specifier: 14.1.1-canary.7 version: link:../react-refresh-utils '@next/swc': - specifier: 14.1.1-canary.6 + specifier: 14.1.1-canary.7 version: link:../next-swc '@opentelemetry/api': specifier: 1.6.0 @@ -1077,8 +1077,8 @@ importers: specifier: 0.26.2 version: 0.26.2 '@vercel/turbopack-ecmascript-runtime': - specifier: https://gitpkg-fork.vercel.sh/vercel/turbo/crates/turbopack-ecmascript-runtime/js?turbopack-240122.1 - version: '@gitpkg-fork.vercel.sh/vercel/turbo/crates/turbopack-ecmascript-runtime/js?turbopack-240122.1(react-refresh@0.12.0)(webpack@5.86.0)' + specifier: https://gitpkg-fork.vercel.sh/vercel/turbo/crates/turbopack-ecmascript-runtime/js?turbopack-240123.1 + version: '@gitpkg-fork.vercel.sh/vercel/turbo/crates/turbopack-ecmascript-runtime/js?turbopack-240123.1(react-refresh@0.12.0)(webpack@5.86.0)' acorn: specifier: 8.5.0 version: 8.5.0 @@ -1569,7 +1569,7 @@ importers: version: 1.0.20 devDependencies: next: - specifier: 14.1.1-canary.6 + specifier: 14.1.1-canary.7 version: link:../next outdent: specifier: 0.8.0 @@ -25626,9 +25626,9 @@ packages: /zwitch@2.0.4: resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} - '@gitpkg-fork.vercel.sh/vercel/turbo/crates/turbopack-ecmascript-runtime/js?turbopack-240122.1(react-refresh@0.12.0)(webpack@5.86.0)': - resolution: {tarball: https://gitpkg-fork.vercel.sh/vercel/turbo/crates/turbopack-ecmascript-runtime/js?turbopack-240122.1} - id: '@gitpkg-fork.vercel.sh/vercel/turbo/crates/turbopack-ecmascript-runtime/js?turbopack-240122.1' + '@gitpkg-fork.vercel.sh/vercel/turbo/crates/turbopack-ecmascript-runtime/js?turbopack-240123.1(react-refresh@0.12.0)(webpack@5.86.0)': + resolution: {tarball: https://gitpkg-fork.vercel.sh/vercel/turbo/crates/turbopack-ecmascript-runtime/js?turbopack-240123.1} + id: '@gitpkg-fork.vercel.sh/vercel/turbo/crates/turbopack-ecmascript-runtime/js?turbopack-240123.1' name: '@vercel/turbopack-ecmascript-runtime' version: 0.0.0 dependencies: