Terminal
Refunds

Refunds

Cancel or refund Stripe Terminal payments.

Stripe Terminal uses a two-step process to prevent unintended and duplicate payments on card_present payments.

When the SDK returns a confirmed PaymentIntent to your app, the payment is authorized, but not captured; you can cancel these payments on your server. We recommend reconciling payments on your backend after a day’s activity to prevent unintended authorizations and uncollected funds.

If the PaymentIntent has already been captured, you must refund the underlying charge created by the PaymentIntent, using the refunds API, Dashboard, or Terminal SDK methods.

Canceling payments Client-sideServer-side

You can cancel a card_present PaymentIntent at any time before it has been captured. Canceling a PaymentIntent releases all uncaptured funds, and a canceled PaymentIntent can no longer be used to perform charges.

This can be useful if, for example, your customer decides to use a different payment method or pay with cash after the payment has been processed. In your application’s UI, consider allowing the user to cancel after processing the payment, before finalizing the payment and notifying your backend to capture.

With the JavaScript SDK, you must cancel uncaptured payments on the server. The iOS and Android SDKs let you cancel from the client side.

// Action for a "Cancel" button func cancelAction() { if let paymentIntent = self.paymentIntent { Terminal.shared.cancelPaymentIntent(paymentIntent) { cancelResult, cancelError in if let error = cancelError { print("cancelPaymentIntent failed: \(error)") } else { print("cancelPaymentIntent succeeded") } } } }
// Action for a "Cancel" button - (void)cancelAction { if (self.paymentIntent) { [[SCPTerminal shared] cancelPaymentIntent:self.paymentIntent completion:^(SCPPaymentIntent *cancelResult, NSError *cancelError) { if (cancelError) { NSLog(@"cancelPaymentIntent failed: %@", cancelError); } else { NSLog(@"cancelPaymentIntent succeeded"); } }]; } }
Terminal.getInstance().cancelPaymentIntent(paymentIntent, object: PaymentIntentCallback { override fun onSuccess(paymentIntent: PaymentIntent) { // Placeholder for handling a successful cancelation } override fun onFailure(exception: TerminalException) { // Placeholder for handling a failed cancelation } })
Terminal.getInstance().cancelPaymentIntent(paymentIntent, new PaymentIntentCallback() { @Override public void onSuccess(PaymentIntent paymentIntent) { // Placeholder for handling a successful cancelation } @Override public void onFailure(TerminalException exception) { // Placeholder for handling a failed cancelation } })

Refunds Client-sideServer-side

When you use a PaymentIntent to collect payment from a customer, Stripe creates a charge behind the scenes. To refund the customer’s payment after the PaymentIntent has succeeded, create a refund by passing in the PaymentIntent id or the charge id. You can also optionally refund part of a payment by specifying an amount.

You can perform refunds with the API or through the Dashboard. For Interac transactions in Canada, the Verifone P400 also supports an client-side refund flow.

Server-side

Server-side refunds do not require a cardholder to present their card again at the point of sale. The following example shows how to create a full refund by passing in the PaymentIntent id.

curl https://api.stripe.com/v1/refunds \ -u sk_test_4eC39HqLyjWDarjtT1zdp7dc: \ -d payment_intent=pi_Aabcxyz01aDfoo
# Set your secret key. Remember to switch to your live secret key in production! # See your keys here: https://dashboard.stripe.com/account/apikeys Stripe.api_key = 'sk_test_4eC39HqLyjWDarjtT1zdp7dc' refund = Stripe::Refund.create({ payment_intent: 'pi_Aabcxyz01aDfoo', })
# Set your secret key. Remember to switch to your live secret key in production! # See your keys here: https://dashboard.stripe.com/account/apikeys stripe.api_key = 'sk_test_4eC39HqLyjWDarjtT1zdp7dc' refund = stripe.Refund.create( payment_intent='pi_Aabcxyz01aDfoo', )
// Set your secret key. Remember to switch to your live secret key in production! // See your keys here: https://dashboard.stripe.com/account/apikeys \Stripe\Stripe::setApiKey('sk_test_4eC39HqLyjWDarjtT1zdp7dc'); $re = \Stripe\Refund::create([ 'payment_intent' => 'pi_Aabcxyz01aDfoo', ]);
// Set your secret key. Remember to switch to your live secret key in production! // See your keys here: https://dashboard.stripe.com/account/apikeys Stripe.apiKey = "sk_test_4eC39HqLyjWDarjtT1zdp7dc"; Refund refund = Refund.create(RefundCreateParams.builder() .setPaymentIntent("pi_Aabcxyz01aDfoo") .build());
// Set your secret key. Remember to switch to your live secret key in production! // See your keys here: https://dashboard.stripe.com/account/apikeys const stripe = require('stripe')('sk_test_4eC39HqLyjWDarjtT1zdp7dc'); const refund = await stripe.refunds.create({ payment_intent: 'pi_Aabcxyz01aDfoo', });
// Set your secret key. Remember to switch to your live secret key in production! // See your keys here: https://dashboard.stripe.com/account/apikeys stripe.Key = "sk_test_4eC39HqLyjWDarjtT1zdp7dc" refundParams := &stripe.RefundParams{ PaymentIntent: "pi_Aabcxyz01aDfoo", } r, err := refund.New(refundParams)
// Set your secret key. Remember to switch to your live secret key in production! // See your keys here: https://dashboard.stripe.com/account/apikeys StripeConfiguration.ApiKey = "sk_test_4eC39HqLyjWDarjtT1zdp7dc"; var refunds = new RefundService(); var refundOptions = new RefundCreateOptions { PaymentIntent = "pi_Aabcxyz01aDfoo" }; var refund = refunds.Create(refundOptions);

To refund part of a PaymentIntent, provide an amount parameter, as an integer in cents (or the charge currency’s smallest currency unit):

curl https://api.stripe.com/v1/refunds \ -u sk_test_4eC39HqLyjWDarjtT1zdp7dc: \ -d payment_intent=pi_Aabcxyz01aDfoo \ -d amount=1000
# Set your secret key. Remember to switch to your live secret key in production! # See your keys here: https://dashboard.stripe.com/account/apikeys Stripe.api_key = 'sk_test_4eC39HqLyjWDarjtT1zdp7dc' refund = Stripe::Refund.create({ amount: 1000, payment_intent: 'pi_Aabcxyz01aDfoo', })
# Set your secret key. Remember to switch to your live secret key in production! # See your keys here: https://dashboard.stripe.com/account/apikeys stripe.api_key = 'sk_test_4eC39HqLyjWDarjtT1zdp7dc' refund = stripe.Refund.create( amount=1000, payment_intent='pi_Aabcxyz01aDfoo', )
// Set your secret key. Remember to switch to your live secret key in production! // See your keys here: https://dashboard.stripe.com/account/apikeys \Stripe\Stripe::setApiKey('sk_test_4eC39HqLyjWDarjtT1zdp7dc'); $re = \Stripe\Refund::create([ 'amount' => 1000, 'payment_intent' => 'pi_Aabcxyz01aDfoo', ]);
// Set your secret key. Remember to switch to your live secret key in production! // See your keys here: https://dashboard.stripe.com/account/apikeys Stripe.apiKey = "sk_test_4eC39HqLyjWDarjtT1zdp7dc"; Refund refund = Refund.create(RefundCreateParams.builder() .setAmount(1000L) .setPaymentIntent("pi_Aabcxyz01aDfoo") .build());
// Set your secret key. Remember to switch to your live secret key in production! // See your keys here: https://dashboard.stripe.com/account/apikeys const stripe = require('stripe')('sk_test_4eC39HqLyjWDarjtT1zdp7dc'); const refund = await stripe.refunds.create({ amount: 1000, payment_intent: 'pi_Aabcxyz01aDfoo', });
// Set your secret key. Remember to switch to your live secret key in production! // See your keys here: https://dashboard.stripe.com/account/apikeys stripe.Key = "sk_test_4eC39HqLyjWDarjtT1zdp7dc" refundParams := &stripe.RefundParams{ Amount: 1000, PaymentIntent: "pi_Aabcxyz01aDfoo", } r, err := refund.New(refundParams)
// Set your secret key. Remember to switch to your live secret key in production! // See your keys here: https://dashboard.stripe.com/account/apikeys StripeConfiguration.ApiKey = "sk_test_4eC39HqLyjWDarjtT1zdp7dc"; var refunds = new RefundService(); var refundOptions = new RefundCreateOptions { PaymentIntent = "pi_Aabcxyz01aDfoo", Amount = 1000 }; var refund = refunds.Create(refundOptions);

Client-side Verifone P400 only CA Interac only

In-person refunds are mandatory for Interac transactions in Canada. You will not be able to create refunds in the API or in the Dashboard for these payments; you will have to use the client-side methods. In this flow, the reader will prompt the cardholder to present the card used in the original charge. Once the card details have been read successfully, your application will be able to process the refund. Similar to server-side refunds, you can perform partial refunds by passing in an amount less than the transaction value.

The currency and the card used for refund processing must be the same used for the original charge, otherwise the request will fail with an error.

The below example attempts a server-side refund first, and if the server-side refund is unsuccessful, proceeds with a client-side refund.

this.client.refundCharge({chargeId: "ch_xxxxxxxxxx"}).then( function(refundChargeResult) { return refundChargeResult; }, function(refundChargeError) { if (refundChargeError === "invalid_request_error") { return this.terminal.collectRefundPaymentMethod( "ch_xxxxxxxxxx", 2000, "cad" ).then( function(collectRefundPaymentMethodResult) { if (collectRefundPaymentMethodResult.error) { // Placeholder for handling collectRefundPaymentMethodResult.error } else { return this.terminal.processRefund(); } } ).then( function(processRefundResult) { if (processRefundResult.error) { // Placeholder for handling processRefundResult.error } else { console.log("Charge fully refunded!"); return processRefundResult; } } ) } else { // Placeholder for handling other refundChargeError messages } } );
try { let refund = await this.client.refundCharge({chargeId: "ch_xxxxxxxxxx"}); } catch (error) { if (error.message === "invalid_request_error") { const result = await this.terminal.collectRefundPaymentMethod( "ch_xxxxxxxxxx", 2000, "cad" ); if (result.error) { // Placeholder for handling result.error } else { const refund = await this.terminal.processRefund(); if (refund.error) { // Placeholder for handling refund.error } else { console.log("Charge fully refunded!"); return refund; } } }

Next steps

Congratulations! Next, you might want to configure the customer-facing checkout experience in your app.

Was this page helpful?
Questions? Contact us.
Developer tutorials on YouTube.
You can unsubscribe at any time. Read our privacy policy.