Help Center Developer Docs E-commerce checkout

E-commerce checkout

The amount comes from a cart, so it is different for every order. That rules out checkout buttons, which charge one fixed price, and puts this squarely in API territory.


Flow

Customer     →  Clicks "Pay" in your cart
Your server  →  Creates an order, then POST /v1/payment-links
Your server  →  Redirects the customer to the Brykto pay page
Customer     →  Pays from their wallet
Brykto       →  Sends the order.paid webhook
Your server  →  Marks the order paid
Customer     →  Returns to your site and sees the order confirmed

Why the API and not a button

Amount Suits
Checkout button Fixed, set in the dashboard One product at one price
POST /v1/payment-links Whatever your code sends A cart total

The amount never touches the browser, so there is nothing for a customer to edit. That is the main reason to prefer this over anything client-side.


Step 1 - Create the payment when they click Pay

import os
import requests
from flask import redirect, abort

BASE    = "https://bryktopay.com"
API_KEY = os.environ["BRYKTO_API_KEY"]


@app.post("/cart/checkout")
def checkout():
    order = db.create_order(cart_for(current_user))

    response = requests.post(
        f"{BASE}/v1/payment-links",
        headers={"X-API-Key": API_KEY},
        json={
            "amount": float(order.total),
            "currency": order.currency,
            "description": f"Order {order.number}",
            "order_id": order.number,
            "customer_email": order.customer_email,
        },
        timeout=10,
    )
    if response.status_code == 402:
        abort(503, "Payments are temporarily unavailable.")
    response.raise_for_status()

    payment = response.json()
    db.attach_payment(order.id, payment["request_id"])
    return redirect(f"{BASE}/pay/{payment['request_id']}")
const BASE    = "https://bryktopay.com";
const API_KEY = process.env.BRYKTO_API_KEY;

router.post("/cart/checkout", async (req, res) => {
  const order = await db.createOrder(await cartFor(req.user));

  const response = await fetch(`${BASE}/v1/payment-links`, {
    method: "POST",
    headers: {
      "X-API-Key": API_KEY,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      amount: Number(order.total),
      currency: order.currency,
      description: `Order ${order.number}`,
      order_id: order.number,
      customer_email: order.customerEmail,
    }),
  });

  if (response.status === 402) {
    return res.status(503).send("Payments are temporarily unavailable.");
  }
  if (!response.ok) throw new Error(await response.text());

  const payment = await response.json();
  await db.attachPayment(order.id, payment.request_id);
  res.redirect(`${BASE}/pay/${payment.request_id}`);
});

Setting order_id to your own order number is what lets the webhook find the order again.


Step 2 - Fulfil from the webhook

from decimal import Decimal

@app.post("/webhook/brykto")
def brykto_webhook():
    body = request.get_data()
    if not verify(body,
                  request.headers.get("X-Brykto-Signature", ""),
                  request.headers.get("X-Brykto-Timestamp", "")):
        abort(400)

    event = request.get_json()
    if event["event"] != "order.paid":
        return "", 200

    data = event["data"]
    if db.already_handled(data["tx_hash"]):
        return "", 200

    order = db.get_order_by_number(data["order_id"])
    if order is None:
        return "", 200

    received = Decimal(data["amount_received"] or data["amount"])
    db.mark_paid(order.id, tx_hash=data["tx_hash"], received=received)
    send_confirmation_email(order)

    return "", 200
router.post("/webhook/brykto",
  express.raw({ type: "application/json" }),
  async (req, res) => {
    const ok = verify(
      req.body,
      req.headers["x-brykto-signature"],
      req.headers["x-brykto-timestamp"],
    );
    if (!ok) return res.status(400).end();

    const event = JSON.parse(req.body);
    if (event.event !== "order.paid") return res.status(200).end();

    const data = event.data;
    if (await db.alreadyHandled(data.tx_hash)) return res.status(200).end();

    const order = await db.getOrderByNumber(data.order_id);
    if (!order) return res.status(200).end();

    await db.markPaid(order.id, {
      txHash:   data.tx_hash,
      received: data.amount_received ?? data.amount,
    });
    await sendConfirmationEmail(order);

    res.status(200).end();
  });

The amount is not worth re-checking here the way it would be with a client-set price, because you set it server-side. Storing amount_received is still worth doing, since it can differ slightly from what you asked for.


Step 3 - Getting the customer back

The API cannot return the customer to your site

POST /v1/payment-links takes no return URL. After paying, your customer stays on the Brykto confirmation page, where they are offered an email receipt.

Three ways to handle that, in order of how much work they are:

  1. Email the confirmation. Your webhook already knows the order is paid. Send the receipt and the customer never needs to come back.
  2. Give them a link before they leave. Show "return to your order" on your cart page before redirecting, pointing at /orders/{number}. That page reads your own database, which the webhook has updated.
  3. Keep the order in their session and show a banner on your next page load.

Whichever you pick, the order status must come from your database, not from anything the browser carries back.


Notes

  • API payments expire 30 minutes after creation. That is fine here, because you create it at the moment they click Pay.
  • A customer who abandons the pay page leaves an order that never gets marked paid. Nothing to clean up.
  • Free plan accounts stop at 30 completed payments a calendar month. The API returns 402 after that, which the code above turns into a clean failure rather than a stack trace.
  • If you need the customer returned to your own page automatically, a fixed-price checkout button can do that, because its success URL is set in the dashboard. It cannot do a variable amount.

Next steps