diff --git a/www/apps/book/public/llms-full.txt b/www/apps/book/public/llms-full.txt
index e1fc5c401c..351092e5a1 100644
--- a/www/apps/book/public/llms-full.txt
+++ b/www/apps/book/public/llms-full.txt
@@ -29714,7 +29714,7 @@ export async function POST(
// ...
is_tax_inclusive: true,
}],
- }
+ },
})
res.send(result)
@@ -47485,8 +47485,8 @@ export const applyFirstPurchasePromoWorkflow = createWorkflow(
entity: "promotion",
fields: ["code"],
filters: {
- code: FIRST_PURCHASE_PROMOTION_CODE
- }
+ code: FIRST_PURCHASE_PROMOTION_CODE,
+ },
}).config({ name: "retrieve-promotions" })
when({
diff --git a/www/apps/resources/.content.eslintrc.mjs b/www/apps/resources/.content.eslintrc.mjs
index aaeab75dcc..e781aeed77 100644
--- a/www/apps/resources/.content.eslintrc.mjs
+++ b/www/apps/resources/.content.eslintrc.mjs
@@ -19,7 +19,11 @@ const compat = new FlatCompat({
export default [
{
- ignores: ["**/references/**/*"],
+ ignores: [
+ "**/references/**/*",
+ // TODO remove this once we support v1 comments
+ "**/nextjs-starter/guides/customize-stripe/**",
+ ],
},
{
plugins: {
diff --git a/www/apps/resources/app/commerce-modules/promotion/promotion-taxes/page.mdx b/www/apps/resources/app/commerce-modules/promotion/promotion-taxes/page.mdx
index 33c6ac6805..47175a4b18 100644
--- a/www/apps/resources/app/commerce-modules/promotion/promotion-taxes/page.mdx
+++ b/www/apps/resources/app/commerce-modules/promotion/promotion-taxes/page.mdx
@@ -91,7 +91,7 @@ export async function POST(
// ...
is_tax_inclusive: true,
}],
- }
+ },
})
res.send(result)
diff --git a/www/apps/resources/app/how-to-tutorials/tutorials/first-purchase-discounts/page.mdx b/www/apps/resources/app/how-to-tutorials/tutorials/first-purchase-discounts/page.mdx
index 7e91f28060..055a7a7789 100644
--- a/www/apps/resources/app/how-to-tutorials/tutorials/first-purchase-discounts/page.mdx
+++ b/www/apps/resources/app/how-to-tutorials/tutorials/first-purchase-discounts/page.mdx
@@ -235,8 +235,8 @@ export const applyFirstPurchasePromoWorkflow = createWorkflow(
entity: "promotion",
fields: ["code"],
filters: {
- code: FIRST_PURCHASE_PROMOTION_CODE
- }
+ code: FIRST_PURCHASE_PROMOTION_CODE,
+ },
}).config({ name: "retrieve-promotions" })
when({
diff --git a/www/apps/resources/app/nextjs-starter/guides/customize-stripe/page.mdx b/www/apps/resources/app/nextjs-starter/guides/customize-stripe/page.mdx
index ff1038ed9f..c154351cae 100644
--- a/www/apps/resources/app/nextjs-starter/guides/customize-stripe/page.mdx
+++ b/www/apps/resources/app/nextjs-starter/guides/customize-stripe/page.mdx
@@ -1,17 +1,19 @@
---
tags:
- storefront
- - payment
+ - name: payment
+ label: Customize Stripe in Next.js Starter
products:
- payment
---
-import { Prerequisites } from "docs-ui"
+import { Prerequisites, InlineIcon } from "docs-ui"
+import { EllipsisHorizontal } from "@medusajs/icons"
export const ogImage = "https://res.cloudinary.com/dza7lstvk/image/upload/v1734007558/Medusa%20Resources/integrations-stripe_qfnwtf.jpg"
export const metadata = {
- title: `Customize the Stripe Integration in the Next.js Starter`,
+ title: `Use Stripe's Payment Element in the Next.js Starter Storefront`,
openGraph: {
images: [
{
@@ -36,21 +38,26 @@ export const metadata = {
# {metadata.title}
-Stripe is a popular payment provider amongst Medusa users. While the Next.js Starter comes with basic Stripe card payments, you can customize it to accept different payment methods through the Stripe Payment Element. The Payment Element is a web UI component that supports over 40 payment methods (like PayPal, local banks, etc) while handling input validation and error management.
+In this tutorial, you'll learn how to customize the Next.js Starter Storefront to use [Stripe's Payment Element](https://docs.stripe.com/payments/payment-element).
-In this guide, you'll learn how to customize the starter's Stripe integration to accept multiple payment methods, such as PayPal, through Stripe.
+By default, the Next.js Starter Storefront comes with a basic Stripe card payment integration. However, you can replace it with Stripe's Payment Element instead.
-
+By using the Payment Element, you can offer a unified payment experience that supports various payment methods, including credit cards, PayPal, iDeal, and more, all within a single component.
-The code snippets are regularly updated to ensure they work with the latest changes in the Next.js Starter Storefront's repository. If you find certain code snippets don't match your starter, make sure you've pulled all changes from the repository. If you have the latest changes, please consider [creating an issue](https://github.com/medusajs/medusa/issues) to update the documentation.
+## Summary
-
+By following this tutorial, you'll learn how to:
-## Step 1: Setup Medusa Project
+- Set up a Medusa application with the Stripe Module Provider.
+- Customize the Next.js Starter Storefront to use Stripe's Payment Element.
-In this step, you'll setup a Medusa application and configure the Stripe Module Provider.
+---
-### Install Medusa Application
+## Step 1: Set Up Medusa Project
+
+In this step, you'll set up a Medusa application and configure the Stripe Module Provider. You can skip this step if you already have a Medusa application running with the Stripe Module Provider configured.
+
+### a. Install Medusa Application
-Once the installation finishes successfully, the Medusa Admin dashboard will open with a form to create a new user. Enter the user's credential and submit the form.
+Once the installation finishes successfully, the Medusa Admin dashboard will open with a form to create a new user. Enter the user's credentials and submit the form.
-Afterwards, you can login with the new user and explore the dashboard. The Next.js Starter Storefront is also running at `http://localhost:8000`.
+Afterwards, you can log in with the new user and explore the dashboard. The Next.js Starter Storefront is also running at `http://localhost:8000`.
@@ -93,7 +100,9 @@ Check out the [troubleshooting guides](../../../troubleshooting/create-medusa-ap
-### Configure Stripe Module Provider
+### b. Configure Stripe Module Provider
+
+Next, you'll configure the [Stripe Module Provider](../../../commerce-modules/payment/payment-provider/stripe/page.mdx) in your Medusa application. The Stripe Module Provider allows you to accept payments through Stripe in your Medusa application.
```
-You also need to add the Stripe publishable API key in the Next.js Starter's environment variables:
+Where `` is your Stripe [Secret API Key](https://support.stripe.com/questions/locate-api-keys-in-the-dashboard).
-```bash
+You also need to add the Stripe [Publishable API Key](https://support.stripe.com/questions/locate-api-keys-in-the-dashboard) in the Next.js Starter Storefront's environment variables:
+
+```bash badgeLabel="Storefront" badgeColor="blue"
NEXT_PUBLIC_STRIPE_KEY=
```
-### Enable Stripe in a Region
+Where `` is your Stripe [Publishable API Key](https://support.stripe.com/questions/locate-api-keys-in-the-dashboard).
-Lastly, you need to add Stripe as a payment provider to one or more regions in your Medusa Admin. To do so:
+### d. Enable Stripe in a Region
-1. Log in to you Medusa Admin dashboard at `http://localhost:9000/app` and go to Settings -> Regions.
-2. Create or select a region.
-3. Click the three dots on the upper right corner, then click “Edit”.
-4. For the Payment Providers field, select “Stripe (Stripe)”
-5. Click the Save button.
+Finally, you need to add Stripe as a payment provider to one or more regions in your Medusa Admin. This will allow customers to use Stripe as a payment method during checkout.
+
+To do that:
+
+1. Log in to your Medusa Admin dashboard at `http://localhost:9000/app`.
+2. Go to Settings -> Regions.
+3. Select a region you want to enable Stripe for (or create a new one).
+4. Click the icon on the upper right corner.
+5. Choose "Edit" from the dropdown menu.
+6. In the Payment Providers field, select “Stripe (STRIPE)”
+7. Click the Save button.
+
+Do this for all regions you want to enable Stripe for.
---
-## Step 2: Update the Payment Element
+## Step 2: Update Payment Step in Checkout
-
+You'll start customizing the Next.js Starter Storefront by updating the payment step in the checkout process. By default, the payment step is implemented to show all available payment methods in the region the customer is in, and allows the customer to select one of them.
-To use Stripe Payment components in a checkout form, you needs to wrap it in a [Stripe Elements](https://stripe.com/en-nl/payments/elements) context provider. Since the Next.js Starter already comes with a simple Stripe Card Element integration, the necessary context is already provided. You don’t have to edit this part for the customizations made in this guide, but in case you wish to make any changes to the provider, the logic is handled in the `src/modules/checkout/components/payment-wrapper/index.tsx` and `src/modules/checkout/components/payment-wrapper/stripe-wrapper.tsx` files.
+In this step, you'll replace the current payment method selection with Stripe's Payment Element. You'll no longer need to handle different payment methods separately, as the Payment Element will automatically adapt to the available methods configured in your Stripe account.
-
+### a. Update the Stripe SDKs
-Within the `` (`src/modules/checkout/components/payment/index.tsx`) component, you present the different payment options to the user. In this file, you'll change the payment integration approach by replacing the custom payment method selection with Stripe's Payment Element component.
+To ensure you have the latest Stripe packages, update the `@stripe/react-stripe-js` and `@stripe/stripe-js` packages in your Next.js Starter Storefront:
+
+```bash npm2yarn badgeLabel="Storefront" badgeColor="blue"
+npm install @stripe/react-stripe-js@latest @stripe/stripe-js@latest
+```
+
+### b. Update the Payment Component
+
+The `Payment` component in `src/modules/checkout/components/payment/index.tsx` shows the payment step in checkout. It handles the payment method selection and submission. You'll update this component first to use the Payment Element.
@@ -175,113 +201,150 @@ The full final code is available at the end of the section.
-First, import the `PaymentElement` component, two Stripe hooks and the type for change events within `PaymentElement`:
+First, add the following imports at the top of the file:
```tsx title="src/modules/checkout/components/payment/index.tsx"
// ...other imports
+import { useContext } from "react"
import { PaymentElement, useElements, useStripe } from "@stripe/react-stripe-js"
import { StripePaymentElementChangeEvent } from "@stripe/stripe-js"
+import { StripeContext } from "../payment-wrapper/stripe-wrapper"
```
-Next, alter the state management within the component to:
+You import components from the Stripe SDKs, and the `StripeContext` created in the `StripeWrapper` component. This context will allow you to check whether Stripe is ready to be used.
-1. Remove the `cardBrand` state.
-2. Rename `cardComplete` to `stripeComplete`.
-3. Keep the loading and error states.
-4. Update the type for `selectedPaymentMethod` to `string` and remove the default value.
+Next, in the `Payment` component, replace the existing state variables with the following:
```tsx title="src/modules/checkout/components/payment/index.tsx"
const [isLoading, setIsLoading] = useState(false)
const [error, setError] = useState(null)
const [stripeComplete, setStripeComplete] = useState(false)
-const [selectedPaymentMethod, setSelectedPaymentMethod] = useState()
-```
+const [selectedPaymentMethod, setSelectedPaymentMethod] = useState("")
-As a next step you can remove the `isStripe` check on line 42. The Medusa payment provider will always be Stripe in this setup, managing different payment methods within the Payment Element. Also remove the `useOptions` object on line 51, as it’s no longer needed.
-
-Next, initiate the two Stripe hooks you imported earlier, but only if the Stripe context is ready:
-
-```tsx title="src/modules/checkout/components/payment/index.tsx"
+const stripeReady = useContext(StripeContext)
const stripe = stripeReady ? useStripe() : null
const elements = stripeReady ? useElements() : null
```
-To track changes in the `PaymentElemement`, add within the component a handler that handles different change events:
+You define the following variables:
+
+- `isLoading`: A boolean that indicates whether the payment is being processed.
+- `error`: A string that holds any error message related to the payment.
+- `stripeComplete`: A boolean that indicates whether operations with the Stripe Payment Element are complete. When this is enabled, the customer can proceed to the "Review" checkout step.
+- `selectedPaymentMethod`: A string that holds the currently selected payment method in the Payment Element. For example, `card` or `eps`.
+- `stripeReady`: A boolean that indicates whether Stripe is ready to be used, fetched from the `StripeContext`.
+ - This context is defined in `src/modules/checkout/components/payment-wrapper/stripe-wrapper.tsx` and it wraps the checkout page in Stripe's `Elements` component, which initializes the Stripe SDKs.
+- `stripe`: The Stripe instance, which is used to interact with the Stripe API. It's only initialized if `stripeReady` from the Stripe context is true.
+- `elements`: The Stripe Elements instance, which is used to manage the Payment Element. It's also only initialized if `stripeReady` is true.
+
+Next, add the following function to the `Payment` component to handle changes in the Payment Element:
```tsx title="src/modules/checkout/components/payment/index.tsx"
const handlePaymentElementChange = async (
- event: StripePaymentElementChangeEvent
- ) => {
- // Catches the selected payment method and sets it to state
- if (event.value.type) {
- setSelectedPaymentMethod(event.value.type)
- }
-
- // Sets stripeComplete on form completion
- setStripeComplete(event.complete)
+ event: StripePaymentElementChangeEvent
+) => {
+ // Catches the selected payment method and sets it to state
+ if (event.value.type) {
+ setSelectedPaymentMethod(event.value.type)
+ }
+
+ // Sets stripeComplete on form completion
+ setStripeComplete(event.complete)
- // Clears any errors on successful completion
- if (event.complete) {
- setError(null)
- }
+ // Clears any errors on successful completion
+ if (event.complete) {
+ setError(null)
+ }
}
```
-Then, edit the `handleSubmit` function to handle user submissions of the payment step. This will not trigger the payment yet, but submit the payment details and render the final Review step in the checkout:
+This function will be called on every change in Stripe's Payment Element, such as when the customer selects a payment method.
+
+In the function, you update the `selectedPaymentMethod` state with the type of payment method selected by the customer, set `stripeComplete` to true when the payment element is complete, and clear any error messages when the payment element is complete.
+
+Then, to customize the payment step's submission, replace the `handleSubmit` function with the following:
```tsx title="src/modules/checkout/components/payment/index.tsx"
- const handleSubmit = async () => {
- setIsLoading(true)
- setError(null)
+const handleSubmit = async () => {
+ setIsLoading(true)
+ setError(null)
- try {
- // Check if the necessary context is ready
- if (!stripe || !elements) {
- setError("Payment processing not ready. Please try again.")
- return
- }
-
- // Submit the payment method details
- await elements.submit().catch((err) => {
- console.error(err)
- setError(err.message || "An error occurred with the payment")
- return
- })
-
- // Navigate to the final checkout step
- router.push(pathname + "?" + createQueryString("step", "review"), {
- scroll: false,
- })
- } catch (err: any) {
- setError(err.message)
- } finally {
- setIsLoading(false)
+ try {
+ // Check if the necessary context is ready
+ if (!stripe || !elements) {
+ setError("Payment processing not ready. Please try again.")
+ return
}
+
+ // Submit the payment method details
+ await elements.submit().catch((err) => {
+ console.error(err)
+ setError(err.message || "An error occurred with the payment")
+ return
+ })
+
+ // Navigate to the final checkout step
+ router.push(pathname + "?" + createQueryString("step", "review"), {
+ scroll: false,
+ })
+ } catch (err: any) {
+ setError(err.message)
+ } finally {
+ setIsLoading(false)
}
+}
```
-To make sure a payment session is initiated on the current Medusa cart once a user reaches the payment step, add within the component an `initStripe` function and call it from a `useEffect` hook:
+In this function, you use the `elements.submit()` method to submit the payment method details entered by the customer in the Payment Element. This doesn't actually confirm the payment yet; it just prepares the payment method for confirmation.
+
+Once the payment method is submitted successfully, you navigate the customer to the Review step of the checkout process.
+
+Next, to make sure a payment session is initialized when the customer reaches the payment step, add the following to the `Payment` component:
```tsx title="src/modules/checkout/components/payment/index.tsx"
- const initStripe = async () => {
- try {
- await initiatePaymentSession(cart, {
- provider_id: "pp_stripe_stripe",
- })
- } catch (err) {
- console.error("Failed to initialize Stripe session:", err)
- setError("Failed to initialize payment. Please try again.")
- }
+const initStripe = async () => {
+ try {
+ await initiatePaymentSession(cart, {
+ // TODO: change the provider ID if using a different ID in medusa-config.ts
+ provider_id: "pp_stripe_stripe",
+ })
+ } catch (err) {
+ console.error("Failed to initialize Stripe session:", err)
+ setError("Failed to initialize payment. Please try again.")
}
+}
- useEffect(() => {
- if (!activeSession && isOpen) {
- initStripe()
- }
- }, [cart, isOpen, activeSession])
+useEffect(() => {
+ if (!activeSession && isOpen) {
+ initStripe()
+ }
+}, [cart, isOpen, activeSession])
```
-Now that you have all the necessary setup in place, you'll add the Stripe `PaymentElement` to the Payment component. Replace the code inside the `!paidByGiftcard && availablePaymentMethods?.length` condition with the following:
+You add an `initStripe` function that initiates a payment session in the Medusa server. Notice that you set the provider ID to `pp_stripe_stripe`, which is the ID of the Stripe payment provider in your Medusa application if the ID in `medusa-config.ts` is `stripe`.
+
+If you used a different ID, change the `stripe` in the middle accordingly. For example, if you set the ID to `payment`, you would set the `provider_id` to `pp_payment_stripe`.
+
+You also add a `useEffect` hook that calls `initStripe` when the payment step is opened and there is no active payment session.
+
+You'll now update the component's return statement to render the Payment Element.
+
+First, find the following lines in the return statement:
+
+```tsx title="src/modules/checkout/components/payment/index.tsx"
+{!paidByGiftcard && availablePaymentMethods?.length && (
+ <>
+ setPaymentMethod(value)}
+ >
+ {/* ... */}
+
+ >
+)}
+```
+
+And replace them with the following:
```tsx title="src/modules/checkout/components/payment/index.tsx"
{!paidByGiftcard &&
@@ -299,7 +362,12 @@ Now that you have all the necessary setup in place, you'll add the Stripe `Payme
}
```
-And refactor the button to reflect the new changes:
+You replace the radio group with the payment methods available in Medusa with the `PaymentElement` component from the Stripe SDK. You pass it the following props:
+
+- `onChange`: A callback function that is called when the payment element's state changes. You pass the `handlePaymentElementChange` function you defined earlier.
+- `options`: An object that contains options for the payment element, such as the `layout`. Refer to [Stripe's documentation](https://docs.stripe.com/payments/payment-element#options) for other options available.
+
+Next, find the button rendered afterward and replace it with the following:
```tsx title="src/modules/checkout/components/payment/index.tsx"