# Set up future PayPal payments Learn how to save PayPal details and charge your customers later. Set up future PayPal payments to save customer payment details for subscriptions, delayed charges, and streamlined future purchases. Learn how to enable and use recurring payments with PayPal through Stripe. ## Enable recurring payments Stripe automatically enables recurring payments for most users when they [activate PayPal payments](https://docs.stripe.com/payments/paypal/activate.md) in the Stripe Dashboard. However, due to PayPal’s policies and regional restrictions, some users might need to enable recurring payments manually. This includes users that set up their accounts before we introduced automatic enablement. To manually enable recurring payments: 1. Go to your [Payment methods settings](https://dashboard.stripe.com/settings/payment_methods). 1. Click **PayPal** > **Enable** in the **Recurring payments** section. After you enable recurring payments, it appears as **pending** in the Dashboard. It usually takes up to five business days to get access. When you’re granted access, recurring payments are available in your [PayPal settings](https://dashboard.stripe.com/settings/payment_methods). In testing environments, recurring payments are enabled by default. Use [Stripe Checkout](https://docs.stripe.com/payments/checkout.md) to collect PayPal payment details in advance, and determine the final amount or payment date later. Use it to: - Save payment methods to a wallet to streamline future purchases - Collect surcharges after fulfilling a service - [Start a free trial for a subscription](https://docs.stripe.com/billing/subscriptions/trials.md) ## Set up Stripe [Server-side] First, you need a Stripe account. [Register now](https://dashboard.stripe.com/register). Use our official libraries for access to the Stripe API from your application: #### Ruby ```bash # Available as a gem sudo gem install stripe ``` ```ruby # If you use bundler, you can add this line to your Gemfile gem 'stripe' ``` ## 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 Checkout Session [Client-side] [Server-side] Before you can accept PayPal payments, your customer must authorize you to use their PayPal account for future payments through Stripe Checkout. Add a checkout button to your website that calls a server-side endpoint to create a [Checkout Session](https://docs.stripe.com/api/checkout/sessions.md). ```html Checkout
``` Create a Checkout Session in `setup` mode to collect the required information. After creating the Checkout Session, redirect your customer to the [URL](https://docs.stripe.com/api/checkout/sessions/object.md#checkout_session_object-url) returned in the response. #### Accounts v2 ```curl curl https://api.stripe.com/v1/checkout/sessions \ -u "<>:" \ -d "customer_account={{CUSTOMERACCOUNT_ID}}" \ -d "payment_method_types[]=paypal" \ -d mode=setup \ --data-urlencode "success_url=https://example.com/success?session_id={CHECKOUT_SESSION_ID}" ``` #### Customers v1 ```curl curl https://api.stripe.com/v1/checkout/sessions \ -u "<>:" \ -d "customer={{CUSTOMER_ID}}" \ -d "payment_method_types[]=paypal" \ -d mode=setup \ --data-urlencode "success_url=https://example.com/success?session_id={CHECKOUT_SESSION_ID}" ``` When your customer provides their payment method details, they’re redirected to the `success_url`, a page on your website that informs them that their payment method was saved successfully. Make the Session ID available on your success page by including the `{CHECKOUT_SESSION_ID}` template variable in the `success_url` as in the above example. > Don’t rely on the redirect to the `success_url` alone for detecting payment initiation, because: > > - Malicious users could directly access the `success_url` without paying and gain access to your goods or services. - After a successful payment, customers might close their browser tab before they’re redirected to the `success_url`. ## Retrieve the payment method [Server-side] After a customer submits their payment details, retrieve the [PaymentMethod](https://docs.stripe.com/payments/payment-methods.md) object. A *PaymentMethod* (PaymentMethods represent your customer's payment instruments, used with the Payment Intents or Setup Intents APIs) stores the customer’s PayPal account information for future payments. You can retrieve the PaymentMethod synchronously using the `success_url` or asynchronously using *webhooks* (A webhook is a real-time push notification sent to your application as a JSON payload through HTTPS requests). The decision to retrieve the PaymentMethod synchronously or asynchronously depends on your tolerance for dropoff, as customers might not always reach the `success_url` after a successful payment (for example, it’s possible for them to close their browser tab before the redirect occurs). Using webhooks prevents your integration from experiencing this form of dropoff. #### Webhooks Handle `checkout.session.completed` webhooks, which contain a Session object. To learn more, see [setting up webhooks](https://docs.stripe.com/webhooks.md). The following example is a `checkout.session.completed` response. ```json { "id": "evt_1Ep24XHssDVaQm2PpwS19Yt0", "object": "event", "api_version": "2019-03-14", "created": 1561420781, "data": { "object": { "id": "cs_test_MlZAaTXUMHjWZ7DcXjusJnDU4MxPalbtL5eYrmS2GKxqscDtpJq8QM0k", "object": "checkout.session", "billing_address_collection": null, "client_reference_id": null, "customer": null, "customer_email": null, "display_items": [], "mode": "setup","setup_intent": "seti_1EzVO3HssDVaQm2PJjXHmLlM", "submit_type": null, "subscription": null, "success_url": "https://example.com/success" } }, "livemode": false, "pending_webhooks": 1, "request": { "id": null, "idempotency_key": null }, "type": "checkout.session.completed" } ``` Note the value of the `setup_intent` key, which is the ID for the SetupIntent created with the Checkout Session. A [SetupIntent](https://docs.stripe.com/payments/setup-intents.md) is an object used to set up the customer PayPal account information for future payments. [Retrieve](https://docs.stripe.com/api/setup_intents/retrieve.md) the SetupIntent object with the ID. The returned object contains the `payment_method` ID. ```curl curl https://api.stripe.com/v1/setup_intents/seti_1EzVO3HssDVaQm2PJjXHmLlM \ -u "<>:" ``` #### Success URL Obtain the `session_id` from the URL when a user redirects back to your site and [retrieve](https://docs.stripe.com/api/checkout/sessions/retrieve.md) the Session object. ```curl curl -G https://api.stripe.com/v1/checkout/sessions/{{SESSION_ID}} \ -u "<>:" \ -d "expand[]=setup_intent" ``` > To ensure the `session_id` is available from the URL, include the `session_id={CHECKOUT_SESSION_ID}` template variable in the `success_url` when creating the Checkout Session. Note the SetupIntent created during the Checkout Session. A [SetupIntent](https://docs.stripe.com/payments/setup-intents.md) is an object used to set up the customer PayPal account information for future payments. The returned object contains the `payment_method` ID. ## Handle post-setup events [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. ## 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`. ## Use the payment method for future payments [Server-side] When you’re ready to charge your customer off-session, use the customer and *PaymentMethod* (PaymentMethods represent your customer's payment instruments, used with the Payment Intents or Setup Intents APIs) IDs to create a [PaymentIntent](https://docs.stripe.com/api/payment_intents.md). To find a `paypal` instrument to charge, [list](https://docs.stripe.com/api/payment_methods/list.md) the PaymentMethods associated with your customer. #### Accounts v2 ```curl curl -G https://api.stripe.com/v1/payment_methods \ -u "<>:" \ -d "customer_account={{CUSTOMERACCOUNT_ID}}" \ -d type=paypal ``` #### Customers v1 ```curl curl -G https://api.stripe.com/v1/payment_methods \ -u "<>:" \ -d "customer={{CUSTOMER_ID}}" \ -d type=paypal ``` When you have the customer and `PaymentMethod` IDs, create a `PaymentIntent` with the amount and currency of the payment. Set the following parameters to make the *off-session payment* (A payment is described as off-session if it occurs without the direct involvement of the customer, using previously-collected payment information): - Set [off_session](https://docs.stripe.com/api/payment_intents/confirm.md#confirm_payment_intent-off_session) to `true` to indicate that the customer isn’t in your checkout flow during this payment attempt. This causes the PaymentIntent to throw an error if authentication is required. - Set the value of the PaymentIntent’s [confirm](https://docs.stripe.com/api/payment_intents/create.md#create_payment_intent-confirm) property to `true`, which causes confirmation to occur immediately when the PaymentIntent is created. - Set [payment_method](https://docs.stripe.com/api.md#create_payment_intent-payment_method) to the ID of the `PaymentMethod`. - Set [customer](https://docs.stripe.com/api.md#create_payment_intent-customer) or [customer_account](https://docs.stripe.com/api.md#create_payment_intent-customer_account) to the customer ID. #### Accounts v2 ```curl curl https://api.stripe.com/v1/payment_intents \ -u "<>:" \ -d amount=1099 \ -d currency=eur \ -d "customer_account={{CUSTOMERACCOUNT_ID}}" \ -d "payment_method_types[]=paypal" \ -d "payment_method={{PAYMENTMETHOD_ID}}" \ -d off_session=true \ -d confirm=true ``` #### Customers v1 ```curl curl https://api.stripe.com/v1/payment_intents \ -u "<>:" \ -d amount=1099 \ -d currency=eur \ -d "customer={{CUSTOMER_ID}}" \ -d "payment_method_types[]=paypal" \ -d payment_method={{PAYMENT_METHOD_ID}} \ -d off_session=true \ -d confirm=true ``` ## User-initiated payment method cancellation [Server-side] A customer can cancel the subscription (Billing Agreement) through their PayPal account. When they do so, Stripe emits a [mandate.updated](https://docs.stripe.com/api/events/types.md#event_types-mandate.updated) webhook. All subsequent PaymentIntents using the saved Payment Method will fail until you change to a Payment Method with active mandates. When payments fail for Subscriptions, the status changes to the Subscription status configured in your [automatic collection settings](https://docs.stripe.com/invoicing/automatic-collection.md). Notify the customer of failure and [charge them with a different payment method](https://docs.stripe.com/billing/subscriptions/overview.md#requires-payment-method). ## Optional: Remove a saved PayPal account [Server-side] 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 "<>:" ```