Refunds
Cancel or refund Stripe Terminal payments.
Stripe Terminal uses a two-step process to prevent unintended and duplicate payments. When the SDK returns a confirmed PaymentIntent to your app, the payment is authorized, but not captured.
If the PaymentIntent has already been captured, you must refund the underlying charge created by the PaymentIntent, using the refunds API on your server.
Canceling payments Client-sideServer-side
You can cancel a 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 or Android SDK 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 Server-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 on the charge object. You can also optionally refund part of their payment by specifying an amount. You can perform refunds with the API or through the Dashboard.
Note that this does not apply if you are using separate authorization and capture, and you wish to refund a PaymentIntent that has a status of requires_capture
. In this case, the charge attached to the PaymentIntent is still uncaptured and cannot be refunded directly. You should cancel the PaymentIntent instead.
To find the charge object, you can inspect the PaymentIntent’s charges property, which has an array of all the charges created by the PaymentIntent. The first charge in the array is the one that completed successfully.
The following example shows how to refund a PaymentIntent’s charge:
curl https://api.stripe.com/v1/payment_intents/pi_Aabcxyz01aDfoo \
-u sk_test_4eC39HqLyjWDarjtT1zdp7dc:
# Retrieve the ID of the first charge in PaymentIntent
# charges array returned from the previous request and use
# it to create a refund.
curl https://api.stripe.com/v1/refunds \
-u sk_test_4eC39HqLyjWDarjtT1zdp7dc: \
-d charge="{CHARGE_ID}"
# Set your secret key: remember to change this to your live secret key in production
# See your keys here: https://dashboard.stripe.com/account/apikeys
Stripe.api_key = 'sk_test_4eC39HqLyjWDarjtT1zdp7dc'
intent = Stripe::PaymentIntent.retrieve('pi_Aabcxyz01aDfoo')
intent['charges']['data'].first.refund
# Set your secret key: remember to change this to your live secret key in production
# See your keys here: https://dashboard.stripe.com/account/apikeys
stripe.api_key = 'sk_test_4eC39HqLyjWDarjtT1zdp7dc'
intent = stripe.PaymentIntent.retrieve("pi_Aabcxyz01aDfoo")
intent['charges']['data'][0].refund()
// Set your secret key: remember to change this to your live secret key in production
// See your keys here: https://dashboard.stripe.com/account/apikeys
\Stripe\Stripe::setApiKey('sk_test_4eC39HqLyjWDarjtT1zdp7dc');
$intent = \Stripe\PaymentIntent::retrieve("pi_Aabcxyz01aDfoo");
$intent->charges->data[0]->refund();
// Set your secret key: remember to change this to your live secret key in production
// See your keys here: https://dashboard.stripe.com/account/apikeys
Stripe.apiKey = "sk_test_4eC39HqLyjWDarjtT1zdp7dc";
PaymentIntent intent = PaymentIntent.retrieve("pi_Aabcxyz01aDfoo");
String chargeId = intent.getCharges().getData().get(0).getId();
Refund refund = Refund.create(RefundCreateParams.builder()
.setCharge(chargeId)
.build());
// Set your secret key: remember to change this to your live secret key in production
// See your keys here: https://dashboard.stripe.com/account/apikeys
const stripe = require('stripe')('sk_test_4eC39HqLyjWDarjtT1zdp7dc');
(async () => {
const intent = await stripe.paymentIntents.retrieve("pi_Aabcxyz01aDfoo");
const refund = await stripe.refunds.create({
charge: intent.charges.data[0].id
});
})();
// Set your secret key: remember to change this to your live secret key in production
// See your keys here: https://dashboard.stripe.com/account/apikeys
stripe.Key = "sk_test_4eC39HqLyjWDarjtT1zdp7dc"
intent, err := paymentintent.Get("pi_Aabcxyz01aDfoo", nil)
refundParams := &stripe.RefundParams{
Charge: stripe.String(intent.Charges.Data[0].ID),
}
r, err := refund.New(refundParams)
// Set your secret key: remember to change this to your live secret key in production
// See your keys here: https://dashboard.stripe.com/account/apikeys
StripeConfiguration.SetApiKey("sk_test_4eC39HqLyjWDarjtT1zdp7dc");
var paymentIntents = new PaymentIntentService();
var refunds = new RefundService();
var intent = paymentIntents.Get("pi_Aabcxyz01aDfoo");
var refundOptions = new RefundCreateOptions {
ChargeId = intent.Charges.Data[0].Id
};
var refund = refunds.Create(refundOptions);
We recommend reconciling payments on your backend after a day’s activity to prevent unintended authorizations and uncollected funds.
Next steps
Congratulations! Next, you might want to configure the customer-facing checkout experience in your app.