Create account
Sign in
Home
Payments
Business operations
Financial services
Developer tools
Security
All products
Home
Payments
Business operations
Home
Payments
Business operations
Financial services
Developer tools
Support
Overview
Overview
Sample integration
Example applications
Designing an integration
Integrate your application and readers
Getting started
JavaScript
iOS
Android
Readers
Reader setup
Connecting to a reader
Fleet management
Placing orders
Transactions
Collecting payments
Connect platforms
Saving cards
Refunds
Checkout experience
Cart display
Receipts
Beta
Beta migration guide
Testing
Checklist
Testing
terminal
·
HomePaymentsIn-person payments

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.

Interac is a single-message network; interac_present PaymentIntents are automatically captured. In lieu of canceling PaymentIntents, your application should allow initiating an in-person client-side refund at the end of the checkout flow.

Canceling payments Client-sideServer-side

SDK Reference

  • cancelPaymentIntent (iOS)
  • cancelPaymentIntent (Android)

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.

JavaScript iOS Android

Cancel the PaymentIntent on the server.

Swift Objective-C
PaymentViewControlller.swift
// 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 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") } } } }
APPPaymentViewControlller.m
// 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"); } }]; } }
// 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"); } }]; } }
Kotlin Java
PaymentActivity.kt
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, object: PaymentIntentCallback { override fun onSuccess(paymentIntent: PaymentIntent) { // Placeholder for handling a successful cancelation } override fun onFailure(exception: TerminalException) { // Placeholder for handling a failed cancelation } })
PaymentActivity.java
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 } })
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 Ruby Python PHP Java Node Go .NET
curl https://api.stripe.com/v1/refunds \ -u sk_test_4eC39HqLyjWDarjtT1zdp7dc: \ -d payment_intent=pi_Aabcxyz01aDfoo
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.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\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 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'); const stripe = 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 const Stripe = require('stripe'); const stripe = 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 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);
// 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 Ruby Python PHP Java Node Go .NET
curl https://api.stripe.com/v1/refunds \ -u sk_test_4eC39HqLyjWDarjtT1zdp7dc: \ -d payment_intent=pi_Aabcxyz01aDfoo \ -d amount=1000
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.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\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 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'); const stripe = 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 const Stripe = require('stripe'); const stripe = 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 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);
// 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.

JavaScript iOS Android
JavaScript JavaScript (ESNext)
JavaScript
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 } } );
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; } } }
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; } } }
Swift Objective-C
PaymentViewController.swift
let refundParams = RefundParameters( chargeId: "ch_1FLyVV2eZvKYlo2C9Z8rmX02", amount: 1000, currency: "cad" ) self.refundCancelable = Terminal.shared.collectRefundPaymentMethod(refundParams) { collectError in if let error = collectError { // Handle collectRefundPaymentMethod error print("collectRefundPaymentMethod failed. \(error)") } else { // Process refund Terminal.shared.processRefund { processedRefund, processError in if let error = processError { print("Process refund failed. \(error)") // Handle process error } else if let refund = processedRefund, refund.status == .succeeded { print("Process refund successful! \(refund)") } else { print("Refund pending or unsuccessful.") } } } }
let refundParams = RefundParameters( chargeId: "ch_1FLyVV2eZvKYlo2C9Z8rmX02", amount: 1000, currency: "cad" ) self.refundCancelable = Terminal.shared.collectRefundPaymentMethod(refundParams) { collectError in if let error = collectError { // Handle collectRefundPaymentMethod error print("collectRefundPaymentMethod failed. \(error)") } else { // Process refund Terminal.shared.processRefund { processedRefund, processError in if let error = processError { print("Process refund failed. \(error)") // Handle process error } else if let refund = processedRefund, refund.status == .succeeded { print("Process refund successful! \(refund)") } else { print("Refund pending or unsuccessful.") } } } }
APPPaymentViewController.m
- (void)startRefund { SCPRefundParameters refundParams = [SCPRefundParameters initWithChargeId:@"ch_1FLyVV2eZvKYlo2C9Z8rmX02" amount:@1000 currency:@"cad"]; self.refundCancelable = [[SCPTerminal shared] collectRefundPaymentMethod:refundParams completion:^(SCPError *collectError) { if (collectError) { // Handle collect error NSLog(@"collectRefundPaymentMethod failed: %@", collectError); } else { // Process refund [[SCPTerminal shared] processRefund:^(SCPRefund *processResult, SCPProcessRefundError *processError) { if (processError) { // Handle process error NSLog(@"processRefund failed: %@", processError); } else if (processResult) { if (processResult.status == SCPRefundStatusSucceeded) { NSLog(@"Process refund successful! %@", processResult) } else { NSLog(@"Refund pending or unsuccessful.") } } }]; } }]; }
- (void)startRefund { SCPRefundParameters refundParams = [SCPRefundParameters initWithChargeId:@"ch_1FLyVV2eZvKYlo2C9Z8rmX02" amount:@1000 currency:@"cad"]; self.refundCancelable = [[SCPTerminal shared] collectRefundPaymentMethod:refundParams completion:^(SCPError *collectError) { if (collectError) { // Handle collect error NSLog(@"collectRefundPaymentMethod failed: %@", collectError); } else { // Process refund [[SCPTerminal shared] processRefund:^(SCPRefund *processResult, SCPProcessRefundError *processError) { if (processError) { // Handle process error NSLog(@"processRefund failed: %@", processError); } else if (processResult) { if (processResult.status == SCPRefundStatusSucceeded) { NSLog(@"Process refund successful! %@", processResult) } else { NSLog(@"Refund pending or unsuccessful.") } } }]; } }]; }

Client-side refunds not currently supported on Android.

Next steps

  • Cart Display
  • Receipts
Was this page helpful?
Questions? Contact us.
Developer tutorials on YouTube.
You can unsubscribe at any time. Read our privacy policy.
On this page
Canceling payments
Refunds
Next steps