FPX Bank Element Quickstart

Collect FPX bank information in Malaysia using Elements, our prebuilt UI components.

The FPX bank element is a prebuilt, customizable form for redirecting your customers to their online banking portal for payment authentication.

It’s designed to help you optimize conversion with the following, out-of-the-box features:

  • Bank logos and names for banks participating in FPX
  • Cross-browser support and mobile optimization
  • Automatic localization into English or Malay based on your customer’s browser language settings

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

Building a custom payment form with Elements to create an FPX payment requires four steps:

  1. Create a PaymentIntent on the server
  2. Set up Stripe Elements
  3. Create your payment form
  4. Submit FPX payment from the client to Stripe

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: Set up Stripe Elements

Include the following script in the head section on every page on your site. This script must always load directly from js.stripe.com to ensure the most up to date version of Stripe.js is available to your application. You can’t include the script in a bundle or host a copy of it yourself.

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

Next, create an instance of the Stripe object, providing your publishable API key as the first parameter, and an options object with betas key as shown below:

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

We've placed a random API key in the code. Replace it with your actual publishable API key to test this code through your Stripe account.

When you’re ready to accept live FPX payments, replace the test key with your live key. Learn more about how the keys play into test and live modes.

Step 3: Create your payment form

To collect an FPX payment from your customers, Elements creates a UI component for you that is hosted by Stripe. It is then placed into your payment form, rather than you creating it directly.

To determine where to insert these components, create empty DOM elements (containers) with unique IDs within your payment form. We recommend placing your container within a <label> or next to a <label> with a for attribute that matches the unique id of the Element container. By doing so, the Element automatically gains focus when the customer clicks on the corresponding label.

The PaymentIntent object created in Step 1 contains a client secret, a unique key that you need to pass to Stripe.js on the client side in order to confirm a PaymentIntent. If your application uses server-side rendering, use your template framework to embed the client secret in the payment form using a data attribute or a hidden HTML element.

<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>
get '/checkout' do @intent = # ... Fetch or create the PaymentIntent erb :checkout end
<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="{{ client_secret }}"> Submit Payment </button> <!-- Used to display form errors. --> <div id="error-message" role="alert"></div> </form>
@app.route('/checkout') def checkout(): intent = # ... Fetch or create the PaymentIntent return render_template('checkout.html', client_secret=intent.client_secret)
<?php $intent = # ... Fetch or create the PaymentIntent; ?> <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>
<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="{{ client_secret }}"> Submit Payment </button> <!-- Used to display form errors. --> <div id="error-message" role="alert"></div> </form>
const express = require('express'); const expressHandlebars = require('express-handlebars'); const app = express(); app.engine('.hbs', expressHandlebars({ extname: '.hbs' })); app.set('view engine', '.hbs'); app.set('views', './views'); app.get('/checkout', async (req, res) => { const intent = // ... Fetch or create the PaymentIntent res.render('checkout', { client_secret: intent.client_secret }); }); app.listen(3000, () => { console.log('Running on port 3000') });

When the form above has loaded, create an instance of a 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');

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

The FPX Bank Element emits the customer’s selected bank as it changes. If you would like to perform additional logic with the bank value (such as requiring the field for form validation, for example), you can listen to the change event:

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

The change event also contains a few other helpful properties:

elementType
string
fpxBank, the name of the Element.
empty
Boolean
true if the value is empty.
complete
Boolean
true if the value has been picked by the customer.
complete can be used to progressively disclose the next parts of your form or to enable form submission.
value
string
The FPX bank that the customer picked from the Element. The full list of banks can be found in the FPX guide.

Step 4: Submit FPX payment from the client to Stripe

To complete the payment, retrieve the client secret from the payment form created in Step 3. After obtaining the client secret from the data attribute, use stripe.confirmFpxPayment to complete the payment:

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 = 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 = error.message; } });

Refer to the stripe.confirmFpxPayment reference for usage details.

stripe.confirmFpxPayment confirms the PaymentIntent with the payment method details from the FPX Element and initiates the redirect to the customer’s banking site to complete the payment. If an error occurs in completing the payment, a Promise is returned which resolves with:

  • result.error, there was an error in completing the payment. This includes client-side validation errors. Refer to the API reference for all possible errors and to FPX guide for FPX specific errors.

Optional Manually handling FPX authorization with redirect

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.
  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 are sent to the return_url destination. The payment_intent and payment_intent_client_secret URL query parameters are included and you may pass through your own query parameters, as described above.

Was this page helpful?
Questions? Contact us.
Developer tutorials on YouTube.