Accept a payment

Build a custom payment integration by embedding UI components on your site using Frame.js. This guide covers both client-side and server-side code to create a checkout form that accepts card payments.

Note: Currently, Frame only accepts card payment methods. We'll update you when more options become available.

1. Set up Frame

Currently, we don't have official libraries. Use any programming language you're comfortable with. In this guide, we'll use Ruby for the server-side code.

2. Collect payment details

Use Frame.js Elements to collect payment details on the client side. Frame.js Elements is a prebuilt UI component that simplifies the collection of payment information for various payment methods.

Frame.js Elements uses an iframe to securely send payment information to Frame over an HTTPS connection. To ensure compatibility with all payment methods, avoid placing Frame.js Elements within another iframe, as some methods require redirecting to a separate page for payment confirmation.

Set up Frame.js

The Payment Element is automatically available as part of Frame.js. Include the Frame.js script on your checkout page by adding it to the head of your HTML file. Always load Frame.js directly from js.framepayments.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.framepayments.com/v1/index.js"></script>
</head>

Add the following JavaScript to your checkout page to create an instance of Frame:

const frame = await Frame.init("<PUBLISHABLE_KEY>");

Add the Payment Element to your checkout page

The Payment Element needs a place to live on your checkout page. Create an empty DOM node (container) with a unique ID in your payment form:

<form id="payment-form">
  <div id="payment-card-element">
    <!-- UI Card will create form elements here -->
  </div>
  <button id="pay" type="submit">Pay now</button>
  <div id="messages">
    <!-- Display message to your customers here -->
  </div>
</form>

When the payment element has loaded, create an Elements instance with the type card. Then, create an instance of the Payment Element and mount it to the container DOM node.

// pick up frame theme
const theme = frame.themes("clean");
// create card element and use theme
const card = await frame.createElement("card", { theme: theme  });
// mount it to the container DOM node
card.mount("#payment-card-element");

The Payment Element renders a Card UI form for customers to enter their card details. You can customize its appearance to match your site's design by passing theme options. For more information, see the Styling section.

3. Create a ChargeIntent

The ChargeIntent object represents your intent to collect payment from a customer and tracks charge attempts and state changes throughout the payment process.

Create the ChargeIntent

Create a ChargeIntent on your server with an amount, currency and payment_method.

curl --request POST \
  --url https://api.framepayments.com/v1/charge_intents \
  --header 'Authorization: Bearer API_KEY' \
  --header 'Content-Type: application/json' \
  --data '{
  "amount": 200,
  "currency": "usd",
  "customer": "customer_id",
  "payment_method": "payment_method_id",
  "confirm": true
}'

Note:

Always decide how much to charge on the server side, a trusted environment, as opposed to the client. This prevents malicious customers from being able to choose their own prices.

Retrieve the client secret

The ChargeIntent contains a client secret required for secure payment processing on the client side. Retrieve the client secret from an endpoint on your server, using the browser's fetch function. This approach is best if your client side is a single-page application, particularly one built with a modern frontend framework like React. Create the server endpoint that serves the client secret:

post "/create-charge-intent" do
  charge_intent = # ... Create the ChargeIntent
  { client_secret: charge_intent["client_secret"] }.to_json
end

And then fetch the client secret with JavaScript on the client side:

const { clientSecret } = await fetch("/create-charge-intent", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  // body may contains the payment method information
  body: JSON.stringify(body),
}).then((r) => r.json());

4. Submit the payment to Frame

To process the payment, utilize the frame.confirmCardPayment method from the Frame.js SDK. This method handles the entire payment flow, including:

  • Processing the form details
  • Managing 3D Secure authentication when required
  • Completing the payment transaction

You maintain full control over the post-payment user experience and can implement custom redirect logic after successful payment completion.

const form = document.getElementById("payment-form");
form.addEventListener("submit", async (e) => {
  e.preventDefault();

  const body = { payment_method: cardPayload };
  const { clientSecret } = await fetch("/create-charge-intent", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(body),
  }).then((r) => r.json());

  const { chargeIntent, error } = await frame.confirmCardPayment(clientSecret);

  if (chargeIntent.status === "succeeded") {
    // you can redirect to the success url
  } else {
    // this point where the payment is fail and you can use `error` object
  }
});

Example Implementation

For a complete working example of Frame payment integration, explore our sample application:

We maintain a comprehensive example application that demonstrates best practices for Frame payment integration.

Download full app

Handle post-payment events

Frame sends a charge_intent.succeeded event when the payment is completed. Use the Dashboard or a custom webhook to receive these events and perform actions such as:

  • Sending an invoice to your customer
  • Logging the transaction in your database
  • Updating order status

For more information on setting up and handling webhooks, refer to our Webhook documentation.