FPX payments

    Use the Payment Intents and Payment Methods APIs to accept FPX, a common payment method in Malaysia.

    Overview

    FPX is a single-use payment method. Customers pay with FPX by redirecting from your website, sending you payment, then returning to your website where you get immediate notification on whether the payment succeeded or failed.

    PaymentIntent status

    curl https://api.stripe.com/v1/payment_intents \
    -u sk_test_4eC39HqLyjWDarjtT1zdp7dc: \
    -d payment_method_types[]='fpx' \
    -d amount=1099 \
    -d currency=myr

    Returns a PaymentIntent with status requires_payment_method

    In order to accept FPX payments, you must first enable FPX in the Stripe Dashboard and accept the FPX terms of service. Doing so requires an activated Stripe Account.

    Take special note of the FPX Requirements section when designing and building your checkout and payment confirmation pages.

    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 sudo 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/
    # 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
    # Install via go go get github.com/stripe/stripe-go
    // Then import the package import ( "github.com/stripe/stripe-go" )
    # Install via dotnet dotnet add package Stripe.net dotnet restore
    # Or install via NuGet PM> Install-Package Stripe.net

    2 Create a PaymentIntent Server-side

    A PaymentIntent is an object that represents your intent to collect payment from a customer and 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 myr currency (FPX does not support other currencies). If you already have an integration using the Payment Intents API, add fpx to the list of payment method types for your PaymentIntent.

    curl https://api.stripe.com/v1/payment_intents \ -u sk_test_4eC39HqLyjWDarjtT1zdp7dc: \ -d "payment_method_types[]"=fpx \ -d amount=1099 \ -d currency=myr
    # 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' payment_intent = Stripe::PaymentIntent.create({ payment_method_types: ['fpx'], amount: 1099, currency: 'myr', })
    # 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' payment_intent = stripe.PaymentIntent.create( payment_method_types=['fpx'], amount=1099, currency='myr', )
    // 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'); $payment_intent = \Stripe\PaymentIntent::create([ 'payment_method_types' => ['fpx'], 'amount' => 1099, 'currency' => 'myr', ]);
    // 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"; PaymentIntentCreateParams params = PaymentIntentCreateParams.builder() .setAmount(1099L) .setCurrency("myr") .addPaymentMethodType("fpx") .build(); PaymentIntent paymentIntent = PaymentIntent.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 paymentIntent = await stripe.paymentIntents.create({ payment_method_types: ['fpx'], amount: 1099, currency: 'myr', });
    // 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.PaymentIntentParams{ Amount: stripe.Int64(1099), Currency: stripe.String(string(stripe.CurrencyMYR)), PaymentMethodTypes: stripe.StringSlice([]string{ "fpx", }), } intent, _ := paymentintent.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 service = new PaymentIntentService(); var options = new PaymentIntentCreateOptions { Amount = 1099, Currency = "myr", PaymentMethodTypes = new List<string> { "fpx", }, }; var intent = service.Create(options);

    We recommend creating a PaymentIntent as soon as the amount is known, such as when the customer begins the checkout process. You can update the amount if it changes later (for example, when calculating shipping fees and taxes).

    curl https://api.stripe.com/v1/payment_intents/pi_1DRuHnHgsMRlo4MtwuIAUe6u \ -u sk_test_4eC39HqLyjWDarjtT1zdp7dc: \ -d amount=1499
    # 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' payment_intent = Stripe::PaymentIntent.update( 'pi_1DRuHnHgsMRlo4MtwuIAUe6u', {amount: 1499}, )
    # 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' payment_intent = stripe.PaymentIntent.modify( 'pi_1DRuHnHgsMRlo4MtwuIAUe6u', amount=1499, )
    // 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'); $payment_intent = \Stripe\PaymentIntent::update( 'pi_1DRuHnHgsMRlo4MtwuIAUe6u', ['amount' => 1499] );
    // 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"; PaymentIntent paymentIntent = PaymentIntent.retrieve("pi_1DRuHnHgsMRlo4MtwuIAUe6u"); PaymentIntentUpdateParams params = PaymentIntentUpdateParams.builder() .setAmount(1499L) .build(); paymentIntent.update(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 paymentIntent = await stripe.paymentIntents.update( 'pi_1DRuHnHgsMRlo4MtwuIAUe6u', { amount: 1499, } );
    // 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.PaymentIntentParams{ Amount: stripe.Int64(1499), } intent, _ := paymentintent.Update("pi_1DRuHnHgsMRlo4MtwuIAUe6u", 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 service = new PaymentIntentService(); var options = new PaymentIntentUpdateOptions { Amount = 1499, }; var intent = service.Update("pi_1DRuHnHgsMRlo4MtwuIAUe6u", options);

    3 Collect payment method details using FPX Bank Element Client-side

    Stripe Elements is a set of prebuilt UI components for collecting payment details. Elements is automatically available as a feature of Stripe.js. Include the Stripe.js script on your checkout page by adding it to the head of your HTML file. Always load Stripe.js directly from js.stripe.com to remain PCI compliant. Don’t include the script in a bundle or host a copy of it yourself.

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

    Create an instance of Elements with the following JavaScript on your checkout page.

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

    Elements needs a place to live in your payment form. Create empty DOM nodes (containers) with unique IDs in your payment form and then pass those IDs to Elements.

    <form id="payment-form"> <div class="form-row"> <div> <label for="fpx-bank-element"> FPX Bank </label> <div id="fpx-bank-element"> <!-- A Stripe Element will be inserted here. --> </div> </div> </div> <button id="fpx-button" data-secret="<%= @intent.client_secret %>"> Submit Payment </button> <!-- Used to display form errors. --> <div id="error-message" role="alert"></div> </form>

    When the form above has loaded, create an instance of an fpxBank Element and mount it to the Element container created above.

    var style = { base: { // Add your base input styles here. For example: padding: '10px 12px', color: '#32325d', fontSize: '16px', }, }; // Create an instance of the fpxBank Element. var fpxBank = elements.create( 'fpxBank', { style: style, accountHolderType: 'individual', } ); // Add an instance of the fpxBank Element into the container with id `fpx-bank-element`. fpxBank.mount('#fpx-bank-element');
    const style = { base: { // Add your base input styles here. For example: padding: '10px 12px', color: '#32325d', fontSize: '16px', }, }; // Create an instance of the fpxBank Element. const fpxBank = elements.create( 'fpxBank', { style: style, accountHolderType: 'individual', } ); // Add an instance of the fpxBank Element into the container with id `fpx-bank-element`. fpxBank.mount('#fpx-bank-element');

    4 Submit the payment to Stripe Client-side

    To create a payment on the client side, pass the client secret of the PaymentIntent object that you created in Step 1.

    Use stripe.confirmFpxPayment to handle the redirect away from your page and to complete the payment. Add a return_url to this function to indicate where Stripe should redirect the user after they complete the payment on their bank’s website or mobile application.

    var form = document.getElementById('payment-form'); form.addEventListener('submit', function(event) { event.preventDefault(); var fpxButton = document.getElementById('fpx-button'); var clientSecret = fpxButton.dataset.secret; stripe.confirmFpxPayment(clientSecret, { payment_method: { fpx: fpxBank, }, // Return URL where the customer should be redirected after the authorization return_url: `${window.location.href}`, }).then((result) => { if (result.error) { // Inform the customer that there was an error. var errorElement = document.getElementById('error-message'); errorElement.textContent = result.error.message; } }); });
    const form = document.getElementById('payment-form'); form.addEventListener('submit', async function(event) { event.preventDefault(); const fpxButton = document.getElementById('fpx-button'); const clientSecret = fpxButton.dataset.secret; const result = await stripe.confirmFpxPayment(clientSecret, { payment_method: { fpx: fpxBank, }, // Return URL where the customer should be redirected after the authorization return_url: `${window.location.href}`, }); if (result.error) { // Inform the customer that there was an error. const errorElement = document.getElementById('error-message'); errorElement.textContent = result.error.message; } });

    The return_url should correspond to a page on your website that provides the status of the payment, by verifying the status of the PaymentIntent when rendering the return page. When Stripe redirects the customer to the return_url, the following URL query parameters are provided to verify status. You can also append your own query parameters when providing the return_url. They will persist through the redirect process.

    Parameter Description
    payment_intent The unique identifier for the PaymentIntent.
    payment_intent_client_secret The client secret of the PaymentIntent object.

    5 Fulfill the order Server-side

    Use a method such as webhooks to handle order fulfillment, instead of relying on your customer to return to the payment status page. When a customer completes payment, the PaymentIntent transitions to succeeded and emits the payment_intent.succeeded webhook event. If a customer doesn’t pay, the PaymentIntent will emit the payment_intent.payment_failed webhook event and return to a status of requires_payment_method.

    Optional Handle FPX bank element events

    You can update the configuration options (e.g., the style) of the fpxBank Element using .update(options).

    The FPX Bank Element outputs the customer’s selected bank as it changes. To perform additional logic with the bank value (e.g., requiring the field for form validation), you can listen to the change event:

    fpxBank.on('change', function(event) { var bank = event.value; // Perform any additional logic here... });
    fpxBank.on('change', function(event) { var bank = event.value; // Perform any additional logic here... });

    The change event contains other helpful parameters:

    Parameter Description
    elementType The name of the element. The default value is fpxBank.
    empty If true, the value is empty.
    complete If true, the customer has selected the value. You can use this parameter to progressively disclose the rest of your form or to enable form submission.
    value The FPX bank that the customer selected from the Element. The FPX guide contains the complete list of banks.

    Optional Handle FPX redirect manually

    We recommend relying on Stripe.js to handle FPX redirects and payments with confirmFpxPayment. However, you can also manually redirect your customers by:

    1. Providing the URL where your customers will be redirected after they complete their payment.
    curl https://api.stripe.com/v1/payment_intents/pi_1DRuHnHgsMRlo4MtwuIAUe6u/confirm \ -u sk_test_4eC39HqLyjWDarjtT1zdp7dc: \ -d payment_method=pm_1EnPf7AfTbPYpBIFLxIc8SD9 \ -d return_url="https://shop.example.com/crtA6B28E1"
    # 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' payment_intent = Stripe::PaymentIntent.confirm( 'pi_1DRuHnHgsMRlo4MtwuIAUe6u', { payment_method: 'pm_1EnPf7AfTbPYpBIFLxIc8SD9', return_url: 'https://shop.example.com/crtA6B28E1', }, )
    # 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' payment_intent = stripe.PaymentIntent.modify( 'pi_1DRuHnHgsMRlo4MtwuIAUe6u', payment_method='pm_1EnPf7AfTbPYpBIFLxIc8SD9', return_url='https://shop.example.com/crtA6B28E1', )
    // 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'); $payment_intent = \Stripe\PaymentIntent::retrieve('pi_1DRuHnHgsMRlo4MtwuIAUe6u'); $payment_intent->confirm([ 'payment_method' => 'pm_1EnPf7AfTbPYpBIFLxIc8SD9', 'return_url' => 'https://shop.example.com/crtA6B28E1', ]);
    // 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"; PaymentIntent paymentIntent = PaymentIntent.retrieve("pi_1DRuHnHgsMRlo4MtwuIAUe6u"); PaymentIntentConfirmParams params = PaymentIntentConfirmParams.builder() .setPaymentMethod("pm_1EnPf7AfTbPYpBIFLxIc8SD9") .setReturnUrl("https://shop.example.com/crtA6B28E1") .build(); paymentIntent.confirm(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 paymentIntent = await stripe.paymentIntents.confirm( 'pi_1DRuHnHgsMRlo4MtwuIAUe6u', { payment_method: 'pm_1EnPf7AfTbPYpBIFLxIc8SD9', return_url: 'https://shop.example.com/crtA6B28E1', } );
    // 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.PaymentIntentConfirmParams{ PaymentMethod: "pm_1EnPf7AfTbPYpBIFLxIc8SD9", ReturnURL: "https://shop.example.com/crtA6B28E1", } intent, _ := paymentintent.Confirm("pi_1DRuHnHgsMRlo4MtwuIAUe6u", 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 service = new PaymentIntentService(); var options = new PaymentIntentConfirmOptions { PaymentMethod = "pm_1EnPf7AfTbPYpBIFLxIc8SD9", ReturnURL = "https://shop.example.com/crtA6B28E1", }; var intent = service.Confirm("pi_1DRuHnHgsMRlo4MtwuIAUe6u", options);

    At this point, Stripe checks if the selected bank is online and available to authenticate. If the bank is not available, Stripe returns the following error code and error message:

    error_code offline_bank
    error_message %{bank} is currently offline. Please try using a different bank.

    You should then advise your customer to either select a different bank or payment method to complete the transaction.

    Testing an offline bank You can test the offline_bank error above by creating a PaymentMethod in Step 2 using the following value, and then attaching it to the PaymentIntent as shown above.

    Parameter Value
    fpx[bank] test_offline_bank
    1. Confirming the PaymentIntent has a status of requires_action. The type for the next_action will be redirect_to_url.
    "next_action": { "type": "redirect_to_url", "redirect_to_url": { "url": "https://hooks.stripe.com/...", "return_url": "https://your-website.com/checkout/complete" } }
    1. Redirecting the customer to the URL provided in the next_action property.
    var action = intent.next_action; if (action && action.type === 'redirect_to_url') { window.location = action.redirect_to_url.url; }
    const action = intent.next_action; if (action && action.type === 'redirect_to_url') { window.location = action.redirect_to_url.url; }

    When the customer finishes the payment process, they’re sent to the return_url destination. The payment_intent and payment_intent_client_secret URL query parameters are included and you can pass through your own query parameters, as described above.

    Disputed payments

    FPX payments have a low risk of fraud or unrecognized payments because the customer must authenticate the payment with their bank. Therefore, there is no dispute process that can result in a chargeback and funds withdrawn from your Stripe account.

    Refunds

    FPX payments can be refunded up to 60 days after the original payment. Refunds for FPX payments are asynchronous and take approximately 1 week to complete. We will notify you of the final refund status using the charge.refund.updated webhook event. When a refund succeeds, the Refund object’s status transitions to succeeded. A refund can fail if the customer’s bank is unable to process it correctly (e.g. the bank account is closed). In the rare instance that a refund fails, the Refund object’s status will transition to failed and we will return the amount to your Stripe balance. You will then need to arrange an alternative way of providing your customer with a refund.

    FPX requirements

    Checkout page

    Note that you must adhere to the following requirements on your checkout page:

    Requirement Detail
    Display FPX logo. Refer to the FPX brand guideline here.
    Create FPX Buyer Bank’s selection via dropdown list. The name of the banks must follow the bank name in our bank reference table.
    Present the FPX Terms & Conditions standard wording and hyperlink. The FPX Terms and Conditions url is available here.

    Standard wording: By clicking on the Proceed button, you agree to FPX’s Terms and Conditions.

    Payment confirmation page

    The following information must be displayed on the payment confirmation page that your customer returns to after completing their bank authentication. You can access this information on the Charge object. You can retrieve the Charge object from the payment_intent.succeeded webhook event or directly from the PaymentIntent object.

    Information Source of information
    Transaction Date & Time created from the Charge object.
    Amount amount from the Charge object.
    Seller Order No. statement_descriptor from the Charge object.
    FPX Transaction ID payment_method_details[fpx][transaction_id] from the Charge object.
    Buyer Bank Name payment_method_details[fpx][bank] from the Charge object
    Transaction Status status from the Charge object

    Bank reference

    Bank name Value
    Affin Bank affin_bank
    Alliance Bank alliance_bank
    AmBank ambank
    Bank Islam bank_islam
    Bank Muamalat bank_muamalat
    Bank Rakyat bank_rakyat
    BSN bsn
    CIMB Clicks cimb
    Hong Leong Bank hong_leong_bank
    HSBC Bank hsbc
    KFH kfh
    Maybank2E maybank2e
    Maybank2U maybank2u
    OCBC Bank ocbc
    Public Bank public_bank
    RHB Bank rhb
    Standard Chartered standard_chartered
    UOB Bank uob

    Error codes

    These are the common error codes and corresponding recommended actions:

    Error Code Recommended Action
    invalid_amount FPX transactions must be greater than RM2 and less than RM30,000.
    invalid_bank The provided bank is not supported by FPX. Please use one of the options from the Bank Reference above.
    invalid_currency FPX only supports MYR transactions.
    missing_parameter A required parameter is missing. Please check the error_message for more information about which parameter is required.
    offline_bank The provided bank is currently offline. Please try using a different bank or payment method type.
    payment_method_processing_error_transient An unexpected error occurred preventing us from confirming the payment intent. The payment intent confirmation should be retried.

    Payouts and Transfers from your FPX balance

    Note that for compliance reasons, your FPX funds will settle to a separate fpx balance on your account. In practice, this means that you may receive two separate automatic payouts in a single day, one for your FPX funds and another for all other funds.

    You can explicitly create a payout or transfer (for Connect Platforms) from your fpx balance by specifying source_type=fpx in your API request, like so:

    curl https://api.stripe.com/v1/payouts \ -u sk_test_4eC39HqLyjWDarjtT1zdp7dc: \ -d amount=5000 \ -d currency=myr \ -d source_type=fpx
    # 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' payout = Stripe::Payout.create({ amount: 5000, currency: 'myr', source_type: 'fpx', })
    # 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' payout = stripe.Payout.create( amount=5000, currency='myr', source_type='fpx', )
    // 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'); $payout = \Stripe\Payout::create([ 'amount' => 5000, 'currency' => 'myr', 'source_type' => 'fpx', ]);
    // 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"; PayoutCreateParams params = PayoutCreateParams.builder() .setAmount(5000L) .setCurrency("myr") .setSourceType(PayoutCreateParams.SourceType.FPX) .build(); Payout payout = Payout.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 payout = await stripe.payouts.create({ amount: 5000, currency: 'myr', source_type: 'fpx', });
    // 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.PayoutParams{ Amount: stripe.Int64(5000), Currency: stripe.String(string(stripe.CurrencyMYR)), SourceType: stripe.String(string(stripe.PayoutSourceTypeFPX)), } payout, _ := payout.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 PayoutCreateOptions { Amount = 5000, Currency = "myr", SourceType = "fpx", }; var service = new PayoutService(); var payout = service.Create(options);

    You can also use the Retrieve balance API to see a breakdown of your available and pending Stripe balances by source_type.

    Overview

    The iOS SDK provides UI for accepting payments with FPX. You can integrate the iOS SDK with your app in two ways:

    • Use the Basic Integration, which includes prebuilt UI components for accepting both cards and bank accounts.
    • Create a custom flow using our FPX bank selection UI. This is useful if you have an existing payment UI.

    The example Basic Integration app uses STPPaymentContext to accept FPX.

    In order to accept FPX payments, you must first enable FPX in the Stripe Dashboard and accept the FPX terms of service. Doing so requires an activated Stripe Account.

    Basic Integration

    The Basic integration example app provides a demonstration of FPX support. To activate it, tap Settings at the top left, then enable FPX in the Additional Payment Options section.

    To integrate FPX into your own app using our UI, follow our Basic Integration guide. After completing the integration, enable FPX support by adding FPX to your STPPaymentConfiguration:

    let config = STPPaymentConfiguration.shared() config.additionalPaymentOptions = [.FPX]
    STPPaymentConfiguration *config = [STPPaymentConfiguration sharedPaymentConfiguration]; config.additionalPaymentOptions = STPPaymentOptionTypeFPX;

    Custom integration

    The Custom Integration example app provides a demonstration of FPX support. The source code is in FPXExampleViewController.m.

    To integrate FPX into your own custom payment UI, follow the steps below.

    Step 1: Create a PaymentIntent on the server

    A PaymentIntent is an object that represents your intent to collect payment from a customer, tracking the lifecycle of the payment process through each stage. When you create a PaymentIntent, you specify the amount of money to collect and the currency (FPX only supports MYR). You need the following values to create the PaymentIntent:

    Parameter Value
    payment_method_types fpx
    amount A positive integer in the smallest currency unit that represents the amount to charge the customer (e.g., 1099 for a RM10.99 payment). Stripe supports a minimum amount of RM2 and a maximum amount of RM30,000 per transaction.
    currency myr (FPX must always use Ringgit).
    curl https://api.stripe.com/v1/payment_intents \ -u sk_test_4eC39HqLyjWDarjtT1zdp7dc: \ -d "payment_method_types[]"=fpx \ -d amount=1099 \ -d currency=myr
    # 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' payment_intent = Stripe::PaymentIntent.create({ payment_method_types: ['fpx'], amount: 1099, currency: 'myr', })
    # 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' payment_intent = stripe.PaymentIntent.create( payment_method_types=['fpx'], amount=1099, currency='myr', )
    // 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'); $payment_intent = \Stripe\PaymentIntent::create([ 'payment_method_types' => ['fpx'], 'amount' => 1099, 'currency' => 'myr', ]);
    // 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"; PaymentIntentCreateParams params = PaymentIntentCreateParams.builder() .setAmount(1099L) .setCurrency("myr") .addPaymentMethodType("fpx") .build(); PaymentIntent paymentIntent = PaymentIntent.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 paymentIntent = await stripe.paymentIntents.create({ payment_method_types: ['fpx'], amount: 1099, currency: 'myr', });
    // 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.PaymentIntentParams{ Amount: stripe.Int64(1099), Currency: stripe.String(string(stripe.CurrencyMYR)), PaymentMethodTypes: stripe.StringSlice([]string{ "fpx", }), } intent, _ := paymentintent.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 service = new PaymentIntentService(); var options = new PaymentIntentCreateOptions { Amount = 1099, Currency = "myr", PaymentMethodTypes = new List<string> { "fpx", }, }; var intent = service.Create(options);

    We recommend creating a PaymentIntent as soon as you know the amount, ​​such as the beginning of the checkout process. You can update the amount if it changes later (for example, when calculating shipping fees and taxes):

    curl https://api.stripe.com/v1/payment_intents/pi_1DRuHnHgsMRlo4MtwuIAUe6u \ -u sk_test_4eC39HqLyjWDarjtT1zdp7dc: \ -d amount=1499
    # 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' payment_intent = Stripe::PaymentIntent.update( 'pi_1DRuHnHgsMRlo4MtwuIAUe6u', {amount: 1499}, )
    # 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' payment_intent = stripe.PaymentIntent.modify( 'pi_1DRuHnHgsMRlo4MtwuIAUe6u', amount=1499, )
    // 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'); $payment_intent = \Stripe\PaymentIntent::update( 'pi_1DRuHnHgsMRlo4MtwuIAUe6u', ['amount' => 1499] );
    // 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"; PaymentIntent paymentIntent = PaymentIntent.retrieve("pi_1DRuHnHgsMRlo4MtwuIAUe6u"); PaymentIntentUpdateParams params = PaymentIntentUpdateParams.builder() .setAmount(1499L) .build(); paymentIntent.update(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 paymentIntent = await stripe.paymentIntents.update( 'pi_1DRuHnHgsMRlo4MtwuIAUe6u', { amount: 1499, } );
    // 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.PaymentIntentParams{ Amount: stripe.Int64(1499), } intent, _ := paymentintent.Update("pi_1DRuHnHgsMRlo4MtwuIAUe6u", 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 service = new PaymentIntentService(); var options = new PaymentIntentUpdateOptions { Amount = 1499, }; var intent = service.Update("pi_1DRuHnHgsMRlo4MtwuIAUe6u", options);

    When created successfully, Stripe returns a PaymentIntent object with a status field that indicates the PaymentIntent object requires_payment_method at this stage.

    Step 2: Pass the PaymentIntent’s client secret to the client

    The PaymentIntent object contains a client secret, a unique key that you pass to your app to create a charge. This sample code assumes createPaymentIntent calls the server endpoint that you set up in Step 1 to create a PaymentIntent.

    MyAPIClient.createPaymentIntent() { result in switch (result) { case .success(let clientSecret): // Hold onto clientSecret for Step 4 case .failure(let error): // Handle the error } }
    [MyAPIClient createPaymentIntentWithCompletion:^(NSString *clientSecret, NSError *error) { if (error != nil) { // Handle the error } else { // Hold onto clientSecret for Step 4 } }];

    Each PaymentIntent typically correlates with a single “cart” or customer session in your application. Create the PaymentIntent during checkout and store its ID on the user’s cart in the data model of your application, retrieving it again as necessary.

    Use the client secret to complete the payment process with the amount specified on the PaymentIntent. Don’t log it, embed it in URLs, or expose it to anyone other than the customer.

    Step 3: Present the bank selection UI

    Initialize and present an STPBankSelectionViewController. This returns an STPPaymentMethodParams object based on the user’s selection, which can be used to confirm the PaymentIntent.

    func payWithFPX() { let vc = STPBankSelectionViewController.init(bankMethod: .FPX) vc.delegate = self self.navigationController?.pushViewController(vc, animated: true) } func bankSelectionViewController(_ bankViewController: STPBankSelectionViewController, didCreatePaymentMethodParams paymentMethodParams: STPPaymentMethodParams) { self.confirmPayment(paymentMethodParams: paymentMethodParams) }
    - (void)payWithFPX { STPBankSelectionViewController *vc = [[STPBankSelectionViewController alloc] initWithBankMethod:STPBankSelectionMethodFPX]; vc.delegate = self; [self.navigationController pushViewController:vc animated:YES]; } - (void)bankSelectionViewController:(nonnull STPBankSelectionViewController *)bankViewController didCreatePaymentMethodParams:(STPPaymentMethodParams *)paymentMethodParams { [self confirmPayment:paymentMethodParams]; }

    Step 4: Confirm the PaymentIntent

    To initiate payment collection, you must confirm that your customer intends to pay with the provided payment details.

    Add the FPX payment method to an STPPaymentIntentParams object initialized with the PaymentIntent’s client secret.

    let paymentIntentParams = STPPaymentIntentParams(clientSecret: clientSecret) paymentIntentParams.paymentMethodParams = paymentMethodParams
    STPPaymentIntentParams *paymentIntentParams = [[STPPaymentIntentParams alloc] initWithClientSecret:client_secret]; paymentIntentParams.paymentMethodParams = paymentMethodParams;

    If you want to send additional data, you can assign values to the relevant properties on the STPPaymentIntentParams object. Any parameters that you add are included in the request to Stripe. For example, to send an email to the customer on payment confirmation, use the receiptEmail property.

    To finish confirming the payment and automatically handle any additional required actions for authentication, pass the STPPaymentIntentParams object to the confirmPayment method on STPPaymentHandler sharedManager. STPPaymentHandler will present view controllers using the provided STPAuthenticationContext and guide the user through entering their bank account details.

    The following sample code assumes that MyCheckoutViewController should present the confirmation view controller and implements STPAuthenticationContext.

    let paymentManager = STPPaymentHandler.shared() paymentManager.confirmPayment(withParams: paymentIntentParams, authenticationContext: self, completion: { (status, paymentIntent, error) in switch (status) { case .failed: // Handle error case .canceled: // Handle cancel case .succeeded: // PaymentIntent is confirmed } })
    [[STPPaymentHandler sharedHandler] confirmPayment:paymentIntentParams withAuthenticationContext:self returnURL:nil completion:^(STPPaymentHandlerActionStatus status, STPPaymentIntent * paymentIntent, NSError * error) { switch (status) { case STPPaymentHandlerActionStatusFailed: // Handle the error by inspecting paymentIntent.status break; case STPPaymentHandlerActionStatusCanceled: // Handle cancel break; case STPPaymentHandlerActionStatusSucceeded: // PaymentIntent is confirmed break; } }];

    Step 5: Asynchronously fulfill the customer’s order

    You can use the status returned by STPPaymentHandler to provide immediate feedback to your customers when the payment completes on the client. However, don’t attempt to handle order fulfillment on the client side because customers may leave the app after payment is complete but before the fulfillment process initiates. Instead, make sure you can handle asynchronous events and receive notifications so that you can fulfill orders when payment succeeds.

    You can fulfill an order in several ways:

    When you attempt to collect payment from a customer, the PaymentIntent creates a Charge. You can inspect the PaymentIntent’s charges.data property to obtain the charge:

    # 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' intent = Stripe::PaymentIntent.retrieve('{{PAYMENT_INTENT_ID}}') charges = intent.charges.data
    # 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' intent = stripe.PaymentIntent.retrieve('{{PAYMENT_INTENT_ID}}') charges = intent.charges.data
    // 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'); $intent = \Stripe\PaymentIntent::retrieve('{{PAYMENT_INTENT_ID}}'); $charges = $intent->charges->data;
    // 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"; PaymentIntent intent = PaymentIntent.retrieve("{{PAYMENT_INTENT_ID}}"); List<Charge> charges = intent.getCharges().getData();
    // 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'); (async () => { const intent = await stripe.paymentIntents.retrieve('{{PAYMENT_INTENT_ID}}'); const charges = intent.charges.data; })();
    // 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" intent, err := paymentintent.Get("{{PAYMENT_INTENT_ID}}", nil) charges := intent.Charges.Data
    // 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 service = new PaymentIntentService(); var intent = service.Get("{{PAYMENT_INTENT_ID}}"); var charges = intent.Charges.Data;

    Testing an offline bank You can test the offline_bank error by creating an STPPaymentMethodParams with the following code, and then using it to confirm the PaymentIntent as shown above.

    let fpx = STPPaymentMethodFPXParams() fpx.rawBankString = "test_offline_bank" let paymentMethodParams = STPPaymentMethodParams.init(fpx: fpx, billingDetails: nil, metadata: nil)
    STPPaymentMethodFPXParams *fpx = [[STPPaymentMethodFPXParams alloc] init]; fpx.rawBankString = @"test_offline_bank"; STPPaymentMethodParams *paymentMethodParams = [STPPaymentMethodParams paramsWithFPX:fpx billingDetails:nil metadata:nil];

    FPX requirements

    Checkout page

    Note that you must adhere to the following requirements on your checkout page:

    Requirement Detail
    Display FPX logo. Refer to the FPX brand guideline here.
    Create FPX Buyer Bank’s selection via dropdown list. The name of the banks must follow the bank name in our bank reference table.
    Present the FPX Terms & Conditions standard wording and hyperlink. The FPX Terms and Conditions url is available here.

    Standard wording: By clicking on the Proceed button, you agree to FPX’s Terms and Conditions.

    Payment confirmation page

    The following information must be displayed on the payment confirmation page that your customer returns to after completing their bank authentication. You can access this information on the Charge object. You can retrieve the Charge object from the payment_intent.succeeded webhook event or directly from the PaymentIntent object.

    Information Source of information
    Transaction Date & Time created from the Charge object.
    Amount amount from the Charge object.
    Seller Order No. statement_descriptor from the Charge object.
    FPX Transaction ID payment_method_details[fpx][transaction_id] from the Charge object.
    Buyer Bank Name payment_method_details[fpx][bank] from the Charge object
    Transaction Status status from the Charge object

    Bank reference

    Bank name Value
    Affin Bank affin_bank
    Alliance Bank alliance_bank
    AmBank ambank
    Bank Islam bank_islam
    Bank Muamalat bank_muamalat
    Bank Rakyat bank_rakyat
    BSN bsn
    CIMB Clicks cimb
    Hong Leong Bank hong_leong_bank
    HSBC Bank hsbc
    KFH kfh
    Maybank2E maybank2e
    Maybank2U maybank2u
    OCBC Bank ocbc
    Public Bank public_bank
    RHB Bank rhb
    Standard Chartered standard_chartered
    UOB Bank uob

    Error codes

    These are the common error codes and corresponding recommended actions:

    Error Code Recommended Action
    invalid_amount FPX transactions must be greater than RM2 and less than RM30,000.
    invalid_bank The provided bank is not supported by FPX. Please use one of the options from the Bank Reference above.
    invalid_currency FPX only supports MYR transactions.
    missing_parameter A required parameter is missing. Please check the error_message for more information about which parameter is required.
    offline_bank The provided bank is currently offline. Please try using a different bank or payment method type.
    payment_method_processing_error_transient An unexpected error occurred preventing us from confirming the payment intent. The payment intent confirmation should be retried.

    Overview

    The Android SDK provides a UI for accepting payments with FPX. You can integrate the Android SDK with your app in two ways:

    • Use the Basic Integration, which includes prebuilt UI components for accepting both cards and bank accounts.
    • Use our FPX bank selection UI to create a custom flow. This option is useful if you have an existing payment UI.

    The example Standard Integration app uses PaymentSession to accept FPX.

    In order to accept FPX payments, you must first enable FPX in the Stripe Dashboard and accept the FPX terms of service. Doing so requires an activated Stripe Account.

    Basic integration

    The example app provides a demonstration of FPX support. ​​You can access the FPX demonstration by launching the app and tapping FPX Payment Example.

    To integrate FPX into your own app using our UI, follow our Basic Integration guide. After you’ve completed the integration, enable FPX support by adding FPX to your PaymentSessionConfig:

    public class FpxPaymentActivity extends Activity { private PaymentSession paymentSession; private PaymentSession.PaymentSessionListener paymentSessionListener; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); paymentSession = new PaymentSession( this, new PaymentSessionConfig.Builder() // Optionally specify the `PaymentMethod.Type` // values to use. Default is `PaymentMethod.Type.Card`. .setPaymentMethodTypes(Arrays.asList( PaymentMethod.Type.Fpx, PaymentMethod.Type.Card )) .build() ); paymentSession.init(paymentSessionListener); } private void launchPaymentMethodPicker() { paymentSession.presentPaymentMethodSelection(); } }
    class FpxPaymentActivity : Activity() { private lateinit var paymentSession: PaymentSession private lateinit var paymentSessionListener: PaymentSession.PaymentSessionListener override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val paymentSession = PaymentSession( this, PaymentSessionConfig.Builder() // Optionally specify the `PaymentMethod.Type` // values to use. Default is `PaymentMethod.Type.Card`. .setPaymentMethodTypes(listOf( PaymentMethod.Type.Fpx, PaymentMethod.Type.Card )) .build() ) paymentSession.init(paymentSessionListener) } private fun launchPaymentMethodPicker() { paymentSession.presentPaymentMethodSelection() } }

    Custom integration

    The Stripe Android SDK examples app provides a demonstration of FPX support. The source code is in FpxPaymentActivity.java.

    To integrate FPX into your own custom payment UI, follow the steps below.

    Step 1: Create a PaymentIntent on the server

    A PaymentIntent is an object that represents your intent to collect payment from a customer, tracking the lifecycle of the payment process through each stage. When you create a PaymentIntent, you specify the amount of money to collect and the currency (FPX only supports MYR). You need the following values to create the PaymentIntent:

    Parameter Value
    payment_method_types fpx
    amount A positive integer in the smallest currency unit that represents the amount to charge the customer (e.g., 1099 for a RM10.99 payment). Stripe supports a minimum amount of RM2 and a maximum amount of RM30,000 per transaction.
    currency myr (FPX must always use Ringgit).
    curl https://api.stripe.com/v1/payment_intents \ -u sk_test_4eC39HqLyjWDarjtT1zdp7dc: \ -d "payment_method_types[]"=fpx \ -d amount=1099 \ -d currency=myr
    # 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' payment_intent = Stripe::PaymentIntent.create({ payment_method_types: ['fpx'], amount: 1099, currency: 'myr', })
    # 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' payment_intent = stripe.PaymentIntent.create( payment_method_types=['fpx'], amount=1099, currency='myr', )
    // 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'); $payment_intent = \Stripe\PaymentIntent::create([ 'payment_method_types' => ['fpx'], 'amount' => 1099, 'currency' => 'myr', ]);
    // 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"; PaymentIntentCreateParams params = PaymentIntentCreateParams.builder() .setAmount(1099L) .setCurrency("myr") .addPaymentMethodType("fpx") .build(); PaymentIntent paymentIntent = PaymentIntent.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 paymentIntent = await stripe.paymentIntents.create({ payment_method_types: ['fpx'], amount: 1099, currency: 'myr', });
    // 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.PaymentIntentParams{ Amount: stripe.Int64(1099), Currency: stripe.String(string(stripe.CurrencyMYR)), PaymentMethodTypes: stripe.StringSlice([]string{ "fpx", }), } intent, _ := paymentintent.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 service = new PaymentIntentService(); var options = new PaymentIntentCreateOptions { Amount = 1099, Currency = "myr", PaymentMethodTypes = new List<string> { "fpx", }, }; var intent = service.Create(options);

    We recommend creating a PaymentIntent as soon as you know the amount, ​​such as the beginning of the checkout process. You can update the amount if it changes later (for example, when calculating shipping fees and taxes):

    curl https://api.stripe.com/v1/payment_intents/pi_1DRuHnHgsMRlo4MtwuIAUe6u \ -u sk_test_4eC39HqLyjWDarjtT1zdp7dc: \ -d amount=1499
    # 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' payment_intent = Stripe::PaymentIntent.update( 'pi_1DRuHnHgsMRlo4MtwuIAUe6u', {amount: 1499}, )
    # 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' payment_intent = stripe.PaymentIntent.modify( 'pi_1DRuHnHgsMRlo4MtwuIAUe6u', amount=1499, )
    // 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'); $payment_intent = \Stripe\PaymentIntent::update( 'pi_1DRuHnHgsMRlo4MtwuIAUe6u', ['amount' => 1499] );
    // 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"; PaymentIntent paymentIntent = PaymentIntent.retrieve("pi_1DRuHnHgsMRlo4MtwuIAUe6u"); PaymentIntentUpdateParams params = PaymentIntentUpdateParams.builder() .setAmount(1499L) .build(); paymentIntent.update(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 paymentIntent = await stripe.paymentIntents.update( 'pi_1DRuHnHgsMRlo4MtwuIAUe6u', { amount: 1499, } );
    // 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.PaymentIntentParams{ Amount: stripe.Int64(1499), } intent, _ := paymentintent.Update("pi_1DRuHnHgsMRlo4MtwuIAUe6u", 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 service = new PaymentIntentService(); var options = new PaymentIntentUpdateOptions { Amount = 1499, }; var intent = service.Update("pi_1DRuHnHgsMRlo4MtwuIAUe6u", options);

    When created successfully, Stripe returns a PaymentIntent object with a status field that indicates the PaymentIntent object requires_payment_method at this stage.

    Step 2: Pass the PaymentIntent’s client secret to the client

    The PaymentIntent object contains a client secret, a unique key that you pass to your app to create a charge. This sample code assumes createPaymentIntent calls the server endpoint that you set up in Step 1 to create a PaymentIntent.

    public class FpxPaymentActivity extends Activity { private void createPaymentIntent() { myBackendApiClient.createPaymentIntent(params, new ApiResultCallback<String>() { @Override public void onSuccess(@NonNull String clientSecret) { // Hold onto clientSecret for Step 4 } @Override public void onError(@NonNull Exception e) { // handle error } }); } }
    class FpxPaymentActivity : Activity() { private fun createPaymentIntent() { myBackendApiClient.createPaymentIntent(params, object: ApiResultCallback<String> { override fun onSuccess(clientSecret: String) { retrievePaymentIntent(clientSecret) } override fun onError(e: Exception) { // handle error } }) } }

    Each PaymentIntent typically correlates with a single “cart” or customer session in your application. Create the PaymentIntent during checkout and store its ID on the user’s cart in the data model of your application, retrieving it again as necessary.

    Use the client secret to complete the payment process with the amount specified on the PaymentIntent. Don’t log it, embed it in URLs, or expose it to anyone other than the customer.

    Step 3: Present the bank selection UI

    Use AddPaymentMethodActivityStarter to present AddPaymentMethodActivity. If the user created a Payment Method, you can use AddPaymentMethodActivityStarter.Result.fromIntent(data) to obtain it in your Activity#onActivityResult(). Use the PaymentMethod to confirm the PaymentIntent.

    public class FpxPaymentActivity extends Activity { @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_fpx_payment); setTitle(R.string.fpx_payment_example); PaymentConfiguration.init(this, Settings.PUBLISHABLE_KEY); findViewById(R.id.btn_select_payment_method) .setOnClickListener(view -> launchAddPaymentMethod()); } private void launchAddPaymentMethod() { new AddPaymentMethodActivityStarter(this) .startForResult(new AddPaymentMethodActivityStarter.Args.Builder() .setPaymentMethodType(PaymentMethod.Type.Fpx) .build() ); } @Override protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { super.onActivityResult(requestCode, resultCode, data); final AddPaymentMethodActivityStarter.Result result = AddPaymentMethodActivityStarter.Result.fromIntent(data); if (result != null) { onPaymentMethodResult(result.getPaymentMethod()); } } private void onPaymentMethodResult(@NonNull PaymentMethod paymentMethod) { // confirm the PaymentIntent with the PaymentMethod } }
    class FpxPaymentActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_fpx_payment) setTitle(R.string.fpx_payment_example) PaymentConfiguration.init(this, Settings.PUBLISHABLE_KEY) findViewById<View>(R.id.btn_select_payment_method) .setOnClickListener { launchAddPaymentMethod() } } private fun launchAddPaymentMethod() { AddPaymentMethodActivityStarter(this) .startForResult(AddPaymentMethodActivityStarter.Args.Builder() .setPaymentMethodType(PaymentMethod.Type.Fpx) .build() ) } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) val result = AddPaymentMethodActivityStarter.Result.fromIntent(data) if (result != null) { onPaymentMethodResult(result.paymentMethod) } } private fun onPaymentMethodResult(paymentMethod: PaymentMethod) { // confirm the PaymentIntent with the PaymentMethod } }

    Step 4: Confirm the PaymentIntent

    To initiate payment collection, you must confirm that your customer intends to pay with the provided payment details.

    Create a ConfirmPaymentIntentParams object with the FPX payment method’s ID and the PaymentIntent’s client secret and call Stripe#confirmPayment().

    stripe.confirmPayment(this, ConfirmPaymentIntentParams.createWithPaymentMethodId( paymentMethodId, paymentIntentClientSecret ));
    stripe.confirmPayment(this, ConfirmPaymentIntentParams.createWithPaymentMethodId( paymentMethodId, paymentIntentClientSecret ))

    To confirm the payment and automatically handle authentication, call Stripe#confirmPayment() with the ConfirmPaymentIntentParams object. Stripe will present payment authentication screens and guide the user to enter their bank account details.

    public class FpxPaymentActivity extends Activity { @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) { // If authentication succeeded, the PaymentIntent will have // user actions resolved; otherwise, handle the PaymentIntent // status as appropriate (e.g. the customer may need to choose // a new payment method) final PaymentIntent.Status status = result.getIntent().getStatus(); if (PaymentIntent.Status.RequiresPaymentMethod == status) { // attempt authentication again or ask for a new Payment Method } else if (PaymentIntent.Status.RequiresConfirmation == status) { // handle confirming the PaymentIntent again on the server } else if (PaymentIntent.Status.Succeeded == status) { // capture succeeded } } @Override public void onError(@NonNull Exception e) { // handle error } }); } }
    class FpxPaymentActivity : AppCompatActivity() { 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) { // If authentication succeeded, the PaymentIntent will have // user actions resolved; otherwise, handle the PaymentIntent // status as appropriate (e.g. the customer may need to choose // a new payment method) val status = result.intent.status if (PaymentIntent.Status.RequiresPaymentMethod == status) { // attempt authentication again or ask for a new Payment Method } else if (PaymentIntent.Status.RequiresConfirmation == status) { // handle confirming the PaymentIntent again on the server } else if (PaymentIntent.Status.Succeeded == status) { // capture succeeded } } override fun onError(e: Exception) { // handle error } }) } }

    Step 5: Asynchronously fulfill the customer’s order

    You can use the status returned in Step 4 to provide immediate feedback to your customers when the payment completes on the client. ​​However, don’t attempt to handle order fulfillment on the client side because customers may leave the app after payment is complete but before the fulfillment process initiates. Instead, make sure you can handle asynchronous events and receive notifications so that you can fulfill orders when payment succeeds.

    ​​You can fulfill an order in several ways:

    When you attempt to collect payment from a customer, the PaymentIntent creates a Charge. You can inspect the PaymentIntent’s charges.data property to obtain the charge:

    # 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' intent = Stripe::PaymentIntent.retrieve('{{PAYMENT_INTENT_ID}}') charges = intent.charges.data
    # 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' intent = stripe.PaymentIntent.retrieve('{{PAYMENT_INTENT_ID}}') charges = intent.charges.data
    // 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'); $intent = \Stripe\PaymentIntent::retrieve('{{PAYMENT_INTENT_ID}}'); $charges = $intent->charges->data;
    // 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"; PaymentIntent intent = PaymentIntent.retrieve("{{PAYMENT_INTENT_ID}}"); List<Charge> charges = intent.getCharges().getData();
    // 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'); (async () => { const intent = await stripe.paymentIntents.retrieve('{{PAYMENT_INTENT_ID}}'); const charges = intent.charges.data; })();
    // 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" intent, err := paymentintent.Get("{{PAYMENT_INTENT_ID}}", nil) charges := intent.Charges.Data
    // 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 service = new PaymentIntentService(); var intent = service.Get("{{PAYMENT_INTENT_ID}}"); var charges = intent.Charges.Data;

    Testing an offline bank

    To test the offline_bank error, you can create a PaymentMethodCreateParams with the following code, and then use it to confirm the PaymentIntent as shown above.

    final PaymentMethodCreateParams paymentMethodParams = PaymentMethodCreateParams.create( new PaymentMethodCreateParams.Fpx.Builder() .setBank("test_offline_bank") .build() );
    val paymentMethodParams = PaymentMethodCreateParams.create( PaymentMethodCreateParams.Fpx.Builder() .setBank("test_offline_bank") .build() )

    FPX requirements

    Checkout page

    Note that you must adhere to the following requirements on your checkout page:

    Requirement Detail
    Display FPX logo. Refer to the FPX brand guideline here.
    Create FPX Buyer Bank’s selection via dropdown list. The name of the banks must follow the bank name in our bank reference table.
    Present the FPX Terms & Conditions standard wording and hyperlink. The FPX Terms and Conditions url is available here.

    Standard wording: By clicking on the Proceed button, you agree to FPX’s Terms and Conditions.

    Payment confirmation page

    The following information must be displayed on the payment confirmation page that your customer returns to after completing their bank authentication. You can access this information on the Charge object. You can retrieve the Charge object from the payment_intent.succeeded webhook event or directly from the PaymentIntent object.

    Information Source of information
    Transaction Date & Time created from the Charge object.
    Amount amount from the Charge object.
    Seller Order No. statement_descriptor from the Charge object.
    FPX Transaction ID payment_method_details[fpx][transaction_id] from the Charge object.
    Buyer Bank Name payment_method_details[fpx][bank] from the Charge object
    Transaction Status status from the Charge object

    Bank reference

    Bank name Value
    Affin Bank affin_bank
    Alliance Bank alliance_bank
    AmBank ambank
    Bank Islam bank_islam
    Bank Muamalat bank_muamalat
    Bank Rakyat bank_rakyat
    BSN bsn
    CIMB Clicks cimb
    Hong Leong Bank hong_leong_bank
    HSBC Bank hsbc
    KFH kfh
    Maybank2E maybank2e
    Maybank2U maybank2u
    OCBC Bank ocbc
    Public Bank public_bank
    RHB Bank rhb
    Standard Chartered standard_chartered
    UOB Bank uob

    Error codes

    These are the common error codes and corresponding recommended actions:

    Error Code Recommended Action
    invalid_amount FPX transactions must be greater than RM2 and less than RM30,000.
    invalid_bank The provided bank is not supported by FPX. Please use one of the options from the Bank Reference above.
    invalid_currency FPX only supports MYR transactions.
    missing_parameter A required parameter is missing. Please check the error_message for more information about which parameter is required.
    offline_bank The provided bank is currently offline. Please try using a different bank or payment method type.
    payment_method_processing_error_transient An unexpected error occurred preventing us from confirming the payment intent. The payment intent confirmation should be retried.

    Was this page helpful?

    Feedback about this page?

    Thank you for helping improve Stripe's documentation. If you need help or have any questions, please consider contacting support.

    On this page