Tokenizer → Customer → Payment
End-to-end: collect card data in the browser with the Tokenizer, store it against a customer in the Vault, then charge by customer reference. The canonical card-on-file flow.
This workflow is the default for any product that wants to charge the same customer more than once — subscriptions, marketplaces, save-for-later checkout, anything card-on-file. It has three server-side steps and a single client-side step, and keeps you in SAQ A PCI scope throughout.
Overview
- Client: render the Tokenizer, collect card data, get a one-time token.
- Server: exchange the token for a stored payment method on a Customer record.
- Server: charge the customer by
customer_id, now and for any future charges.
Step 1 — Tokenize in the browser
On the page where the customer enters card details, mount the Tokenizer:
<script src="https://sandbox.fluidpay.com/js/tokenizer/v1/tokenizer.js"></script>
<form id="checkoutForm" method="POST" action="/checkout">
<div id="payment-form"></div>
<input type="hidden" name="payment_token" id="paymentToken" />
<button type="submit">Save card</button>
</form>
<script>
const tokenizer = new Tokenizer({
apikey: "pub_YOUR_PUBLIC_KEY",
container: "#payment-form",
submission: (resp) => {
if (resp.status === "success") {
document.getElementById("paymentToken").value = resp.token;
document.getElementById("checkoutForm").submit();
} else {
// render resp.validationErrors or resp.msg
}
}
});
</script>
When the customer clicks Save, the tokenizer submits card data to moat and receives a token. The token is a single-use opaque string (prefix tok_) that you post to your server.
Step 2 — Vault the token on a customer
On your server, call moat's Customer Vault to create (or update) a customer and attach the tokenized payment method:
const resp = await fetch("https://sandbox.fluidpay.com/api/vault/customer", {
method: "POST",
headers: {
"Authorization": process.env.API_KEY,
"Content-Type": "application/json"
},
body: JSON.stringify({
first_name: form.first_name,
last_name: form.last_name,
email: form.email,
billing_address: {
address_line_1: form.address1,
city: form.city,
state: form.state,
postal_code: form.zip,
country: "US"
},
payment_method: {
token: { id: form.payment_token }
}
})
});
const customer = (await resp.json()).data;
// customer.id -> store against your user record
// customer.default_payment_method_id
The token is consumed. The card is now stored in moat's vault and can be referenced by customer.id for any future charge.
If the user is already in your database and already has a customer_id in moat, call POST /api/vault/customer/{id}/payment-method instead — adds the new card to the existing customer. Set default: true to make it the new default.
Step 3 — Charge the customer
const resp = await fetch("https://sandbox.fluidpay.com/api/transaction", {
method: "POST",
headers: {
"Authorization": process.env.API_KEY,
"Content-Type": "application/json"
},
body: JSON.stringify({
type: "sale",
amount: 2500,
payment_method: {
customer: {
id: customer.id
// omit payment_method_id to use the default
}
},
order_id: "ORD-00042",
idempotency_key: "chk_ORD-00042"
})
});
const txn = (await resp.json()).data;
Future charges
You now have a customer_id linked to your user's account in your database. Any future charge — subscription renewal, re-order, top-up — uses the same step 3 call. You never re-collect the card number.
Updating the card
When the saved card expires or the customer wants to swap it, repeat steps 1 and 2 — tokenize a new card, then attach to the existing customer with POST /api/vault/customer/{id}/payment-method. Set default: true to use the new card for subsequent charges. If you want to remove the old card, call DELETE on it after confirming the new one works.
Error handling checklist
| Step | Failure | Handle by |
|---|---|---|
| Tokenize | resp.status !== "success" | Render field-level errors. Do not submit the form. |
| Vault | Token expired or invalid | Token is single-use and expires in 10 minutes. Prompt the user to re-enter the card. |
| Vault | Card rejected at vault time | moat may perform an AVS/zero-dollar check at vault time. On failure, show the decline reason and prompt for another card. |
| Charge | Transaction declined | Card is still in the vault. Save the transaction failure against the order; prompt for retry or alternate card. |
| Charge | Network timeout | Retry with the same idempotency_key. See Duplicate detection. |