API Examples and Workflows
Assume these variables:
BASE_URLTENANT_IDAUTH
Real-life integration patterns
Example 1: E-commerce one-time checkout (guest or existing customer)
Use this when a shopper is buying products in a cart and you want to collect payment immediately.
How to implement:
- In your frontend, open PekoPay Checkout and get a
paymentTokenfrom the success callback. - In your backend, ensure the shopper exists as a customer (create or reuse by email).
- Create the transaction with cart items and tax options.
- Save the returned transaction ID in your order system and show a success page.
Step-by-step: get and use a payment token
Use this exact sequence in an e-commerce checkout:
- Your backend requests a checkout token from PekoPay.
- Your frontend mounts the embedded checkout form with that token.
- Customer submits card details through the embedded form.
- In
completeTransaction, usedata.sessionTokenas the payment token. - Your frontend sends that
paymentTokento your backend. - Your backend creates the one-time transaction.
Backend step 1: generate checkout token
const tokenResponse = await fetch(`${baseUrl}/payment-token/${tenantId}/generate`, {
method: 'POST',
headers: {
Authorization: `Basic ${auth}`,
'Content-Type': 'application/json'
}
});
if (!tokenResponse.ok) throw new Error('Failed to generate checkout token');
const { token } = await tokenResponse.json();
// Return token to frontendFrontend step 2 to 5: embed checkout form and capture paymentToken
import { PekoCheckoutForm } from '@pekopay/web';
const checkoutFormContainer = document.getElementById('credit-card-form-container');
const payButton = document.getElementById('pay-button');
const form = PekoCheckoutForm.initialize({
sessionToken: token, // from your backend /payment-token/:tenantId/generate
environment: 'sandbox',
container: checkoutFormContainer,
height: 160,
width: 300,
onFormFieldStateChange: (state) => {
console.log('Form field state changed:', state);
},
onLoadError: (error) => {
console.error('Form load error:', error);
},
completeTransaction: async (data) => {
// IMPORTANT: use data.sessionToken (can be refreshed token)
const paymentToken = data.sessionToken;
await fetch('/api/checkout/complete', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ paymentToken, cartId })
});
// Optional: selected card details for future card selection flows
const selectedCard = {
cardLastFourDigits: data.cardData.last4Digits,
cardType: data.cardData.cardType
};
console.log('Selected card:', selectedCard);
},
sessionTokenProvider: async () => {
// Called if the current token expires; fetch and return a fresh token
const response = await fetch('/api/checkout/token');
const { token: refreshedToken } = await response.json();
return refreshedToken;
}
});
form.mount();
payButton?.addEventListener('click', () => {
form.submitFormFields();
});Backend step 4 and 5: use paymentToken to create transaction
curl -X POST "${BASE_URL}/tenants/${TENANT_ID}/transactions" \
-H "Authorization: Basic ${AUTH}" \
-H "Content-Type: application/json" \
-d '{
"paymentToken": "sess_from_checkout_callback",
"customerId": "'$CUSTOMER_ID'",
"items": [
{ "productId": "'$PRODUCT_ID'", "quantity": 2 }
],
"taxRatesOptions": ["gst", "pst"]
}'Important:
- Never create transactions directly from the frontend.
- Always validate cart totals on the backend before charging.
- Treat
paymentTokenas short-lived and one-time use. - Always use the latest
data.sessionTokenfromcompleteTransaction(not the initial token).
Typical backend flow:
# 1) Create customer (skip if already stored in your DB)
curl -X POST "${BASE_URL}/tenants/${TENANT_ID}/customers" \
-H "Authorization: Basic ${AUTH}" \
-H "Content-Type: application/json" \
-d '{
"firstname": "Sarah",
"lastname": "Ng",
"email": "sarah@example.com",
"type": "INDIVIDUAL"
}'
# 2) Charge a one-time transaction using paymentToken from checkout callback
curl -X POST "${BASE_URL}/tenants/${TENANT_ID}/transactions" \
-H "Authorization: Basic ${AUTH}" \
-H "Content-Type: application/json" \
-d '{
"paymentToken": "sess_from_checkout_callback",
"customerId": "'$CUSTOMER_ID'",
"items": [
{ "productId": "'$PRODUCT_ID'", "quantity": 2 }
],
"taxRatesOptions": ["gst", "pst"]
}'Production tips:
- Treat your backend as source of truth: never trust price/quantity from frontend only.
- Make your order creation idempotent using your own request key to avoid double charges.
- Persist both your internal order ID and PekoPay transaction ID for reconciliation.
Example 2: SaaS subscription signup with trial and plan upgrades
Use this when a customer starts on a trial, becomes active billing, and may upgrade later.
How to implement:
- Create a monthly plan (once, by admin tooling or provisioning script).
- At signup, collect
paymentTokenthrough checkout. - Create subscription for the selected plan and taxes.
- If user upgrades, call the switch-plan endpoint.
- Handle subscription lifecycle updates (success/failure) through your webhook processing.
Typical backend flow:
# 1) Create a plan (one-time setup)
curl -X POST "${BASE_URL}/tenants/${TENANT_ID}/plans" \
-H "Authorization: Basic ${AUTH}" \
-H "Content-Type: application/json" \
-d '{
"name": "Growth Monthly",
"description": "Growth tier for teams",
"recurringChargeAmount": 99.00,
"chargeFrequency": "MONTHLY",
"currency": "CAD",
"trialPeriodDays": 14,
"gracePeriodDays": 7,
"active": true
}'
# 2) Create subscription at signup
curl -X POST "${BASE_URL}/tenants/${TENANT_ID}/subscriptions" \
-H "Authorization: Basic ${AUTH}" \
-H "Content-Type: application/json" \
-d '{
"paymentToken": "sess_from_checkout_callback",
"subscription": {
"planId": "'$PLAN_ID'",
"customerId": "'$CUSTOMER_ID'",
"quantity": 1,
"applicableTaxes": ["gst", "pst"]
}
}'
# 3) Upgrade later
curl -X PATCH "${BASE_URL}/tenants/${TENANT_ID}/subscriptions/${SUBSCRIPTION_ID}/switch" \
-H "Authorization: Basic ${AUTH}" \
-H "Content-Type: application/json" \
-d '{ "planId": "'$NEW_PLAN_ID'" }'Production tips:
- Store plan IDs in your config layer instead of hardcoding in frontend code.
- Model entitlements from your own DB state, updated by subscription lifecycle events.
- Add retry + dead-letter handling for webhook ingestion so billing state stays consistent.
Customers
Create:
curl -X POST "${BASE_URL}/tenants/${TENANT_ID}/customers" \
-H "Authorization: Basic ${AUTH}" \
-H "Content-Type: application/json" \
-d '{
"firstname": "Jane",
"lastname": "Doe",
"email": "jane@acme.com",
"phone": "+1-604-555-1111",
"type": "INDIVIDUAL"
}'Update:
curl -X PUT "${BASE_URL}/tenants/${TENANT_ID}/customers/${CUSTOMER_ID}" \
-H "Authorization: Basic ${AUTH}" \
-H "Content-Type: application/json" \
-d '{
"firstname": "Janet",
"lastname": "Doe",
"email": "janet@acme.com"
}'Delete:
curl -X DELETE "${BASE_URL}/tenants/${TENANT_ID}/customers/${CUSTOMER_ID}" \
-H "Authorization: Basic ${AUTH}"Products
Create:
curl -X POST "${BASE_URL}/tenants/${TENANT_ID}/products" \
-H "Authorization: Basic ${AUTH}" \
-H "Content-Type: application/json" \
-d '{
"name": "Premium Support",
"description": "Monthly support plan",
"price": 49.99,
"currency": "CAD",
"active": true
}'One-time transaction
Endpoint:
POST /tenants/:tenantId/transactions
curl -X POST "${BASE_URL}/tenants/${TENANT_ID}/transactions" \
-H "Authorization: Basic ${AUTH}" \
-H "Content-Type: application/json" \
-d '{
"paymentToken": "sess_from_checkout_callback",
"customerId": "'$CUSTOMER_ID'",
"items": [
{ "productId": "'$PRODUCT_ID'", "quantity": 2 }
],
"taxRatesOptions": ["gst", "pst"]
}'Subscriptions
Create plan:
curl -X POST "${BASE_URL}/tenants/${TENANT_ID}/plans" \
-H "Authorization: Basic ${AUTH}" \
-H "Content-Type: application/json" \
-d '{
"name": "Starter Monthly",
"description": "Starter package",
"recurringChargeAmount": 29.99,
"chargeFrequency": "MONTHLY",
"currency": "CAD",
"trialPeriodDays": 14,
"gracePeriodDays": 7,
"active": true
}'Create subscription:
curl -X POST "${BASE_URL}/tenants/${TENANT_ID}/subscriptions" \
-H "Authorization: Basic ${AUTH}" \
-H "Content-Type: application/json" \
-d '{
"paymentToken": "sess_from_checkout_callback",
"subscription": {
"planId": "'$PLAN_ID'",
"customerId": "'$CUSTOMER_ID'",
"quantity": 1,
"applicableTaxes": ["gst", "pst"]
}
}'Switch plan:
curl -X PATCH "${BASE_URL}/tenants/${TENANT_ID}/subscriptions/${SUBSCRIPTION_ID}/switch" \
-H "Authorization: Basic ${AUTH}" \
-H "Content-Type: application/json" \
-d '{ "planId": "'$NEW_PLAN_ID'" }'Cancel:
curl -X DELETE "${BASE_URL}/tenants/${TENANT_ID}/subscriptions/${SUBSCRIPTION_ID}" \
-H "Authorization: Basic ${AUTH}"Lifecycle operations
Add payment method to existing customer:
curl -X PUT "${BASE_URL}/tenants/${TENANT_ID}/customers/${CUSTOMER_ID}/payment-methods" \
-H "Authorization: Basic ${AUTH}" \
-H "Content-Type: application/json" \
-d '{ "paymentToken": "sess_from_checkout_callback" }'Choose default card:
curl -X PUT "${BASE_URL}/tenants/${TENANT_ID}/customers/${CUSTOMER_ID}/credit-card/choose" \
-H "Authorization: Basic ${AUTH}" \
-H "Content-Type: application/json" \
-d '{
"cardLastFourDigits": "1111",
"cardType": "VISA"
}'Update subscription quantity:
curl -X PATCH "${BASE_URL}/tenants/${TENANT_ID}/subscriptions/${SUBSCRIPTION_ID}/quantity" \
-H "Authorization: Basic ${AUTH}" \
-H "Content-Type: application/json" \
-d '{ "quantity": 5 }'Update subscription payment source:
curl -X PATCH "${BASE_URL}/tenants/${TENANT_ID}/subscriptions/${SUBSCRIPTION_ID}/payment-source" \
-H "Authorization: Basic ${AUTH}" \
-H "Content-Type: application/json" \
-d '{ "pfToken": "sess_new_token_from_checkout" }'Node.js API example (no SDK)
const baseUrl = process.env.PEKOPAY_BASE_URL!;
const tenantId = process.env.TENANT_ID!;
const clientId = process.env.CLIENT_ID!;
const clientSecret = process.env.CLIENT_SECRET!;
const auth = Buffer.from(`${clientId}:${clientSecret}`).toString('base64');
const request = async (path: string, init?: RequestInit) => {
const response = await fetch(`${baseUrl}${path}`, {
...init,
headers: {
Authorization: `Basic ${auth}`,
'Content-Type': 'application/json',
...(init?.headers ?? {})
}
});
if (!response.ok) {
throw new Error(`PekoPay request failed: ${response.status}`);
}
return response.json();
};
const token = await request(`/payment-token/${tenantId}/generate`);
const products = await request(`/tenants/${tenantId}/products`);
const customers = await request(`/tenants/${tenantId}/customers`);