Payments
Bank debits and transfers
BECS Direct Debit in Australia
Accept a payment

Accept a BECS Direct Debit payment

Use the Payment Intents and Payment Methods APIs to accept BECS Direct Debit payments in Australia.

Use Stripe Elements, our prebuilt UI components, to create a payment form that lets you securely collect a customer’s bank details without handling the sensitive data. Accepting BECS Direct Debit payments on your website consists of creating an object to track a payment, collecting payment method information and mandate acknowledgement, and submitting the payment to Stripe for processing. Stripe uses this payment object, the PaymentIntent, to track and handle all the states of the payment until the payment completes.

Stripe users in Australia can use the Australia Bank Account Element and a PaymentIntent to accept BECS Direct Debit payments from customers with an Australian bank account.

1 Set up Stripe Server-side

First, you need a Stripe account. Register now.

Use our official libraries for access to the Stripe API from your application:

# Available as a gem gem install stripe
# If you use bundler, you can add this line to your Gemfile gem 'stripe'
# Install through pip pip install --upgrade stripe
# Or find the Stripe package on http://pypi.python.org/pypi/stripe/
# Find the version you want to pin: # https://github.com/stripe/stripe-python/blob/master/CHANGELOG.md # Specify that version in your requirements.txt file stripe>=2.48.0,<3.0
# Install the PHP library via Composer composer require stripe/stripe-php
# Or download the source directly: https://github.com/stripe/stripe-php/releases
/* For Gradle, add the following dependency to your build.gradle and replace {VERSION} with the version number you want to use from - https://mvnrepository.com/artifact/com.stripe/stripe-java or - https://github.com/stripe/stripe-java/releases/latest */ implementation "com.stripe:stripe-java:{VERSION}"
<!-- For Maven, add the following dependency to your POM and replace {VERSION} with the version number you want to use from - https://mvnrepository.com/artifact/com.stripe/stripe-java or - https://github.com/stripe/stripe-java/releases/latest --> <dependency> <groupId>com.stripe</groupId> <artifactId>stripe-java</artifactId> <version>{VERSION}</version> </dependency>
# For other environments, manually install the following JARs: # - The Stripe JAR from https://github.com/stripe/stripe-java/releases/latest # - Google Gson from https://github.com/google/gson
# Install via npm npm install --save stripe
# Make sure your project is using Go Modules god mod init # Install stripe-go go get -u github.com/stripe/stripe-go/v71
// Then import the package import ( "github.com/stripe/stripe-go/v71" )
# Install via dotnet dotnet add package Stripe.net dotnet restore
# Or install via NuGet PM> Install-Package Stripe.net

2 Create or retrieve a Customer Server-side

To reuse a BECS Direct Debit account for future payments, it must be attached to a Customer.

You should create a Customer object when your customer creates an account with your business. Associating the ID of the Customer object with your own internal representation of a customer will enable you to retrieve and use the stored payment method details later. If your customer is making a payment as a guest, you can still create a Customer object before payment and associate it with your internal representation of the customer’s account later.

Create a new Customer or retrieve an existing Customer to associate with this payment. Include the following code on your server to create a new Customer.

curl https://api.stripe.com/v1/customers \ -u sk_test_4eC39HqLyjWDarjtT1zdp7dc: \ -X POST
# 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' customer = Stripe::Customer.create
# 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' customer = stripe.Customer.create()
// 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'); $customer = \Stripe\Customer::create();
// 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"; CustomerCreateParams params = CustomerCreateParams.builder() .build(); Customer customer = Customer.create(params);
// 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 customer = await stripe.customers.create();
// 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" params := &stripe.CustomerParams{} c, _ := customer.New(params)
// 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 options = new CustomerCreateOptions{}; var service = new CustomerService(); var customer = service.Create(options);

3 Create a PaymentIntent Server-side

A PaymentIntent is an object that represents your intent to collect a payment from a customer. The PaymentIntent tracks the lifecycle of the payment process through each stage. First, create a PaymentIntent on your server and specify the amount to collect and the aud currency (BECS Direct Debit does not support other currencies). If you already have an integration using the Payment Intents API, add au_becs_debit to the list of payment method types for your PaymentIntent.

To save the BECS Direct Debit account for reuse, set the setup_future_usage parameter to off_session. BECS Direct Debit only accepts an off_session value for this parameter.

curl https://api.stripe.com/v1/payment_intents \ -u sk_test_4eC39HqLyjWDarjtT1zdp7dc: \ -d amount=1099 \ -d setup_future_usage=off_session \ -d currency=aud \ -d customer="{{CUSTOMER_ID}}" \ -d "payment_method_types[]"=au_becs_debit
require 'sinatra' require 'json' require 'stripe' # 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' get '/pay' do payment_intent = Stripe::PaymentIntent.create({ amount: 1099, currency: 'aud', setup_future_usage: 'off_session', customer: customer['id'], payment_method_types: ['au_becs_debit'], }) client_secret = payment_intent['client_secret'] # Pass the client secret to the client end
import stripe import json from flask import Flask, request, render_template app = Flask(__name__, static_folder=".", static_url_path="", template_folder=".") # 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' @app.route('/pay') def pay(): payment_intent = stripe.PaymentIntent.create( amount=1099, currency='aud', setup_future_usage='off_session', customer=customer['id'], payment_method_types=['au_becs_debit'] ) client_secret = payment_intent.client_secret # Pass the client secret to the client if __name__ == '__main__': app.run()
// 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'); # vendor using composer require_once('vendor/autoload.php'); $payment_intent = \Stripe\PaymentIntent::create([ 'amount' => 1099, 'currency' => 'aud', 'setup_future_usage' => 'off_session', 'customer' => $customer->id, 'payment_method_types' => ['au_becs_debit'], ]); $client_secret = $payment_intent->client_secret // Pass the client secret to the client
import java.util.HashMap; import java.util.Map; import com.stripe.Stripe; import com.stripe.model.PaymentIntent; public class StripeJavaQuickStart { public static void main(String[] args) { // 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"; Map<String, Object> paymentIntentParams = new HashMap<String, Object>(); paymentIntentParams.put("amount", 1099); paymentIntentParams.put("currency", "aud"); paymentIntentParams.put("setup_future_usage", "off_session"); params.put("customer", customer.getId()); paymentIntentParams.put("payment_method_types", Arrays.asList("au_becs_debit")); PaymentIntent paymentIntent = PaymentIntent.create(paymentIntentParams); String clientSecret = paymentIntent.getClientSecret(); // Pass the client secret to the client } }
// Using Express const express = require('express'); const app = express(); app.use(express.json()); const { resolve } = require("path"); // 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 paymentIntent = await stripe.paymentIntents.create({ amount: 1099, currency: 'aud', setup_future_usage: 'off_session', customer: customer.id, payment_method_types: ['au_becs_debit'], }); const clientSecret = paymentIntent.client_secret; // Pass the client secret to the client
package main import ( "encoding/json" "log" "net/http" "github.com/stripe/stripe-go/v71" "github.com/stripe/stripe-go/v71/paymentintent" ) func main() { // 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" paymentIntentParams := &stripe.PaymentIntentParams{ Amount: stripe.Int64(1099), Currency: stripe.String(string(Stripe.CurrencyAUD)), SetupFutureUsage: stripe.String("off_session"), Customer: stripe.String(customer.ID), PaymentMethodTypes: stripe.StringSlice([]string{ "au_becs_debit", }), } paymentIntent, err := paymentintent.New(paymentIntentParams) clientSecret := paymentIntent.ClientSecret // Pass the client secret to the client }
// 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"; using System; using System.Collections.Generic; using Microsoft.AspNetCore.Mvc; using Stripe; namespace StripeExampleApi.Controllers { [Route("/pay")] public class PayController : Controller { public IActionResult Index() { var service = new PaymentIntentService(); var options = new PaymentIntentCreateOptions { Amount = 1099, Currency = "aud", SetupFutureUsage = "off_session", Customer = "{{CUSTOMER_ID}}", PaymentMethodTypes = new List<string> { "au_becs_debit", }, }; var paymentIntent = service.Create(options); var clientSecret = paymentIntent.ClientSecret; // Pass the client secret to the client } } }

After creating a PaymentIntent, Stripe returns a PaymentIntent object containing a client_secret property. Pass the client secret to the client side.

4 Collect payment method details and mandate acknowledgment Client-side

You’re ready to collect payment information on the client with Stripe Elements. Elements is a set of prebuilt UI components for collecting payment details.

A Stripe Element contains an iframe that securely sends the payment information to Stripe over a HTTPS connection. The checkout page address must also start with https:// rather than http:// for your integration to work.

You can test your integration without using HTTPS. Enable it when you’re ready to accept live payments.

Set up Stripe Elements

Stripe Elements is automatically available as a feature of Stripe.js. Include the Stripe.js script on your payment page by adding it to the head of your HTML file. Always load Stripe.js directly from js.stripe.com to remain PCI compliant. Do not include the script in a bundle or host a copy of it yourself.

<head> <title>Submit Payment</title> <script src="https://js.stripe.com/v3/"></script> </head>

Create an instance of Elements with the following JavaScript on your payment page:

var stripe = Stripe('pk_test_TYooMQauvdEDq54NiTphI7jx'); var elements = stripe.elements();
const stripe = Stripe('pk_test_TYooMQauvdEDq54NiTphI7jx'); const elements = stripe.elements();

Direct Debit Requests

Before you can create a BECS Direct Debit payment, your customer must agree with the Direct Debit Request Service Agreement. They do so by submitting a completed Direct Debit Request (DDR). The approval gives you a mandate to debit their account. The Mandate is a record of the permission a customer has given you to debit their payment method corresponding to this Direct Debit Request.

For online mandate acceptance, you can create a form to collect the necessary information. The form should be served over HTTPS and capture the following information:

Information Description
Account name The full name of the account holder
BSB number The Bank-State-Branch number of the bank account (eg. 123-456)
Account number The bank account number (eg. 87654321)

When collecting a Direct Debit Request, you should follow our BECS Direct Debit Terms and as part of your checkout form:

  • Display the exact terms of Stripe’s DDR service agreement either inline on the form, or on a page linked from the form, and identifying it as the “DDR service agreement”
  • Display the following standard authorization text for your customer to accept the BECS DDR, where you replace Rocketship Inc with your company name. Their acceptance authorizes you to initiate BECS Direct Debit payments from their bank account.

The details of the accepted mandate are generated when setting up a PaymentMethod or confirming a PaymentIntent. At all times, you should be able to share this mandate—the accepted DDR and its accompanying DDR service agreement—with your customer, either in print or as a non-changeable electronic copy (such as via email). Stripe hosts this for you under the url property of the Mandate object linked to the PaymentMethod. You can review an example of how this looks.

Add and configure an Australia Bank Account Element

The Australia Bank Account Element will help you collect and validate both the BSB number and the account number. It needs a place to live in your payment form. Create empty DOM nodes (container) with unique IDs in your payment form. Additionally, your customer must read and accept the Direct Debit Request service agreement.

<form action="/charge" method="post" id="payment-form"> <div class="form-row inline"> <div class="col"> <label for="accountholder-name"> Name </label> <input id="accountholder-name" name="accountholder-name" placeholder="John Smith" required /> </div> <div class="col"> <label for="email"> Email Address </label> <input id="email" name="email" type="email" placeholder="john.smith@example.com" required /> </div> </div> <div class="form-row"> <!-- Using a label with a for attribute that matches the ID of the Element container enables the Element to automatically gain focus when the customer clicks on the label. --> <label for="au-bank-account-element"> Bank Account </label> <div id="au-bank-account-element"> <!-- A Stripe Element will be inserted here. --> </div> </div> <!-- Used to display bank (branch) name associated with the entered BSB --> <div id="bank-name"></div> <!-- Used to display form errors. --> <div id="error-message" role="alert"></div> <!-- Display mandate acceptance text. --> <div class="col" id="mandate-acceptance"> By providing your bank account details and confirming this payment, you agree to this Direct Debit Request and the <a href="https://stripe.com/au-becs-dd-service-agreement/legal">Direct Debit Request service agreement</a>, and authorise Stripe Payments Australia Pty Ltd ACN 160 180 343 Direct Debit User ID number 507156 (“Stripe”) to debit your account through the Bulk Electronic Clearing System (BECS) on behalf of Rocketship Inc. (the "Merchant") for any amounts separately communicated to you by the Merchant. You certify that you are either an account holder or an authorised signatory on the account listed above. </div> <!-- Add the client_secret from the PaymentIntent as a data attribute --> <button id="submit-button" data-secret="{{CLIENT_SECRET}}">Confirm Payment</button> </form>
/** * Shows how you can use CSS to style your Element's container. * These classes are added to your Stripe Element by default. * You can override these classNames by using the options passed * to the `iban` element. * https://stripe.com/docs/js/elements_object/create_element?type=iban#elements_create-options-classes */ input, .StripeElement { height: 40px; padding: 10px 12px; color: #32325d; background-color: white; border: 1px solid transparent; border-radius: 4px; box-shadow: 0 1px 3px 0 #e6ebf1; -webkit-transition: box-shadow 150ms ease; transition: box-shadow 150ms ease; } input:focus, .StripeElement--focus { box-shadow: 0 1px 3px 0 #cfd7df; } .StripeElement--invalid { border-color: #fa755a; } .StripeElement--webkit-autofill { background-color: #fefde5 !important; }

When the form loads, you can create an instance of the Australia Bank Account Element and mount it to the Element container:

// Custom styling can be passed to options when creating an Element var style = { base: { color: '#32325d', fontSize: '16px', '::placeholder': { color: '#aab7c4' }, ':-webkit-autofill': { color: '#32325d', }, }, invalid: { color: '#fa755a', iconColor: '#fa755a', ':-webkit-autofill': { color: '#fa755a', }, } }; var options = { style: style, disabled: false, hideIcon: false, iconStyle: "default", // or "solid" } // Create an instance of the auBankAccount Element. var auBankAccount = elements.create('auBankAccount', options); // Add an instance of the auBankAccount Element into // the `au-bank-account-element` <div>. auBankAccount.mount('#au-bank-account-element');
// Custom styling can be passed to options when creating an Element const style = { base: { color: '#32325d', fontSize: '16px', '::placeholder': { color: '#aab7c4' }, ':-webkit-autofill': { color: '#32325d', }, }, invalid: { color: '#fa755a', iconColor: '#fa755a', ':-webkit-autofill': { color: '#fa755a', }, } }; const options = { style: style, disabled: false, hideIcon: false, iconStyle: "default", // or "solid" } // Create an instance of the auBankAccount Element. const auBankAccount = elements.create('auBankAccount', options); // Add an instance of the auBankAccount Element into // the `au-bank-account-element` <div>. auBankAccount.mount('#au-bank-account-element');

Install React Stripe.js and the Stripe.js loader from the npm public registry.

npm install --save @stripe/react-stripe-js @stripe/stripe-js

We also provide a UMD build for sites that do not use npm or modules.

Include the Stripe.js script, which exports a global Stripe function, and the UMD build of React Stripe.js, which exports a global ReactStripe object. Always load the Stripe.js script directly from js.stripe.com to remain PCI compliant. Do not include the script in a bundle or host a copy of it yourself.

<!-- Stripe.js --> <script src="https://js.stripe.com/v3/"></script> <!-- React Stripe.js development build --> <script src="https://unpkg.com/@stripe/react-stripe-js@latest/dist/react-stripe.umd.js"></script> <!-- When you are ready to deploy your site to production, remove the above development script, and include the following production build. --> <script src="https://unpkg.com/@stripe/react-stripe-js@latest/dist/react-stripe.umd.min.js"></script>

Add Stripe.js and Elements to your page

To use Element components, wrap the root of your React app in an Elements provider. Call loadStripe with your publishable key and pass the returned Promise to the Elements provider.

import React from 'react'; import ReactDOM from 'react-dom'; import {Elements} from '@stripe/react-stripe-js'; import {loadStripe} from '@stripe/stripe-js'; import CheckoutForm from './CheckoutForm'; // Make sure to call `loadStripe` outside of a component’s render to avoid // recreating the `Stripe` object on every render. const stripePromise = loadStripe("pk_test_TYooMQauvdEDq54NiTphI7jx"); function App() { return ( <Elements stripe={stripePromise}> <CheckoutForm /> </Elements> ); }; ReactDOM.render(<App />, document.getElementById('root'));

Direct Debit Requests

Before you can create a BECS Direct Debit payment, your customer must agree with the Direct Debit Request Service Agreement. They do so by submitting a completed Direct Debit Request (DDR). The approval gives you a mandate to debit their account. The Mandate is a record of the permission a customer has given you to debit their payment method corresponding to this Direct Debit Request.

For online mandate acceptance, you can create a form to collect the necessary information. The form should be served over HTTPS and capture the following information:

Information Description
Account name The full name of the account holder
BSB number The Bank-State-Branch number of the bank account (eg. 123-456)
Account number The bank account number (eg. 87654321)

When collecting a Direct Debit Request, you should follow our BECS Direct Debit Terms and as part of your checkout form:

  • Display the exact terms of Stripe’s DDR service agreement either inline on the form, or on a page linked from the form, and identifying it as the “DDR service agreement”
  • Display the following standard authorization text for your customer to accept the BECS DDR, where you replace Rocketship Inc with your company name. Their acceptance authorizes you to initiate BECS Direct Debit payments from their bank account.

The details of the accepted mandate are generated when setting up a PaymentMethod or confirming a PaymentIntent. At all times, you should be able to share this mandate—the accepted DDR and its accompanying DDR service agreement—with your customer, either in print or as a non-changeable electronic copy (such as via email). Stripe hosts this for you under the url property of the Mandate object linked to the PaymentMethod. You can review an example of how this looks.

Add and configure an AuBankAccountElement component

The AuBankAccountElement component will help you collect and validate both the BSB number and the account number. Additionally, your customer must read and accept the Direct Debit Request service agreement.

/** * Use the CSS tab above to style your Element's container. */ import React from 'react'; import {AuBankAccountElement} from '@stripe/react-stripe-js'; import './BecsFormStyles.css' // Custom styling can be passed as options when creating an Element. const AU_BANK_ACCOUNT_STYLE = { base: { color: '#32325d', fontSize: '16px', '::placeholder': { color: '#aab7c4' }, ':-webkit-autofill': { color: '#32325d', }, }, invalid: { color: '#fa755a', iconColor: '#fa755a', ':-webkit-autofill': { color: '#fa755a', }, } }; const AU_BANK_ACCOUNT_ELEMENT_OPTIONS = { style: AU_BANK_ACCOUNT_STYLE, disabled: false, hideIcon: false, iconStyle: "default", // or "solid" }; export default function BecsForm({onSubmit, disabled}) { return ( <form onSubmit={onSubmit}> <div className="form-row inline"> <div className="col"> <label> Name <input name="accountholder-name" placeholder="John Smith" required /> </label> </div> <div className="col"> <label> Email Address <input name="email" type="email" placeholder="john.smith@example.com" required /> </label> </div> </div> <div className="form-row"> <label> Bank Account <AuBankAccountElement options={AU_BANK_ACCOUNT_ELEMENT_OPTIONS} /> </label> </div> {/* Display mandate acceptance text. */} <div className="mandate-acceptance"> By providing your bank account details and confirming this payment, you agree to this Direct Debit Request and the <a href="https://stripe.com/au-becs-dd-service-agreement/legal">Direct Debit Request service agreement</a>, and authorise Stripe Payments Australia Pty Ltd ACN 160 180 343 Direct Debit User ID number 507156 (“Stripe”) to debit your account through the Bulk Electronic Clearing System (BECS) on behalf of Rocketship Inc. (the "Merchant") for any amounts separately communicated to you by the Merchant. You certify that you are either an account holder or an authorised signatory on the account listed above. </div> <button type="submit" disabled={disabled}>Confirm Payment</button> </form> ); };
/** * Shows how you can use CSS to style your Element's container. * These classes are added to your Stripe Element by default. * You can override these classNames by using the options passed * to the IbanElement component. * https://stripe.com/docs/js/elements_object/create_element?type=iban#elements_create-options-classes */ input, .StripeElement { height: 40px; padding: 10px 12px; color: #32325d; background-color: white; border: 1px solid transparent; border-radius: 4px; box-shadow: 0 1px 3px 0 #e6ebf1; -webkit-transition: box-shadow 150ms ease; transition: box-shadow 150ms ease; } input:focus, .StripeElement--focus { box-shadow: 0 1px 3px 0 #cfd7df; } .StripeElement--invalid { border-color: #fa755a; } .StripeElement--webkit-autofill { background-color: #fefde5 !important; }

Elements are completely customizable. You can style Elements to match the look and feel of your site, providing a seamless checkout experience for your customers. It’s also possible to style various input states, for example when the Element has focus.

5 Submit the payment to Stripe Client-side

Rather than sending the entire PaymentIntent object to the client, use its client secret from step 2. This is different from your API keys that authenticate Stripe API requests.

Use stripe.confirmAuBecsDebitPayment to both collect the mandate and complete the payment when the user submits the form. Including the customer’s email address and the account holder’s name in the billing_details property of the payment_method parameter is required to create a BECS Direct Debit PaymentMethod.

var form = document.getElementById('payment-form'); var accountholderName = document.getElementById('accountholder-name'); var email = document.getElementById('email'); var submitButton = document.getElementById('submit-button'); var clientSecret = submitButton.dataset.secret; form.addEventListener('submit', function(event) { event.preventDefault(); stripe.confirmAuBecsDebitPayment( clientSecret, { payment_method: { au_becs_debit: auBankAccount, billing_details: { name: accountholderName.value, email: email.value } } } ); });
const form = document.getElementById('payment-form'); const accountholderName = document.getElementById('accountholder-name'); const email = document.getElementById('email'); const submitButton = document.getElementById('submit-button'); const clientSecret = submitButton.dataset.secret; form.addEventListener('submit', (event) => { event.preventDefault(); stripe.confirmAuBecsDebitPayment( clientSecret, { payment_method: { au_becs_debit: auBankAccount, billing_details: { name: accountholderName.value, email: email.value } } } ); });

After confirming the PaymentIntent, you should share the mandate URL from the Mandate object with your customer. We also recommend including the following details to your customer when you confirm their mandate has been established:

  • an explicit confirmation message that indicates a Direct Debit arrangement has been set up
  • the business name that will appear on the customer’s bank statement whenever their account gets debited
  • the payment amount and schedule (if applicable)
  • a link to the generated DDR mandate URL

The Mandate object’s ID is accessible from the payment_method_details on the charge object of the PaymentIntent, which is sent as part of the payment_intent.processing event sent after confirmation but can also be retrieved through the API.

Use stripe.confirmAuBecsDebitPayment to both collect the mandate and complete the payment when the user submits the form. Including the customer’s email address and the account holder’s name in the billing_details property of the payment_method parameter is required to create a BECS Direct Debit PaymentMethod.

To call stripe.confirmAuBecsDebitPayment from your payment form component, use the useStripe and useElements hooks.

If you prefer traditional class components over hooks, you can instead use an ElementsConsumer.

import React from 'react'; import {useStripe, useElements, AuBankAccountElement} from '@stripe/react-stripe-js'; import BecsForm from './BecsForm'; export default function CheckoutForm() { const stripe = useStripe(); const elements = useElements(); const handleSubmit = async (event) => { // We don't want to let default form submission happen here, // which would refresh the page. event.preventDefault(); if (!stripe || !elements) { // Stripe.js has not yet loaded. // Make sure to disable form submission until Stripe.js has loaded. return; } const auBankAccount = elements.getElement(AuBankAccountElement); // For brevity, this example is using uncontrolled components for // the accountholder's name and email. In a real world app you will // probably want to use controlled components. // https://reactjs.org/docs/uncontrolled-components.html // https://reactjs.org/docs/forms.html#controlled-components const accountholderName = event.target['accountholder-name']; const email = event.target.email; const result = await stripe.confirmAuBecsDebitPayment('{{CLIENT_SECRET}}', { payment_method: { au_becs_debit: auBankAccount, billing_details: { name: accountholderName.value, email: email.value, }, } }); if (result.error) { // Show error to your customer. console.log(result.error.message); } else { // Show a confirmation message to your customer. // The PaymentIntent is in the 'processing' state. // BECS Direct Debit is a delayed notification payment // method, so funds are not immediately available. } }; return ( <BecsForm onSubmit={handleSubmit} disabled={!stripe} /> ); }
import React from 'react'; import {ElementsConsumer, AuBankAccountElement} from '@stripe/react-stripe-js'; import BecsForm from './BecsForm'; class CheckoutForm extends React.Component { handleSubmit = async (event) => { // We don't want to let default form submission happen here, // which would refresh the page. event.preventDefault(); const {stripe, elements} = this.props if (!stripe || !elements) { // Stripe.js has not yet loaded. // Make sure to disable form submission until Stripe.js has loaded. return; } const auBankAccount = elements.getElement(AuBankAccountElement); // For brevity, this example is using uncontrolled components for // the accountholder's name and email. In a real world app you will // probably want to use controlled components. // https://reactjs.org/docs/uncontrolled-components.html // https://reactjs.org/docs/forms.html#controlled-components const accountholderName = event.target['accountholder-name']; const email = event.target.email; const result = await stripe.confirmAuBecsDebitPayment('{{CLIENT_SECRET}}', { payment_method: { au_becs_debit: auBankAccount, billing_details: { name: accountholderName.value, email: email.value, }, } }); if (result.error) { // Show error to your customer. console.log(result.error.message); } else { // Show a confirmation message to your customer. // The PaymentIntent is in the 'processing' state. // BECS Direct Debit is a delayed notification payment // method, so funds are not immediately available. } }; render() { const {stripe} = this.props; return ( <BecsForm onSubmit={this.handleSubmit} disabled={!stripe} /> ); } } export default function InjectedCheckoutForm() { return ( <ElementsConsumer> {({stripe, elements}) => ( <CheckoutForm stripe={stripe} elements={elements} /> )} </ElementsConsumer> ); }

After confirming the PaymentIntent, you should share the mandate URL from the Mandate object with your customer. We also recommend including the following details to your customer when you confirm their mandate has been established:

  • an explicit confirmation message that indicates a Direct Debit arrangement has been set up
  • the business name that will appear on the customer’s bank statement whenever their account gets debited
  • the payment amount and schedule (if applicable)
  • a link to the generated DDR mandate URL

The Mandate object’s ID is accessible from the payment_method_details on the charge object of the PaymentIntent, which is sent as part of the payment_intent.processing event sent after confirmation but can also be retrieved through the API.

6 Confirm the PaymentIntent succeeded Server-side

BECS Direct Debit is a delayed notification payment method, which means that funds are not immediately available. A BECS Direct Debit PaymentIntent typically remains in a processing state for 3 business days after submission to the BECS network. This submission happens once per day. Once the payment succeeds, the associated PaymentIntent status updates from processing to succeeded.

The following events are sent when the PaymentIntent status is updated:

Event Description Next steps
payment_intent.processing The customer’s payment was submitted to Stripe successfully. Wait for the initiated payment to succeed or fail.
payment_intent.succeeded The customer’s payment succeeded. Fulfill the goods or services that the customer purchased.
payment_intent.payment_failed The customer’s payment was declined. Contact the customer via email or push notification and request another payment method.

Note that because setup_future_usage and customer were set, the PaymentMethod will be attached to the Customer object once the payment enters the processing state. This attachment happens regardless of whether payment eventually succeeds or fails.

When a Direct Debit attempt fails, Stripe sends a payment_intent.payment_failed event containing a PaymentIntent object. The last_payment_error attribute on the PaymentIntent contains a code and message describing details of the failure.

The failures can be transient or final for the mandate associated with the failed PaymentIntent. In case of a final failure, Stripe revokes the mandate to prevent additional failure costs. When this happens, and you need your customer to pay, it is your responsibility to reach out to your customer to establish a new mandate by re-collecting the bank account information.

For the following failure codes returned, Stripe updates the mandate status as follows:

Failure Code Description Mandate Status
debit_not_authorized There’s a permanent failure due to a restriction or block to debit the account and you should reach out to your customer. inactive
account_closed There’s a permanent failure because the account has been closed and you should reach out to your customer. inactive
no_account There’s a permanent failure because there’s no account for the provided bank information and you should reach out to your customer. inactive
refer_to_customer There’s a transient failure (e.g. insufficient funds) and you can re-attempt to debit without collecting a new mandate. active

We recommend using webhooks to confirm the charge has succeeded or failed, and to notify the customer that mandate establishment and/or payment are complete or require additional attention.

7 Test the integration

You can test your form using the test BSB number 000-000 and one of the test account numbers below with your confirmAuBecsDebitPayment request.

Account Number Description
000123456 The PaymentIntent status transitions from processing to succeeded. The mandate status remains active.
900123456 The PaymentIntent status transitions from processing to succeeded (with a three-minute delay). The mandate status remains active.
111111113 The PaymentIntent status transitions from processing to requires_payment_method with an account_closed failure code. The mandate status will become inactive.
111111116 The PaymentIntent status transitions from processing to requires_payment_method with a no_account failure code. The mandate status will become inactive.
222222227 The PaymentIntent status transitions from processing to requires_payment_method with a refer_to_customer failure code. The mandate status will remain active.
922222227 The PaymentIntent status transitions from processing to requires_payment_method with a refer_to_customer failure code (with a three-minute delay). The mandate status will remain active.
333333335 The PaymentIntent status transitions from processing to requires_payment_method with a debit_not_authorized failure code. The mandate status will become inactive.

Webhook events are triggered when using test account numbers. In test mode, PaymentIntents succeed and fail immediately, and as a result, the respective payment_intent.succeeded and payment_intent.payment_failed events trigger immediately as well. In live mode, the webhooks get triggered with the same delays as those of their related PaymentIntent successes and failures.

Optional Validate the Australia Bank Account Element Client-side

The Australia Bank Account Element validates user input as it is typed. To help your customers fix mistakes, you should listen to change events on the Australia Bank Account Element and display any errors:

auBankAccount.on('change', function(event) { var displayError = document.getElementById('error-message'); if (event.error) { displayError.textContent = event.error.message; } else { displayError.textContent = ''; } });
auBankAccount.on('change', (event) => { const displayError = document.getElementById('error-message'); if (event.error) { displayError.textContent = event.error.message; } else { displayError.textContent = ''; } });
<AuBankAccountElement onChange={(event) => { if (event.error) { // Store event.error.message in state and display it. } else { // Remove existing error from state. } }}>

The change event contains other parameters that can help to build a richer user experience. Refer to the Stripe.js reference for more details.

Use STPAUBECSFormView, Stripe’s prebuilt BECS payment details collection UI, to create a payment form that securely collects bank details without handling sensitive customer data. Accepting BECS Direct Debit payments in your app consists of:

  • Creating an object to track a payment
  • ​​Collecting payment method information and mandate acknowledgement
  • Submitting the payment to Stripe for processing

Stripe users in Australia can use the STPAUBECSFormView and a PaymentIntent to accept BECS Direct Debit payments from customers with an Australian bank account.

1 Set up Stripe Server-side Client-side

First, you need a Stripe account. Register now.

Server-side

This integration requires endpoints on your server that talk to the Stripe API. Use our official libraries for access to the Stripe API from your server:

# Available as a gem gem install stripe
# If you use bundler, you can add this line to your Gemfile gem 'stripe'
# Install through pip pip install --upgrade stripe
# Or find the Stripe package on http://pypi.python.org/pypi/stripe/
# Find the version you want to pin: # https://github.com/stripe/stripe-python/blob/master/CHANGELOG.md # Specify that version in your requirements.txt file stripe>=2.48.0,<3.0
# Install the PHP library via Composer composer require stripe/stripe-php
# Or download the source directly: https://github.com/stripe/stripe-php/releases
/* For Gradle, add the following dependency to your build.gradle and replace {VERSION} with the version number you want to use from - https://mvnrepository.com/artifact/com.stripe/stripe-java or - https://github.com/stripe/stripe-java/releases/latest */ implementation "com.stripe:stripe-java:{VERSION}"
<!-- For Maven, add the following dependency to your POM and replace {VERSION} with the version number you want to use from - https://mvnrepository.com/artifact/com.stripe/stripe-java or - https://github.com/stripe/stripe-java/releases/latest --> <dependency> <groupId>com.stripe</groupId> <artifactId>stripe-java</artifactId> <version>{VERSION}</version> </dependency>
# For other environments, manually install the following JARs: # - The Stripe JAR from https://github.com/stripe/stripe-java/releases/latest # - Google Gson from https://github.com/google/gson
# Install via npm npm install --save stripe
# Make sure your project is using Go Modules god mod init # Install stripe-go go get -u github.com/stripe/stripe-go/v71
// Then import the package import ( "github.com/stripe/stripe-go/v71" )
# Install via dotnet dotnet add package Stripe.net dotnet restore
# Or install via NuGet PM> Install-Package Stripe.net

Client-side

The iOS SDK is open source, fully documented, and compatible with apps supporting iOS 10 or above.

  1. If you haven't already, install the latest version of CocoaPods.
  2. If you don't have an existing Podfile, run the following command to create one:
    pod init
  3. Add this line to your Podfile:
    pod 'Stripe'
  4. Run the following command:
    pod install
  5. Don't forget to use the .xcworkspace file to open your project in Xcode, instead of the .xcodeproj file, from here on out.
  6. In the future, to update to the latest version of the SDK, just run:
    pod update Stripe
  1. If you haven't already, install the latest version of Carthage.
  2. Add this line to your Cartfile:
    github "stripe/stripe-ios"
  3. Follow the Carthage installation instructions.
  4. In the future, to update to the latest version of the SDK, run the following command:
    carthage update stripe-ios --platform ios
  1. Head to our GitHub releases page and download and unzip Stripe.framework.zip.
  2. Drag Stripe.framework to the "Embedded Binaries" section of your Xcode project's "General" settings. Make sure to select "Copy items if needed".
  3. Head to the "Build Phases" section of your Xcode project settings, and create a new "Run Script Build Phase". Paste the following snippet into the text field:
    bash "${BUILT_PRODUCTS_DIR}/${FRAMEWORKS_FOLDER_PATH}/Stripe.framework/integrate-dynamic-framework.sh"
  4. In the future, to update to the latest version of our SDK, just repeat steps 1 and 2.

When your app starts, configure the SDK with your Stripe publishable key so that it can make requests to the Stripe API.

import UIKit import Stripe @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { Stripe.setDefaultPublishableKey("pk_test_TYooMQauvdEDq54NiTphI7jx") // do any other necessary launch configuration return true } }
#import "AppDelegate.h" #import <Stripe/Stripe.h> @implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { [Stripe setDefaultPublishableKey:@"pk_test_TYooMQauvdEDq54NiTphI7jx"]; // do any other necessary launch configuration return YES; } @end

2 Collect payment method details and mandate acknowledgment Client-side

You can securely collect BECS Debit payment information with STPAUBECSFormView, a drop-in UI component provided by the SDK. STPAUBECSFormView​ provides a UI for customers to enter their name, email, BSB number, and account number—in addition to displaying the BECS Direct Debit Terms.

Create an instance of STPAUBECSFormView​ configured with your company name and set up a delegate for the SDK to notify after the customer enters the required details to create an instance of STPPaymentMethodParams​. You can also customize STPAUBECSFormView​ to match the look and feel of your app by providing values to STPAUBECSFormView​'s public properties.

import UIKit import Stripe class CheckoutViewController: UIViewController { private var becsFormView = STPAUBECSDebitFormView(companyName: "Example Company Inc.") private let payButton = UIButton() private var paymentIntentClientSecret: String? override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = .secondarySystemBackground payButton.layer.cornerRadius = 5 payButton.contentEdgeInsets = UIEdgeInsets(top: 4, left: 8, bottom: 4, right: 8) payButton.backgroundColor = .systemGray3 payButton.titleLabel?.font = UIFont.systemFont(ofSize: 18) payButton.setTitle("Accept Mandate and Pay", for: .normal) payButton.addTarget(self, action: #selector(pay), for: .touchUpInside) payButton.isEnabled = false payButton.translatesAutoresizingMaskIntoConstraints = false view.addSubview(payButton) becsFormView.becsDebitFormDelegate = self becsFormView.translatesAutoresizingMaskIntoConstraints = false view.addSubview(becsFormView) NSLayoutConstraint.activate([ becsFormView.leadingAnchor.constraint(equalTo: view.leadingAnchor), view.trailingAnchor.constraint(equalTo: becsFormView.trailingAnchor), becsFormView.topAnchor.constraint(equalToSystemSpacingBelow: view.safeAreaLayoutGuide.topAnchor, multiplier: 2), payButton.centerXAnchor.constraint(equalTo: view.centerXAnchor), payButton.topAnchor.constraint(equalToSystemSpacingBelow: becsFormView.bottomAnchor, multiplier: 2), ]) } @objc func pay() { // ... } } extension CheckoutViewController: STPAUBECSDebitFormViewDelegate { func auBECSDebitForm(_ form: STPAUBECSDebitFormView, didChangeToStateComplete complete: Bool) { payButton.isEnabled = complete payButton.backgroundColor = complete ? .systemBlue : .systemGray3 } }
#import "CheckoutViewController.h" #import <Stripe/Stripe.h> @interface CheckoutViewController () <STPAUBECSDebitFormViewDelegate> @property (nonatomic) STPAUBECSDebitFormView *becsFormView; @property (nonatomic, readonly) UIButton *payButton; @property (nonatomic, copy) NSString *paymentIntentClientSecret; @end @implementation CheckoutViewController - (void)viewDidLoad { [super viewDidLoad]; self.view.backgroundColor = [UIColor whiteColor]; _payButton = [UIButton buttonWithType:UIButtonTypeCustom]; self.payButton.layer.cornerRadius = 5; self.payButton.backgroundColor = [UIColor systemGray3Color]; self.payButton.titleLabel.font = [UIFont systemFontOfSize:18]; self.payButton.contentEdgeInsets = UIEdgeInsetsMake(4, 8, 4, 8); self.payButton.enabled = NO; [self.payButton setTitle:@"Accept Mandate and Pay" forState:UIControlStateNormal]; [self.payButton addTarget:self action:@selector(pay) forControlEvents:UIControlEventTouchUpInside]; self.payButton.translatesAutoresizingMaskIntoConstraints = NO; [self.view addSubview:self.payButton]; self.becsFormView = [[STPAUBECSDebitFormView alloc] initWithCompanyName:@"Example Company Inc."]; self.becsFormView.becsDebitFormDelegate = self; self.becsFormView.translatesAutoresizingMaskIntoConstraints = NO; [self.view addSubview:self.becsFormView]; [NSLayoutConstraint activateConstraints:@[ [self.becsFormView.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor], [self.view.trailingAnchor constraintEqualToAnchor:self.becsFormView.trailingAnchor], [self.becsFormView.topAnchor constraintEqualToSystemSpacingBelowAnchor:self.view.safeAreaLayoutGuide.topAnchor multiplier:2], [self.payButton.centerXAnchor constraintEqualToAnchor:self.view.centerXAnchor], [self.payButton.topAnchor constraintEqualToSystemSpacingBelowAnchor:self.becsFormView.bottomAnchor multiplier:2], ]]; } - (void)pay { // ... } #pragma mark - STPAUBECSDebitFormViewDelegate - (void)auBECSDebitForm:(STPAUBECSDebitFormView *)form didChangeToStateComplete:(BOOL)complete { self.payButton.enabled = complete; self.payButton.backgroundColor = complete ? [UIColor systemBlueColor] : [UIColor systemGray3Color]; } @end

3 Create a PaymentIntent Server-side

A PaymentIntent is an object that represents your intent to collect a payment from a customer. The PaymentIntent tracks the lifecycle of the payment process through each stage. First, create a PaymentIntent on your server and specify the amount to collect and the aud currency (BECS Direct Debit does not support other currencies). If you already have an integration using the Payment Intents API, add au_becs_debit to the list of payment method types for your PaymentIntent.

To save the BECS Direct Debit account for reuse, set the setup_future_usage parameter to off_session. BECS Direct Debit only accepts an off_session value for this parameter.

curl https://api.stripe.com/v1/payment_intents \ -u sk_test_4eC39HqLyjWDarjtT1zdp7dc: \ -d amount=1099 \ -d setup_future_usage=off_session \ -d currency=aud \ -d customer="{{CUSTOMER_ID}}" \ -d "payment_method_types[]"=au_becs_debit
require 'sinatra' require 'json' require 'stripe' # 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' get '/pay' do payment_intent = Stripe::PaymentIntent.create({ amount: 1099, currency: 'aud', setup_future_usage: 'off_session', customer: customer['id'], payment_method_types: ['au_becs_debit'], }) client_secret = payment_intent['client_secret'] # Pass the client secret to the client end
import stripe import json from flask import Flask, request, render_template app = Flask(__name__, static_folder=".", static_url_path="", template_folder=".") # 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' @app.route('/pay') def pay(): payment_intent = stripe.PaymentIntent.create( amount=1099, currency='aud', setup_future_usage='off_session', customer=customer['id'], payment_method_types=['au_becs_debit'] ) client_secret = payment_intent.client_secret # Pass the client secret to the client if __name__ == '__main__': app.run()
// 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'); # vendor using composer require_once('vendor/autoload.php'); $payment_intent = \Stripe\PaymentIntent::create([ 'amount' => 1099, 'currency' => 'aud', 'setup_future_usage' => 'off_session', 'customer' => $customer->id, 'payment_method_types' => ['au_becs_debit'], ]); $client_secret = $payment_intent->client_secret // Pass the client secret to the client
import java.util.HashMap; import java.util.Map; import com.stripe.Stripe; import com.stripe.model.PaymentIntent; public class StripeJavaQuickStart { public static void main(String[] args) { // 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"; Map<String, Object> paymentIntentParams = new HashMap<String, Object>(); paymentIntentParams.put("amount", 1099); paymentIntentParams.put("currency", "aud"); paymentIntentParams.put("setup_future_usage", "off_session"); params.put("customer", customer.getId()); paymentIntentParams.put("payment_method_types", Arrays.asList("au_becs_debit")); PaymentIntent paymentIntent = PaymentIntent.create(paymentIntentParams); String clientSecret = paymentIntent.getClientSecret(); // Pass the client secret to the client } }
// Using Express const express = require('express'); const app = express(); app.use(express.json()); const { resolve } = require("path"); // 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 paymentIntent = await stripe.paymentIntents.create({ amount: 1099, currency: 'aud', setup_future_usage: 'off_session', customer: customer.id, payment_method_types: ['au_becs_debit'], }); const clientSecret = paymentIntent.client_secret; // Pass the client secret to the client
package main import ( "encoding/json" "log" "net/http" "github.com/stripe/stripe-go/v71" "github.com/stripe/stripe-go/v71/paymentintent" ) func main() { // 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" paymentIntentParams := &stripe.PaymentIntentParams{ Amount: stripe.Int64(1099), Currency: stripe.String(string(Stripe.CurrencyAUD)), SetupFutureUsage: stripe.String("off_session"), Customer: stripe.String(customer.ID), PaymentMethodTypes: stripe.StringSlice([]string{ "au_becs_debit", }), } paymentIntent, err := paymentintent.New(paymentIntentParams) clientSecret := paymentIntent.ClientSecret // Pass the client secret to the client }
// 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"; using System; using System.Collections.Generic; using Microsoft.AspNetCore.Mvc; using Stripe; namespace StripeExampleApi.Controllers { [Route("/pay")] public class PayController : Controller { public IActionResult Index() { var service = new PaymentIntentService(); var options = new PaymentIntentCreateOptions { Amount = 1099, Currency = "aud", SetupFutureUsage = "off_session", Customer = "{{CUSTOMER_ID}}", PaymentMethodTypes = new List<string> { "au_becs_debit", }, }; var paymentIntent = service.Create(options); var clientSecret = paymentIntent.ClientSecret; // Pass the client secret to the client } } }

After creating a PaymentIntent, Stripe returns a PaymentIntent object containing a client_secret property. Pass the client secret to the client side.

4 Submit the payment to Stripe Client-side

When the customer taps the Pay button, confirm the PaymentIntent to complete the payment.

First, assemble a STPPaymentIntentParams object with:

  1. The STPAUBECSFormView’s paymentMethodParams property
  2. The PaymentIntent client secret from your server

Rather than sending the entire PaymentIntent object to the client, use its client secret from step 3. This is different from your API keys that authenticate Stripe API requests.

Next, complete the payment by calling the STPPaymentHandler confirmPayment method.

import UIKit import Stripe class CheckoutViewController: UIViewController { // ... @objc func pay() { guard let paymentIntentClientSecret = paymentIntentClientSecret, let paymentMethodParams = becsFormView.paymentMethodParams else { return; } let paymentIntentParams = STPPaymentIntentParams(clientSecret: paymentIntentClientSecret) paymentIntentParams.paymentMethodParams = paymentMethodParams STPPaymentHandler.shared().confirmPayment(withParams: paymentIntentParams, authenticationContext: self) { (handlerStatus, paymentIntent, error) in switch handlerStatus { case .succeeded: // Payment succeeded // ... case .canceled: // Payment canceled // ... case .failed: // Payment failed // ... @unknown default: fatalError() } } } } extension CheckoutViewController: STPAuthenticationContext { func authenticationPresentingViewController() -> UIViewController { return self } }
#import "CheckoutViewController.h" #import <Stripe/Stripe.h> @interface CheckoutViewController () <STPAUBECSDebitFormViewDelegate, STPAuthenticationContext> // ... @end @implementation CheckoutViewController // ... - (void)pay { if (self.paymentIntentClientSecret == nil || self.becsFormView.paymentMethodParams == nil) { return; } STPPaymentIntentParams *paymentIntentParams = [[STPPaymentIntentParams alloc] initWithClientSecret:self.paymentIntentClientSecret]; paymentIntentParams.paymentMethodParams = self.becsFormView.paymentMethodParams; [[STPPaymentHandler sharedHandler] confirmPayment:paymentIntentParams withAuthenticationContext:self completion:^(STPPaymentHandlerActionStatus handlerStatus, STPPaymentIntent * handledIntent, NSError * _Nullable handlerError) { switch (handlerStatus) { case STPPaymentHandlerActionStatusFailed: // Payment failed // ... break; case STPPaymentHandlerActionStatusCanceled: // Payment canceled // ... break; case STPPaymentHandlerActionStatusSucceeded: // Payment succeeded // ... break; } }]; } #pragma mark - STPAuthenticationContext - (UIViewController *)authenticationPresentingViewController { return self; } @end

After confirming the PaymentIntent​, share the mandate URL from the Mandate object with your customer. We also recommend including the following details when you confirm their mandate has been established:

  • An explicit confirmation message that indicates a Direct Debit arrangement has been set up
  • The business name that will appear on the customer’s bank statement whenever their account gets debited
  • The payment amount and schedule (if applicable)
  • A link to the generated DDR mandate URL

​​You can access the the Mandate​ object’s ID from the payment_method_details​ on the charge object of the PaymentIntent (included with the payment_intent.processing​ event sent after confirmation) or you can retrieve it through the API.

5 Confirm the PaymentIntent succeeded Server-side

BECS Direct Debit is a delayed notification payment method, which means that funds are not immediately available. A BECS Direct Debit PaymentIntent typically remains in a processing state for 3 business days after submission to the BECS network. This submission happens once per day. Once the payment succeeds, the associated PaymentIntent status updates from processing to succeeded.

The following events are sent when the PaymentIntent status is updated:

Event Description Next steps
payment_intent.processing The customer’s payment was submitted to Stripe successfully. Wait for the initiated payment to succeed or fail.
payment_intent.succeeded The customer’s payment succeeded. Fulfill the goods or services that the customer purchased.
payment_intent.payment_failed The customer’s payment was declined. Contact the customer via email or push notification and request another payment method.

Note that because setup_future_usage and customer were set, the PaymentMethod will be attached to the Customer object once the payment enters the processing state. This attachment happens regardless of whether payment eventually succeeds or fails.

When a Direct Debit attempt fails, Stripe sends a payment_intent.payment_failed event containing a PaymentIntent object. The last_payment_error attribute on the PaymentIntent contains a code and message describing details of the failure.

The failures can be transient or final for the mandate associated with the failed PaymentIntent. In case of a final failure, Stripe revokes the mandate to prevent additional failure costs. When this happens, and you need your customer to pay, it is your responsibility to reach out to your customer to establish a new mandate by re-collecting the bank account information.

For the following failure codes returned, Stripe updates the mandate status as follows:

Failure Code Description Mandate Status
debit_not_authorized There’s a permanent failure due to a restriction or block to debit the account and you should reach out to your customer. inactive
account_closed There’s a permanent failure because the account has been closed and you should reach out to your customer. inactive
no_account There’s a permanent failure because there’s no account for the provided bank information and you should reach out to your customer. inactive
refer_to_customer There’s a transient failure (e.g. insufficient funds) and you can re-attempt to debit without collecting a new mandate. active

We recommend using webhooks to confirm the charge has succeeded or failed, and to notify the customer that mandate establishment and/or payment are complete or require additional attention.

6 Test the integration

You can test your form using the test BSB number 000-000 and one of the test account numbers below with your STPPaymentHandler confirmPayment method call.

Account Number Description
000123456 The PaymentIntent status transitions from processing to succeeded. The mandate status remains active.
900123456 The PaymentIntent status transitions from processing to succeeded (with a three-minute delay). The mandate status remains active.
111111113 The PaymentIntent status transitions from processing to requires_payment_method with an account_closed failure code. The mandate status will become inactive.
111111116 The PaymentIntent status transitions from processing to requires_payment_method with a no_account failure code. The mandate status will become inactive.
222222227 The PaymentIntent status transitions from processing to requires_payment_method with a refer_to_customer failure code. The mandate status will remain active.
922222227 The PaymentIntent status transitions from processing to requires_payment_method with a refer_to_customer failure code (with a three-minute delay). The mandate status will remain active.
333333335 The PaymentIntent status transitions from processing to requires_payment_method with a debit_not_authorized failure code. The mandate status will become inactive.

Webhook events are triggered when using test account numbers. In test mode, PaymentIntents succeed and fail immediately, and as a result, the respective payment_intent.succeeded and payment_intent.payment_failed events trigger immediately as well. In live mode, the webhooks get triggered with the same delays as those of their related PaymentIntent successes and failures.

Use BecsDebitWidget, Stripe’s prebuilt BECS payment details collection UI, to create a payment form that securely collects bank details without handling sensitive customer data. Accepting BECS Direct Debit payments in your app consists of:

  • Creating an object to track a payment
  • ​​Collecting payment method information and mandate acknowledgement
  • Submitting the payment to Stripe for processing

Stripe users in Australia can use the BecsDebitWidget and a PaymentIntent to accept BECS Direct Debit payments from customers with an Australian bank account.

1 Set up Stripe Server-side Client-side

First, you need a Stripe account. Register now.

Server-side

This integration requires endpoints on your server that talk to the Stripe API. Use our official libraries for access to the Stripe API from your server:

# Available as a gem gem install stripe
# If you use bundler, you can add this line to your Gemfile gem 'stripe'
# Install through pip pip install --upgrade stripe
# Or find the Stripe package on http://pypi.python.org/pypi/stripe/
# Find the version you want to pin: # https://github.com/stripe/stripe-python/blob/master/CHANGELOG.md # Specify that version in your requirements.txt file stripe>=2.48.0,<3.0
# Install the PHP library via Composer composer require stripe/stripe-php
# Or download the source directly: https://github.com/stripe/stripe-php/releases
/* For Gradle, add the following dependency to your build.gradle and replace {VERSION} with the version number you want to use from - https://mvnrepository.com/artifact/com.stripe/stripe-java or - https://github.com/stripe/stripe-java/releases/latest */ implementation "com.stripe:stripe-java:{VERSION}"
<!-- For Maven, add the following dependency to your POM and replace {VERSION} with the version number you want to use from - https://mvnrepository.com/artifact/com.stripe/stripe-java or - https://github.com/stripe/stripe-java/releases/latest --> <dependency> <groupId>com.stripe</groupId> <artifactId>stripe-java</artifactId> <version>{VERSION}</version> </dependency>
# For other environments, manually install the following JARs: # - The Stripe JAR from https://github.com/stripe/stripe-java/releases/latest # - Google Gson from https://github.com/google/gson
# Install via npm npm install --save stripe
# Make sure your project is using Go Modules god mod init # Install stripe-go go get -u github.com/stripe/stripe-go/v71
// Then import the package import ( "github.com/stripe/stripe-go/v71" )
# Install via dotnet dotnet add package Stripe.net dotnet restore
# Or install via NuGet PM> Install-Package Stripe.net

Client-side

The Android SDK is open source and fully documented.

To install the SDK, add stripe-android to the dependencies block of your app/build.gradle file:

apply plugin: 'com.android.application' android { ... } dependencies { // ... // Stripe Android SDK implementation 'com.stripe:stripe-android:15.0.1' }

Configure the SDK with your Stripe publishable key so that it can make requests to the Stripe API, such as in your Application subclass:

import com.stripe.android.PaymentConfiguration class MyApp : Application() { override fun onCreate() { super.onCreate() PaymentConfiguration.init( applicationContext, "pk_test_TYooMQauvdEDq54NiTphI7jx" ) } }
import com.stripe.android.PaymentConfiguration; public class MyApp extends Application { @Override public void onCreate() { super.onCreate(); PaymentConfiguration.init( getApplicationContext(), "pk_test_TYooMQauvdEDq54NiTphI7jx" ); } }

Our code samples also use OkHttp and GSON to make HTTP requests to a server.

2 Collect payment method details and mandate acknowledgment Client-side

You can securely collect BECS Debit payment information with BecsDebitWidget, a drop-in UI component provided by the SDK. BecsDebitWidget provides a UI for customers to enter their name, email, BSB number, and account number. It also displays the BECS Direct Debit Terms.

Add BecsDebitWidget to your layout

Add BecsDebitWidget to your layout and configure the app:companyName attribute with your company name.

<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <!-- app:companyName is a required attribute --> <com.stripe.android.view.BecsDebitWidget android:id="@+id/becs_debit_widget" android:layout_width="match_parent" android:layout_height="wrap_content" app:companyName="@string/company_name" /> <Button android:id="@+id/pay_button" android:layout_width="300dp" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:enabled="false" android:text="@string/pay_with_becs_debit" /> </LinearLayout>

Style the BecsDebitWidget

The BecsDebitWidget can be further customized by adding the following styles to your application’s styles.xml.

<?xml version="1.0" encoding="utf-8"?> <!-- Optionally customize the widget's EditText fields --> <style name="Stripe.BecsDebitWidget.EditText" parent="Stripe.Base.BecsDebitWidget.EditText"> <!-- Add custom styles here --> </style> <!-- Optionally customize the mandate --> <style name="Stripe.BecsDebitWidget.MandateAcceptanceTextView" parent="Stripe.Base.BecsDebitWidget.MandateAcceptanceTextView"> <!-- Add custom styles here --> </style>

Configure your Activity

Set a BecsDebitWidget.ValidParamsCallback instance on BecsDebitWidget#validParamsCallback to be notified after the customer enters the required details to create an instance of PaymentMethodCreateParams​.

class CheckoutActivity : Activity() { private val stripe: Stripe by lazy { Stripe(this, PaymentConfiguration.getInstance(this).publishableKey) } private lateinit var becsDebitWidget: BecsDebitWidget private lateinit var paymentIntentClientSecret: String override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.checkout_activity) becsDebitWidget = findViewById(R.id.becs_debit_widget) val payButton = findViewById<Button>(R.id.pay_button) becsDebitWidget.validParamsCallback = object : BecsDebitWidget.ValidParamsCallback { override fun onInputChanged(isValid: Boolean) { // enable payButton if the customer's input is valid payButton.isEnabled = isValid } } payButton.setOnClickListener { onPayClicked(paymentIntentClientSecret) } createPaymentIntent() } private fun createPaymentIntent() { // Create a PaymentIntent on your backend and return the client_secret to // this Activity. Set paymentIntentClientSecret to the client_secret. } }
public class CheckoutActivity extends Activity { private Stripe stripe; private BecsDebitWidget becsDebitWidget; private String paymentIntentClientSecret; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.checkout_activity); stripe = new Stripe( this, PaymentConfiguration.getInstance(this).getPublishableKey() ); becsDebitWidget = findViewById(R.id.becs_debit_widget); final Button payButton = findViewById(R.id.pay_button); payButton.setOnClickListener(v -> { onPayClicked(paymentIntentClientSecret); }); // enable payButton if the customer's input is valid becsDebitWidget .setValidParamsCallback(isValid -> payButton.setEnabled(isValid)); createPaymentIntent(); } private void createPaymentIntent() { // Create a PaymentIntent on your backend and return the client_secret to // this Activity. Set paymentIntentClientSecret to the client_secret. } }

3 Create a PaymentIntent Server-side

A PaymentIntent is an object that represents your intent to collect a payment from a customer. The PaymentIntent tracks the lifecycle of the payment process through each stage. First, create a PaymentIntent on your server and specify the amount to collect and the aud currency (BECS Direct Debit does not support other currencies). If you already have an integration using the Payment Intents API, add au_becs_debit to the list of payment method types for your PaymentIntent.

To save the BECS Direct Debit account for reuse, set the setup_future_usage parameter to off_session. BECS Direct Debit only accepts an off_session value for this parameter.

curl https://api.stripe.com/v1/payment_intents \ -u sk_test_4eC39HqLyjWDarjtT1zdp7dc: \ -d amount=1099 \ -d setup_future_usage=off_session \ -d currency=aud \ -d customer="{{CUSTOMER_ID}}" \ -d "payment_method_types[]"=au_becs_debit
require 'sinatra' require 'json' require 'stripe' # 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' get '/pay' do payment_intent = Stripe::PaymentIntent.create({ amount: 1099, currency: 'aud', setup_future_usage: 'off_session', customer: customer['id'], payment_method_types: ['au_becs_debit'], }) client_secret = payment_intent['client_secret'] # Pass the client secret to the client end
import stripe import json from flask import Flask, request, render_template app = Flask(__name__, static_folder=".", static_url_path="", template_folder=".") # 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' @app.route('/pay') def pay(): payment_intent = stripe.PaymentIntent.create( amount=1099, currency='aud', setup_future_usage='off_session', customer=customer['id'], payment_method_types=['au_becs_debit'] ) client_secret = payment_intent.client_secret # Pass the client secret to the client if __name__ == '__main__': app.run()
// 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'); # vendor using composer require_once('vendor/autoload.php'); $payment_intent = \Stripe\PaymentIntent::create([ 'amount' => 1099, 'currency' => 'aud', 'setup_future_usage' => 'off_session', 'customer' => $customer->id, 'payment_method_types' => ['au_becs_debit'], ]); $client_secret = $payment_intent->client_secret // Pass the client secret to the client
import java.util.HashMap; import java.util.Map; import com.stripe.Stripe; import com.stripe.model.PaymentIntent; public class StripeJavaQuickStart { public static void main(String[] args) { // 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"; Map<String, Object> paymentIntentParams = new HashMap<String, Object>(); paymentIntentParams.put("amount", 1099); paymentIntentParams.put("currency", "aud"); paymentIntentParams.put("setup_future_usage", "off_session"); params.put("customer", customer.getId()); paymentIntentParams.put("payment_method_types", Arrays.asList("au_becs_debit")); PaymentIntent paymentIntent = PaymentIntent.create(paymentIntentParams); String clientSecret = paymentIntent.getClientSecret(); // Pass the client secret to the client } }
// Using Express const express = require('express'); const app = express(); app.use(express.json()); const { resolve } = require("path"); // 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 paymentIntent = await stripe.paymentIntents.create({ amount: 1099, currency: 'aud', setup_future_usage: 'off_session', customer: customer.id, payment_method_types: ['au_becs_debit'], }); const clientSecret = paymentIntent.client_secret; // Pass the client secret to the client
package main import ( "encoding/json" "log" "net/http" "github.com/stripe/stripe-go/v71" "github.com/stripe/stripe-go/v71/paymentintent" ) func main() { // 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" paymentIntentParams := &stripe.PaymentIntentParams{ Amount: stripe.Int64(1099), Currency: stripe.String(string(Stripe.CurrencyAUD)), SetupFutureUsage: stripe.String("off_session"), Customer: stripe.String(customer.ID), PaymentMethodTypes: stripe.StringSlice([]string{ "au_becs_debit", }), } paymentIntent, err := paymentintent.New(paymentIntentParams) clientSecret := paymentIntent.ClientSecret // Pass the client secret to the client }
// 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"; using System; using System.Collections.Generic; using Microsoft.AspNetCore.Mvc; using Stripe; namespace StripeExampleApi.Controllers { [Route("/pay")] public class PayController : Controller { public IActionResult Index() { var service = new PaymentIntentService(); var options = new PaymentIntentCreateOptions { Amount = 1099, Currency = "aud", SetupFutureUsage = "off_session", Customer = "{{CUSTOMER_ID}}", PaymentMethodTypes = new List<string> { "au_becs_debit", }, }; var paymentIntent = service.Create(options); var clientSecret = paymentIntent.ClientSecret; // Pass the client secret to the client } } }

After creating a PaymentIntent, Stripe returns a PaymentIntent object containing a client_secret property. Pass the client secret to the client side.

4 Submit the payment to Stripe Client-side

When the customer taps the Pay button, confirm the PaymentIntent to complete the payment.

First, assemble a ConfirmPaymentIntentParams object with:

  1. The BecsDebitWidget#params property
  2. The PaymentIntent client secret from your server

Rather than sending the entire PaymentIntent object to the client, use its client secret from step 3. This is different from your API keys that authenticate Stripe API requests.

Next, complete the payment by calling the Stripie#confirmPayment() method.

class CheckoutActivity : Activity() { // ... fun onPayClicked(paymentIntentClientSecret: String) { becsDebitWidget.params?.let { params -> stripe.confirmPayment( this, ConfirmPaymentIntentParams.createWithPaymentMethodCreateParams( params, paymentIntentClientSecret ) ) } } override fun onActivityResult( requestCode: Int, resultCode: Int, data: Intent? ) { super.onActivityResult(requestCode, resultCode, data) stripe.onPaymentResult( requestCode, data, object : ApiResultCallback<PaymentIntentResult> { override fun onSuccess(result: PaymentIntentResult) { when (result.outcome) { StripeIntentResult.Outcome.SUCCEEDED -> { // PaymentIntent confirmation succeeded } StripeIntentResult.Outcome.CANCELED -> { // PaymentIntent confirmation canceled } StripeIntentResult.Outcome.FAILED -> { // PaymentIntent confirmation failed } } } override fun onError(e: Exception) { // exception thrown during PaymentIntent confirmation } } ) } }
public class CheckoutActivity extends Activity { private Stripe stripe; private BecsDebitWidget becsDebitWidget; // ... private void onPayClicked(@NonNull String paymentIntentIntentClientSecret) { final PaymentMethodCreateParams params = becsDebitWidget.getParams(); if (params != null) { stripe.confirmPayment( this, ConfirmPaymentIntentParams.createWithPaymentMethodCreateParams( params, paymentIntentIntentClientSecret ) ); } else { // customer input is invalid } } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); stripe.onPaymentResult( requestCode, data, new ApiResultCallback<PaymentIntentResult>() { @Override public void onSuccess(@NonNull PaymentIntentResult result) { final int outcome = result.getOutcome(); if (outcome == StripeIntentResult.Outcome.SUCCEEDED) { // PaymentIntent confirmation succeeded } else if (outcome == StripeIntentResult.Outcome.SUCCEEDED) { // PaymentIntent confirmation canceled } else if (outcome == StripeIntentResult.Outcome.FAILED) { // PaymentIntent confirmation failed } } @Override public void onError(@NonNull Exception e) { // exception thrown during PaymentIntent confirmation } } ) } }

After confirming the PaymentIntent​, share the mandate URL from the Mandate object with your customer. We also recommend including the following details when you confirm their mandate has been established:

  • An explicit confirmation message that indicates a Direct Debit arrangement has been set up
  • The business name that will appear on the customer’s bank statement whenever their account gets debited
  • The payment amount and schedule (if applicable)
  • A link to the generated DDR mandate URL

​​You can access the the Mandate​ object’s ID from the payment_method_details​ on the charge object of the PaymentIntent (included with the payment_intent.processing​ event sent after confirmation) or you can retrieve it through the API.

5 Confirm the PaymentIntent succeeded Server-side

BECS Direct Debit is a delayed notification payment method, which means that funds are not immediately available. A BECS Direct Debit PaymentIntent typically remains in a processing state for 3 business days after submission to the BECS network. This submission happens once per day. Once the payment succeeds, the associated PaymentIntent status updates from processing to succeeded.

The following events are sent when the PaymentIntent status is updated:

Event Description Next steps
payment_intent.processing The customer’s payment was submitted to Stripe successfully. Wait for the initiated payment to succeed or fail.
payment_intent.succeeded The customer’s payment succeeded. Fulfill the goods or services that the customer purchased.
payment_intent.payment_failed The customer’s payment was declined. Contact the customer via email or push notification and request another payment method.

Note that because setup_future_usage and customer were set, the PaymentMethod will be attached to the Customer object once the payment enters the processing state. This attachment happens regardless of whether payment eventually succeeds or fails.

When a Direct Debit attempt fails, Stripe sends a payment_intent.payment_failed event containing a PaymentIntent object. The last_payment_error attribute on the PaymentIntent contains a code and message describing details of the failure.

The failures can be transient or final for the mandate associated with the failed PaymentIntent. In case of a final failure, Stripe revokes the mandate to prevent additional failure costs. When this happens, and you need your customer to pay, it is your responsibility to reach out to your customer to establish a new mandate by re-collecting the bank account information.

For the following failure codes returned, Stripe updates the mandate status as follows:

Failure Code Description Mandate Status
debit_not_authorized There’s a permanent failure due to a restriction or block to debit the account and you should reach out to your customer. inactive
account_closed There’s a permanent failure because the account has been closed and you should reach out to your customer. inactive
no_account There’s a permanent failure because there’s no account for the provided bank information and you should reach out to your customer. inactive
refer_to_customer There’s a transient failure (e.g. insufficient funds) and you can re-attempt to debit without collecting a new mandate. active

We recommend using webhooks to confirm the charge has succeeded or failed, and to notify the customer that mandate establishment and/or payment are complete or require additional attention.

6 Test the integration

You can test your form using the test BSB number 000-000 and one of the test account numbers below with your Stripe#confirmPayment() method call.

Account Number Description
000123456 The PaymentIntent status transitions from processing to succeeded. The mandate status remains active.
900123456 The PaymentIntent status transitions from processing to succeeded (with a three-minute delay). The mandate status remains active.
111111113 The PaymentIntent status transitions from processing to requires_payment_method with an account_closed failure code. The mandate status will become inactive.
111111116 The PaymentIntent status transitions from processing to requires_payment_method with a no_account failure code. The mandate status will become inactive.
222222227 The PaymentIntent status transitions from processing to requires_payment_method with a refer_to_customer failure code. The mandate status will remain active.
922222227 The PaymentIntent status transitions from processing to requires_payment_method with a refer_to_customer failure code (with a three-minute delay). The mandate status will remain active.
333333335 The PaymentIntent status transitions from processing to requires_payment_method with a debit_not_authorized failure code. The mandate status will become inactive.

Webhook events are triggered when using test account numbers. In test mode, PaymentIntents succeed and fail immediately, and as a result, the respective payment_intent.succeeded and payment_intent.payment_failed events trigger immediately as well. In live mode, the webhooks get triggered with the same delays as those of their related PaymentIntent successes and failures.

See also

Congrats, you are done with your integration! You can learn about saving bank account details without a payment and integrating with Connect.

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