HTML Checkout

Learn how to use HTML Checkout to collect payments on your website.

Our HTML checkout option is like Flutterwave Inline, but done entirely in HTML. Simply create a standard HTML form with the payment details. When the customer submits the form, they'll be redirected to our payment page, where they can complete the payment.

An Example

Here's an example of how you'd implement HTML checkout:

ℹ️

Try it Out

You can try out the payment above. Use the card number 4187427415564246with CVV 828 and expiry 09/32, or grab a test card or bank account from our Testing helpers page and try making the payment. It works!

Let's take a closer look at what this code is doing.

First, we create a regular HTML form. The form must use the POST method, and the action must point to Flutterwave's checkout page.

<form
	method="POST"
	action="https://checkout.flutterwave.com/v3/hosted/pay"
></form>

Next up is the payment button. This is the button the customer clicks after they've reviewed their order and are ready to pay you. Make sure it's inside the form and set it to type="submit" so the form submits when it's clicked.

<form method="POST" action="https://checkout.flutterwave.com/v3/hosted/pay">
	<button type="submit">Pay Now</button>
</form>

Finally, we add hidden input fields to the form containing the payment options. These payment options are the same values used in the Inline and Standard flows, converted into form fields. Object fields are referenced with square brackets.

For example, a payload like this:

{
	"public_key": "FLWPUBK_TEST-SANDBOXDEMOKEY-X",
	"tx_ref": "bitethtx-019203",
	"amount": 3400,
	"currency": "NGN",
	"payment_options": "card, ussd",
	"redirect_url": "https://demoredirect.localhost.me/",
	"meta": {
		"token": 54
	},
	"customer": {
		"name": "Jesse Pinkman",
		"email": "[email protected]"
	}
}

Becomes this:

<input type="hidden" name="public_key" value="FLWPUBK_TEST-SANDBOXDEMOKEY-X" />
<input type="hidden" name="tx_ref" value="bitethtx-019203" />
<input type="hidden" name="amount" value="3400" />
<input type="hidden" name="currency" value="NGN" />
<input
	type="hidden"
	name="redirect_url"
	value="https://demoredirect.localhost.me/"
/>
<input type="hidden" name="meta[token]" value="54" />
<input type="hidden" name="customer[name]" value="Jesse Pinkman" />
<input
	type="hidden"
	name="customer[email]"
	value="[email protected]"
/>

That's it for HTML checkout. When the customer submits the form, they'll be redirected to the payment page.

After Payment

Four things will happen when payment is made (successful or cancelled):

  1. We'll redirect to your redirect_url with status and tx_ref query parameters after payment is complete.
  2. We'll send you a webhook if you have that enabled.
  3. We'll send an email receipt to your customer if the payment was successful (unless you've disabled that).
  4. We'll send you an email notification.

When we redirect to you, the status query parameter will be one of the following:

  • "cancelled": If the customer clicks "Cancel Payment".
  • "successful": If the payment was successful. There'll also be a transaction_id query parameter containing the Flutterwave transaction ID.

On your server, you should handle the redirect and always verify the final state of the transaction.

🚧

Transaction Verification must be Server-Side

Transaction verification should always be done on the server, as it makes use of your secret key, which should never be exposed publicly.

Here's what transaction verification could look like in a Node.js app with our backend SDK:

app.get('/payment-callback', async (req, res) => {
	if (req.query.status === 'successful') {
		const transactionDetails = await Transaction.find({
			ref: req.query.tx_ref,
		});
		const response = await flw.Transaction.verify({
			id: req.query.transaction_id,
		});
		if (
			response.data.status === 'successful' &&
			response.data.amount === transactionDetails.amount &&
			response.data.currency === 'NGN'
		) {
			// Success! Confirm the customer's payment
		} else {
			// Inform the customer their payment was unsuccessful
		}
	}
});

What if the Payment Fails?

If the payment attempt fails (for instance, due to insufficient funds), you don't need to do anything. We'll keep the payment page open so the customer can try again until the payment succeeds or they choose to cancel.

If you have webhooks enabled, we'll send you a notification for each failed payment attempt. This is useful in case you want to later reach out to customers who had issues paying.

Handling Payment Retries and Timeout on Checkout

Flutterwave allows you to configure retries and timeouts on checkout to improve your customers' experience further. Timeouts help you limit the completion time for each payment. Once the timeout period is completed, the payment window is closed, and the user is redirected to the specified URL (redirect_url). Uncompleted transactions are cancelled and marked as failed.

Timeouts can be set to a maximum value of 1440 minutes.

Additionally, You can limit how many attempts users make on checkout. By setting max_retry, the user is prevented from attempting transactions unnecessarily at checkout. When making a payment, the transaction would be cancelled and marked as failed if the user attempts to reach max retry.

Using these configurations can help you improve security on checkout by limiting malicious users' payment attempts. For example, If timeout and retry for a transaction are set to 10 mins and five (5) attempts respectively, The transaction fails automatically if the user makes more than five attempts or spends longer than 10 mins to complete the transaction.

<input type="hidden" name="public_key" value="FLWPUBK_TEST-SANDBOXDEMOKEY-X" />
<input type="hidden" name="tx_ref" value="bitethtx-019203" />
<input type="hidden" name="amount" value="3400" />
<input type="hidden" name="currency" value="NGN" />
<input
	type="hidden"
	name="redirect_url"
	value="https://demoredirect.localhost.me/"
/>
<input type="hidden" name="meta[token]" value="54" />
<input type="hidden" name="customer[name]" value="Jesse Pinkman" />
<input
	type="hidden"
	name="customer[email]"
	value="[email protected]"
/>
<input type="hidden" name="configurations[session_duration]" value="10" />
#Session timeout in minutes (maxValue: 1440 minutes)
<input type="hidden" name="configurations[max_retry_attempt]" value="5" /> #Max
retry (int)

Next Steps

All done! If you get stuck, you can always ask for help on our support page.

And if HTML checkout isn't your cup of tea, we've got you covered. Check out our payments overview to see other options.