Mantepay

Integration guide

Create a payment link on your server, redirect the customer to it, and confirm the payment before you fulfill the order. The API is at https://mantepay.com. It takes no API key.

Create a payment link

POST /api/sessions

ParameterRequiredDescription
merchantYesThe address that receives the USDC payout.
amountCentsYesThe amount to charge, in US cents. See amounts and fees.
returnUrlYesWhere the payment page sends the customer after they pay. https only (http for localhost), at most 1024 characters, no credentials.
maxFeeBpsNoThe highest fee you accept, in basis points. Defaults to 1000 (10%). The maximum is 3000.
expiresInNoSeconds until the link expires. Defaults to 3600. Between 60 and 2592000.
const res = await fetch("https://mantepay.com/api/sessions", {
  method: "POST",
  headers: { "content-type": "application/json" },
  body: JSON.stringify({
    merchant: "0x70997970C51812dc3A010C7d01b50e0d17dc79C8",
    amountCents: 2500,
    returnUrl: "https://shop.example/thanks?order=42",
  }),
});
const { id, url } = await res.json();

The response is 201:

{
  "v": 1,
  "id": "0x4f1c…e9a2",
  "url": "https://mantepay.com/pay#AnCZeXDF…",
  "merchant": "0x70997970C51812dc3A010C7d01b50e0d17dc79C8",
  "amountCents": 2500,
  "maxFeeBps": 1000,
  "expiresAt": 1800003600,
  "returnUrl": "https://shop.example/thanks?order=42"
}

id identifies the payment. Store it with the order. url is the payment page. expiresAt is in Unix seconds.

Redirect the customer

Send the customer to url. A link can be paid once and stops working at expiresAt. If an attempt fails, the customer can try again on the same link.

After a successful payment the page shows a button to returnUrl. It does not redirect automatically, and the customer may not return.

Do not treat a visit to returnUrl as proof of payment. Check the payment status, as below, before you fulfill the order.

Check the payment status

GET /api/sessions/{id}

id is the value returned when you created the link.

const res = await fetch(`https://mantepay.com/api/sessions/${id}`);
const payment = await res.json();
if (payment.status === "paid") {
  // fulfill the order
}

The response for a paid payment:

{
  "v": 1,
  "id": "0x4f1c…e9a2",
  "status": "paid",
  "merchant": "0x70997970C51812dc3A010C7d01b50e0d17dc79C8",
  "amountCents": 2500,
  "payout": "24.25",
  "paidAt": 1800000300
}
StatusDescription
pendingNot paid. This includes a customer who is still entering their card details, and one whose last attempt failed and who can try again until the link expires.
processingA payment is being processed. Check again shortly.
paidPaid. The USDC has been sent to merchant.

merchant, amountCents, payout and paidAt are present once the status is paid. payout is the USDC you received, after the fee, and paidAt is in Unix seconds.

There are no webhooks. Poll this endpoint, for example when the customer reaches your returnUrl, and until expiresAt for orders that are still waiting.

Amounts and fees

What the customer sees

The payment page shows the amount and the merchant address, then the card form. Card details are encrypted in the customer's browser. Above the card fields the page states:

When you pay, your card is used to buy a $25.00 Sephora e-gift card. That purchase is how the merchant is paid. The gift card is not issued to you. Your card statement will show the retailer's name, not the merchant's.

Testing

In test mode the payment page says so, no retailer is contacted and nothing is charged. Use any future expiry date and any three-digit security code. Do not enter a real card. The demo shop creates a payment link, sends you to pay it and checks the payment status when you come back.

Card numberResult
4242 4242 4242 4242Succeeds.
4000 0000 0000 0002Declined.
4000 0000 0000 3220Requires 3-D Secure.

Errors

A failed request returns {"error": "…"} with one of these statuses.

StatusDescription
400The request is invalid. The message begins with the parameter, for example returnUrl: must be an https address.
413The request body is larger than 32 KB.
429Rate limit exceeded: 60 link requests and 120 status requests a minute, across all clients.
503Temporarily unavailable. Retry shortly.