Embed checkout in your React Native mobile app
Embedding checkout in your React Native mobile app consists of multiple steps.
To support Apple Pay on Embedded Checkout, follow these instructions.
React Native app steps
Never store or use your Peach Payments credentials from your mobile app. Doing so is a security risk since all your mobile app users gain access to your credentials and can perform actions on your behalf.
To show a WebView containing Embedded Checkout, you must install the react-native-webview package.
Using the
source={{html}}property of WebView does not work as expected and can cause issues with Embedded Checkout. For Embedded Checkout to work as expected, ensure that your backend serves an HTTPS page with Embedded Checkout on it. It cannot be a localhost address to avoid issues with payment methods.
The following React Native example component calls your API to begin the Checkout process. It assumes that your API accepts a JSON object that contains an array of basket items.
import { SafeAreaView, Platform, StyleSheet } from "react-native";
import { useEffect, useState, useRef } from "react";
import { WebView } from "react-native-webview";
const yourApi = "https://<yourAPI>";
export function PaymentScreen() {
const [url, setUrl] = useState("");
const webViewRef = useRef(null);
useEffect(async () => {
const response = await fetch(yourApi, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
basket: [
{
id: "item-1",
quantity: 1,
},
],
}),
});
if (!response.ok) {
console.log("Error");
return;
}
const data = await response.json();
setUrl(data.url);
}, []);
if (!url) {
return <LoadingScreen />;
}
return (
<SafeAreaView style={{ flex: 1 }}>
// The following WebView settings are experimental but are designed to maintain checkout state, especially during 3-D Secure authentication and when the app is minimised or put in the background. They should improve your transaction processing success rates if you have trouble with 3-D Secure and the minimising of apps. If these settings cause issues, turn them (except "source={{ uri: url }}" and "originWhitelist={["*"]}") off.
<WebView
ref={webViewRef}
source={{ uri: url }}
style={styles.webView}
onMessage={onMessage}
originWhitelist={["*"]}
javaScriptEnabled={true}
domStorageEnabled={true}
cacheEnabled={true}
// Critical settings for state persistence
incognito={false}
sharedCookiesEnabled={true}
thirdPartyCookiesEnabled={true}
// Android-specific persistence options
androidLayerType={Platform.OS === "android" ? "hardware" : undefined}
// Prevents WebView re-renders and preserves state
key="checkout-webview"
// iOS-specific for persistence
allowsBackForwardNavigationGestures={false}
applicationNameForUserAgent="PeachPaymentsApp"
// Error handling
onError={(error) => {
console.error("WebView error:", error.nativeEvent.description);
}}
/>
</SafeAreaView>
);
}The following WebView settings are experimental but are designed to maintain checkout state, especially during 3-D Secure authentication and when the app is minimised or put in the background. They should improve your transaction processing success rates if you have trouble with 3-D Secure and the minimising of apps. If these settings cause issues, turn them off.
domStorageEnabled={true}: Required forlocalStorageandsessionStorageto work. Without this, session data, form inputs, and user preferences are lost when the WebView refreshes.cacheEnabled={true}: Preserves downloaded resources and HTTP responses, preventing complete reloads that reset the application state.incognito={false}: Ensures the WebView shares the same storage context as other WebViews in the app, maintaining consistent state across sessions.sharedCookiesEnabled={true}: Critical for authentication persistence. Without it, login sessions are lost when navigating between screens.thirdPartyCookiesEnabled={true}: Payment processors and authentication services often use third-party cookies to maintain session state during transactions.key="checkout-webview": Provides a stable key to prevent React's reconciliation process from recreating the WebView component during re-renders, which would cause complete state loss.androidLayerType={"hardware"}: Prevents the WebView from being recreated during certain Android lifecycle events, helping maintain state during app minimisation.allowsBackForwardNavigationGestures={false}: Prevents iOS swipe gestures that could interfere with the checkout flow.applicationNameForUserAgent="PeachPaymentsApp": Sets a custom user agent to identify your app in payment processing flows.
Backend steps
Your backend must call Checkout to create the Embedded Checkout instance and provide an HTML endpoint that the app renders.
This ExpressJS example app uses EJS as the HTML template engine.
const express = require("express");
const app = express();
const ejs = require("ejs");
app.engine("html", ejs.renderFile);
app.set("view engine", "html");
app.use(express.json());
// This is an example of a list of items available for purchase and their prices.
// Normally this would come from a database, but is done this way for simplicity.
const allItems = {
"item-1": 10,
"item-2": 20,
}
// Endpoint to create a new Checkout instance and return a `url` that the mobile app must render to show Embedded Checkout.
app.post("/checkout", async function (req,res) {
// See https://gitlab.com/p2886/checkout-samples/-/blob/main/Embedded%20Checkout/app.js?ref_type=heads#L55
// For details of this function
const accessToken = await getAccessToken();
// Get items from basket, calculate total.
const basket = req.body.basket;
const total = basket.reduce((previous, currentItem) => {
// Ideally this would be queried from a database of items available and their prices.
// We are hard coding them for simplicity.
const amount = allItems[currentItem.id] * currentItem.quantity;
return previous + amount;
}, 0);
const orderId = Math.random().toString(36).substring(2, 15);
// Save the order in your database for reference and for tracking your orders.
await saveOrder(orderId, basket, total);
// See https://gitlab.com/p2886/checkout-samples/-/blob/main/Embedded%20Checkout/app.js?ref_type=heads#L88
// For how this function could work.
const checkoutId = await getCheckoutId(accessToken, total, orderId);
const url = `${req.protocol}://${req.get('host')}/checkout/${checkoutId}`;
return res.status(200).json({
url: url
});
});
app.get("/checkout/:checkoutId", function (req,res) {
res.header("Permissions-Policy", "payment self 'src'");
return res.render("index.ejs", {checkoutId: req.params["checkoutId"], key: process.env.PEACH_PAYMENTS_ENTITY_ID});
});
app.listen(3000, function () {
console.log("Server is running on localhost:3000");
});Your index.ejs file could resemble the following:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=no"
/>
<title>Complete your payment</title>
<script src="https://sandbox-checkout.peachpayments.com/js/checkout.js"></script>
</head>
<body>
<div id="payment-form"></div>
</body>
<script>
const checkout = Checkout.initiate({
checkoutId: "<%= checkoutId %>",
key: "<%= key %>",
events: {
onCompleted: (event) => {
console.log(event);
// Stop rendering Embedded Checkout.
checkout.unmount();
// Show paid.
document.getElementById("payment-form").innerText = "Paid!";
},
onCancelled: (event) => {
console.log(event);
// Stop rendering Embedded Checkout.
checkout.unmount();
// Show cancelled.
document.getElementById("payment-form").innerText = "Cancelled!";
},
onExpired: (event) => {
console.log(event);
// Stop rendering Embedded Checkout.
checkout.unmount();
// Show expired.
document.getElementById("payment-form").innerText = "Expired!";
},
onError: (event) => {
console.log(event);
// Stop rendering Embedded Checkout.
checkout.unmount();
// Show error.
document.getElementById("payment-form").innerText = "Error!";
},
},
});
checkout.render("#payment-form");
</script>
</html>Updated about 19 hours ago