Embed checkout in your React Native mobile app

Embedding checkout in your React Native mobile app consists of multiple steps.

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 a page with Embedded Checkout on it.

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 } from "react-native";
import { useEffect, useState } from "react";
import { WebView } from "react-native-webview";

const yourApi = "https://<yourAPI>";

export function PaymentScreen() {
  const [url, setUrl] = useState("");

  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 }}>
      <WebView source={{ uri: url }} originWhitelist={["*"]} />
    </SafeAreaView>
  );
}

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);

  // Here's where we'd save the order in our DB for reference and for tracking our 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 %>",
      options: {
      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!";
        },
      },
    });

    checkout.render("#payment-form");
</script>
</html>