# Set up a subscription with PayPal Learn how to create and charge for a subscription with PayPal. # Direct API Use this guide to set up a *subscription* (A Subscription represents the product details associated with the plan that your customer subscribes to. Allows you to charge the customer on a recurring basis) using [PayPal](https://docs.stripe.com/payments/paypal.md) as a payment method. ## Before you begin - To accept PayPal subscriptions on Stripe, you must [enable PayPal recurring payments in the Dashboard](https://docs.stripe.com/payments/paypal/set-up-future-payments.md?platform=web#enable-recurring-payments). - This feature is only available to specific business locations. [Review the business locations to confirm eligibility](https://docs.stripe.com/payments/paypal.md). ## Create a product and price [Dashboard] [Products](https://docs.stripe.com/api/products.md) represent the item or service you’re selling. [Prices](https://docs.stripe.com/api/prices.md) define how much and how frequently you charge for a product. This includes how much the product costs, what currency you accept, and whether it’s a one-time or recurring charge. If you only have a few products and prices, create and manage them in the Dashboard. This guide uses a stock photo service as an example and charges customers a 15 EUR monthly subscription. To model this: 1. Go to the [Products](https://dashboard.stripe.com/products?active=true) page and click **Create product**. 1. Enter a **Name** for the product. You can optionally add a **Description** and upload an image of the product. 1. Select a **Product tax code**. Learn more about [product tax codes](https://docs.stripe.com/tax/tax-codes.md). 1. Select **Recurring**. Then enter **15** for the price and select **EUR** as the currency. 1. Choose whether to **Include tax in price**. You can either use the default value from your [tax settings](https://dashboard.stripe.com/test/settings/tax) or set the value manually. In this example, select **Auto**. 1. Select **Monthly** for the **Billing period**. 1. Click **More pricing options**. Then select **Flat rate** as the pricing model for this example. Learn more about [flat rate](https://docs.stripe.com/products-prices/pricing-models.md#flat-rate) and other [pricing models](https://docs.stripe.com/products-prices/pricing-models.md). 1. Add an internal **Price description** and [Lookup key](https://docs.stripe.com/products-prices/manage-prices.md#lookup-keys) to organize, query, and update specific prices in the future. 1. Click **Next**. Then click **Add product**. After you create the product and the price, record the price ID so you can use it in subsequent steps. The pricing page displays the ID and it looks similar to this: `price_G0FvDp6vZvdwRZ`. ## Create or retrieve a customer before setup [Server-side] To reuse a PayPal payment method for future payments, attach it to an object that represents your customer. > #### Use the Accounts v2 API to represent customers > > The Accounts v2 API is generally available for Connect users, and in public preview for other Stripe users. If you’re part of the Accounts v2 preview, you need to specify a [specify a preview version](https://docs.stripe.com/api-v2-overview.md#sdk-and-api-versioning) in your code. > > To request access to the Accounts v2 preview, > > For most use cases, we recommend [modeling your customers as customer-configured Account objects](https://docs.stripe.com/accounts-v2/use-accounts-as-customers.md) instead of using [Customer](https://docs.stripe.com/api/customers.md) objects. Create a customer-configured [Account](https://docs.stripe.com/api/v2/core/accounts/create.md#v2_create_accounts-configuration-customer) or [Customer](https://docs.stripe.com/api/customers/create.md) when your customer creates an account with your business, or when saving a payment method. Associate the object’s ID with your own internal representation of a customer. Create a new customer or retrieve an existing one to associate with this payment. #### Accounts v2 ```curl curl -X POST https://api.stripe.com/v2/core/accounts \ -H "Authorization: Bearer <>" \ -H "Stripe-Version: 2026-05-27.preview" \ --json '{ "contact_email": "jenny.rosen@example.com", "display_name": "Jenny Rosen", "configuration": { "customer": {} }, "include": [ "configuration.customer" ] }' ``` #### Customers v1 ```curl curl https://api.stripe.com/v1/customers \ -u "<>:" \ -d "name=Jenny Rosen" \ --data-urlencode "email=jenny.rosen@example.com" ``` ## Create a SetupIntent [Server-side] A [SetupIntent](https://docs.stripe.com/api/setup_intents.md) is an object that represents your intent to set up a customer’s payment method for future payments. The `SetupIntent` tracks the steps of this setup process. Create a `SetupIntent` on your server. Include `paypal` in the [payment_method_types](https://docs.stripe.com/api/setup_intents/create.md#create_setup_intent-payment_method_types) array and specify the ID of the customer-configured [Account](https://docs.stripe.com/api/v2/core/accounts.md) or [Customer](https://docs.stripe.com/api/customers.md). #### Accounts v2 ```curl curl https://api.stripe.com/v1/setup_intents \ -u "<>:" \ -d "customer_account={{CUSTOMERACCOUNT_ID}}" \ -d "payment_method_types[]=paypal" \ -d "payment_method_data[type]=paypal" ``` #### Customers v1 ```curl curl https://api.stripe.com/v1/setup_intents \ -u "<>:" \ -d "customer={{CUSTOMER_ID}}" \ -d "payment_method_types[]=paypal" \ -d "payment_method_data[type]=paypal" ``` The `SetupIntent` object contains a [client_secret](https://docs.stripe.com/api/setup_intents/object.md#setup_intent_object-client_secret), a unique key that you need to pass to Stripe on the client side to redirect your buyer to PayPal and authorize the mandate. ## Redirect your customer [Client-side] When a customer attempts to set up their PayPal account for future payments, we recommend you use [Stripe.js](https://docs.stripe.com/js.md) to confirm the SetupIntent. Stripe.js is our foundational JavaScript library for building payment flows. It will automatically handle complexities like the redirect described below, and enables you to easily extend your integration to other payment methods in the future. Include the Stripe.js script on your checkout page by adding it to the head of your HTML file. ```html Checkout ``` Create an instance of Stripe.js with the following JavaScript on your checkout page. ```javascript // Set your publishable key. Remember to change this to your live publishable key in production! // See your keys here: https://dashboard.stripe.com/apikeys const stripe = Stripe('<>', {} ); ``` To confirm the setup on the client side, pass the client secret of the SetupIntent object that you created in Step 3. The client secret is different from your API keys that authenticate Stripe API requests. It should still be handled carefully because it can complete the charge. Don’t log it, embed it in URLs, or expose it to anyone but the customer. ### Confirm PayPal Setup To authorize you to use their PayPal account for future payments, your customer will be redirected to a PayPal billing agreement page, which they will need to approve before being redirected back to your website. Use [stripe.confirmPayPalSetup](https://docs.stripe.com/js/setup_intents/confirm_paypal_setup) to handle the redirect away from your page and to complete the setup. Add a `return_url` to this function to indicate where Stripe should redirect the user to after they approve the billing agreement on PayPal’s website. ```javascript // Redirects away from the client const {error} = await stripe.confirmPayPalSetup( '{{SETUP_INTENT_CLIENT_SECRET}}', { return_url: 'https://example.com/setup/complete', mandate_data: { customer_acceptance: { type: 'online', online: { infer_from_client: true } } }, } ); if (error) { // Inform the customer that there was an error. } ``` You can find the Payment Method payer ID and Billing Agreement ID on the resulting [Mandate](https://docs.stripe.com/api/mandates/.md) under the [payment_method_details](https://docs.stripe.com/api/mandates/object.md#mandate_object-payment_method_details-paypal) property. You can also find the buyer’s email and payer ID in the [paypal](https://docs.stripe.com/api/payment_methods/object.md#payment_method_object-paypal) property on the [PaymentMethod](https://docs.stripe.com/api/payment_methods.md). | Field | Value | | ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- | | `payer_email` | The email address of the payer on their PayPal account. | | `payer_id` | A unique ID of the payer’s PayPal account. | | `billing_agreement_id` | The PayPal Billing Agreement ID (BAID). This is an ID generated by PayPal which represents the mandate between the business and the customer. | ## Monitor webhooks [Server-side] Use a method such as [webhooks](https://docs.stripe.com/payments/payment-intents/verifying-status.md#webhooks) to confirm the billing agreement was authorized successfully by your customer, instead of relying on your customer to return to the payment status page. When a customer successfully authorizes the billing agreement, the SetupIntent emits the [setup_intent.succeeded](https://docs.stripe.com/api/events/types.md#event_types-setup_intent.succeeded) *webhook* (A webhook is a real-time push notification sent to your application as a JSON payload through HTTPS requests) event. If a customer doesn’t successfully authorize the billing agreement, the SetupIntent will emit the [setup_intent.setup_failed](https://docs.stripe.com/api/events/types.md#event_types-setup_intent.setup_failed) webhook event and returns to a status of `requires_payment_method`. When a customer revokes the billing agreement from their PayPal account, the [mandate.updated](https://docs.stripe.com/api/events/types.md#event_types-mandate.updated) is emitted. ## Create the subscription [Server-side] Create a [subscription](https://docs.stripe.com/api/subscriptions.md) with the price and customer: #### Accounts v2 ```curl curl https://api.stripe.com/v1/subscriptions \ -u "<>:" \ -d "customer_account={{CUSTOMERACCOUNT_ID}}" \ -d default_payment_method=pm_1F0c9v2eZvKYlo2CJDeTrB4n \ -d "items[0][price]=price_F52b2UdntfQsfR" \ -d "expand[0]=latest_invoice.confirmation_secret" \ -d off_session=true ``` #### Customers v1 ```curl curl https://api.stripe.com/v1/subscriptions \ -u "<>:" \ -d "customer={{CUSTOMER_ID}}" \ -d default_payment_method=pm_1F0c9v2eZvKYlo2CJDeTrB4n \ -d "items[0][price]=price_F52b2UdntfQsfR" \ -d "expand[0]=latest_invoice.confirmation_secret" \ -d off_session=true ``` Creating subscriptions automatically charges customers because the [default payment method](https://docs.stripe.com/api/customers/create.md#create_customer-invoice_settings-default_payment_method) is set. After a successful payment, the status in the [Stripe Dashboard](https://dashboard.stripe.com/test/subscriptions) changes to **Active**. The price you created earlier determines subsequent billings. ## Manage subscription status [Client-side] When the initial payment succeeds, the status of the subscription is `active` and no further action is needed. When payments fail, the status is changed to the **Subscription status** configured in your [automatic collection settings](https://docs.stripe.com/invoicing/automatic-collection.md). Notify the customer after a failure and [charge them with a different payment method](https://docs.stripe.com/billing/subscriptions/overview.md#requires-payment-method). ## Update a subscription [Server-side] When you update a subscription, you need to specify `off_session=true`. Otherwise, any new payment requires a user redirection to PayPal for confirmation. For example, if you want to change the quantity of an item included in the subscription you can use: #### Accounts v2 ```curl curl https://api.stripe.com/v1/subscriptions \ -u "<>:" \ -d "customer_account={{CUSTOMERACCOUNT_ID}}" \ -d default_payment_method=pm_1F0c9v2eZvKYlo2CJDeTrB4n \ -d "items[0][price]=price_F52b2UdntfQsfR" \ -d "items[0][quantity]=2" \ -d off_session=true ``` #### Customers v1 ```curl curl https://api.stripe.com/v1/subscriptions \ -u "<>:" \ -d "customer={{CUSTOMER_ID}}" \ -d default_payment_method=pm_1F0c9v2eZvKYlo2CJDeTrB4n \ -d "items[0][price]=price_F52b2UdntfQsfR" \ -d "items[0][quantity]=2" \ -d off_session=true ``` ## Test the integration Test your PayPal integration with your [test API keys](https://docs.stripe.com/keys.md#test-live-modes) by viewing the redirect page. You can test the successful payment case by authenticating the payment on the redirect page. The PaymentIntent will transition from `requires_action` to `succeeded`. To test the case where the user fails to authenticate, use your test API keys and view the redirect page. On the redirect page, click **Fail test payment**. The PaymentIntent will transition from `requires_action` to `requires_payment_method`. ## Optional: Set the billing period When you create a subscription, it automatically sets the billing cycle by default. For example, if a customer subscribes to a monthly plan on September 7, they’re billed on the 7th of every month after that. Some businesses prefer to set the billing cycle manually so that they can charge their customers at the same time each cycle. The [billing cycle anchor](https://docs.stripe.com/api/subscriptions/create.md#create_subscription-billing_cycle_anchor) argument allows you to do this. #### Accounts v2 ```curl curl https://api.stripe.com/v1/subscriptions \ -u "<>:" \ -d "customer_account={{CUSTOMERACCOUNT_ID}}" \ -d "items[0][price]={{PRICE_ID}}" \ -d billing_cycle_anchor=1611008505 ``` #### Customers v1 ```curl curl https://api.stripe.com/v1/subscriptions \ -u "<>:" \ -d "customer={{CUSTOMER_ID}}" \ -d "items[0][price]={{PRICE_ID}}" \ -d billing_cycle_anchor=1611008505 ``` Setting the billing cycle manually automatically charges the customer a prorated amount for the time between the subscription being created and the billing cycle anchor. If you don’t want to charge customers for this time, you can set the [proration_behavior](https://docs.stripe.com/api/subscriptions/create.md#create_subscription-proration_behavior) argument to `none`. You can also combine the billing cycle anchor with [trial periods](https://docs.stripe.com/billing/subscriptions/paypal.md#trial-periods) to give users free access to your product and then charge them a prorated amount. ## Optional: Subscription trials Free trials allow customers access to your product for a period of time for free. Using free trials is different from setting [proration_behavior](https://docs.stripe.com/api/subscriptions/create.md#create_subscription-proration_behavior) to `none` because you can customize how long the free period lasts. Pass a timestamp in [trial end](https://docs.stripe.com/api/subscriptions/create.md#create_subscription-trial_end) to set the trial period. #### Accounts v2 ```curl curl https://api.stripe.com/v1/subscriptions \ -u "<>:" \ -d "customer_account={{CUSTOMERACCOUNT_ID}}" \ -d "items[0][price]={{PRICE_ID}}" \ -d trial_end=1610403705 ``` #### Customers v1 ```curl curl https://api.stripe.com/v1/subscriptions \ -u "<>:" \ -d "customer={{CUSTOMER_ID}}" \ -d "items[0][price]={{PRICE_ID}}" \ -d trial_end=1610403705 ``` You can also combine a [billing cycle anchor](https://docs.stripe.com/billing/subscriptions/paypal.md#billing-cycle) with a free trial. For example, say it’s September 15 and you want to give your customer a free trial for seven days and then start the normal billing cycle on October 1. You can set the free trial to end on September 22 and the billing cycle anchor to October 1. This gives the customer a free trial for seven days and then charges a prorated amount for the time between the trial ending and October 1. On October 1, you charge them the normal subscription amount for their first full billing cycle. ## Optional: Remove a saved PayPal account You can use the [detach](https://docs.stripe.com/api/payment_methods/detach.md) API to remove a customer’s saved PayPal account as a payment method. When you detach a PayPal payment method, it revokes the [mandate](https://docs.stripe.com/api/mandates.md) and also calls the PayPal API to cancel the associated PayPal billing agreement. ```curl curl -X POST https://api.stripe.com/v1/payment_methods/{{PAYMENT_METHOD_ID}}/detach \ -u "<>:" ```