@afriex/checkout
Version:
Checkout service for Afriex SDK
431 lines (339 loc) • 10.9 kB
Markdown
name: afriex-checkout
description: >
Create Afriex hosted checkout sessions with @afriex/checkout CheckoutService
createSession. Covers CreateCheckoutSessionRequest — integer amount in minor
units with a 100 minimum, 3-letter currency, merchantReference, HTTPS
redirectUrl, the required customer name/email/phone/countryCode block,
string-only metadata, and which CheckoutChannel values createSession actually
accepts. Load when building a hosted payment page, redirecting a customer to
pay, or reconciling a checkout session.
metadata:
type: core
library: '@afriex/checkout'
library_version: '3.0.1'
sources:
- 'Afri-exchange/afriex-sdk:packages/checkout/src/CheckoutService.ts'
- 'Afri-exchange/afriex-sdk:packages/checkout/src/types.ts'
# Afriex Checkout
`CheckoutService.createSession` posts to `/checkout-session` and returns a
`checkoutUrl` to redirect or embed. Afriex hosts the payment page; the session
is reconciled through the `merchantReference` you supply and the
`CHECKOUT_SESSION.CREATED` webhook.
## Setup
```ts
import { AfriexSDK, Environment } from "@afriex/sdk";
const afriex = new AfriexSDK({
apiKey: process.env.AFRIEX_API_KEY!,
environment: Environment.STAGING,
});
const session = await afriex.checkout.createSession({
amount: 500000,
currency: "NGN",
merchantReference: "order_9981",
redirectUrl: "https://shop.example.com/orders/9981/complete",
customer: {
name: "Ada Lovelace",
email: "ada@example.com",
phone: "+2348012345678",
countryCode: "NG",
},
});
console.log(session.checkoutUrl);
```
`amount` is an integer in the currency's minor units — `500000` is 5,000.00
NGN. The minimum accepted value is `100`.
## Core Patterns
### Restrict the session to specific channels
```ts
import { AfriexSDK, Environment } from "@afriex/sdk";
const afriex = new AfriexSDK({
apiKey: process.env.AFRIEX_API_KEY!,
environment: Environment.STAGING,
});
const session = await afriex.checkout.createSession({
amount: 250000,
currency: "NGN",
merchantReference: "order_9982",
redirectUrl: "https://shop.example.com/orders/9982/complete",
channels: ["VIRTUAL_BANK_ACCOUNT", "MOBILE_MONEY"],
customer: {
name: "Ada Lovelace",
email: "ada@example.com",
phone: "+2348012345678",
countryCode: "NG",
},
});
console.log(session.checkoutUrl);
```
`createSession` accepts only `VIRTUAL_BANK_ACCOUNT` and `MOBILE_MONEY`, and
`channels` is required — the API rejects a session that omits it, so there is no
"offer everything" default. Name the rails you want explicitly.
### Carry your own context on the session
```ts
import { AfriexSDK, Environment } from "@afriex/sdk";
const afriex = new AfriexSDK({
apiKey: process.env.AFRIEX_API_KEY!,
environment: Environment.STAGING,
});
const session = await afriex.checkout.createSession({
amount: 100000,
currency: "NGN",
merchantReference: "order_9983",
redirectUrl: "https://shop.example.com/orders/9983/complete",
metadata: {
orderId: "9983",
cartSize: "3",
channel: "web",
},
customer: {
name: "Ada Lovelace",
email: "ada@example.com",
phone: "+2348012345678",
countryCode: "NG",
},
});
console.log(session.checkoutUrl);
```
### Convert a major-unit price to the amount the API expects
```ts
import { AfriexSDK, Environment } from "@afriex/sdk";
const afriex = new AfriexSDK({
apiKey: process.env.AFRIEX_API_KEY!,
environment: Environment.STAGING,
});
function toMinorUnits(majorAmount: number): number {
return Math.round(majorAmount * 100);
}
const session = await afriex.checkout.createSession({
amount: toMinorUnits(4999.5),
currency: "NGN",
merchantReference: "order_9984",
redirectUrl: "https://shop.example.com/orders/9984/complete",
customer: {
name: "Ada Lovelace",
email: "ada@example.com",
phone: "+2348012345678",
countryCode: "NG",
},
});
console.log(session.checkoutUrl);
```
## Common Mistakes
### CRITICAL Passing a major-unit price as amount
Wrong:
```ts
import { AfriexSDK } from "@afriex/sdk";
const afriex = new AfriexSDK({ apiKey: process.env.AFRIEX_API_KEY! });
const priceInNaira = 5000;
const session = await afriex.checkout.createSession({
amount: priceInNaira,
currency: "NGN",
merchantReference: "order_9981",
redirectUrl: "https://shop.example.com/done",
customer: {
name: "Ada Lovelace",
email: "ada@example.com",
phone: "+2348012345678",
countryCode: "NG",
},
});
```
Correct:
```ts
import { AfriexSDK } from "@afriex/sdk";
const afriex = new AfriexSDK({ apiKey: process.env.AFRIEX_API_KEY! });
const priceInNaira = 5000;
const session = await afriex.checkout.createSession({
amount: Math.round(priceInNaira * 100),
currency: "NGN",
merchantReference: "order_9981",
redirectUrl: "https://shop.example.com/done",
customer: {
name: "Ada Lovelace",
email: "ada@example.com",
phone: "+2348012345678",
countryCode: "NG",
},
});
```
`amount` is in minor units, so `5000` passes validation as a well-formed
integer above the 100 minimum and charges the customer 50.00 NGN instead of
5,000.00 — the session is created successfully and the shortfall only surfaces
at reconciliation.
Source: packages/checkout/src/CheckoutService.ts (`validateCreateSessionRequest`)
### HIGH Requesting the CARD channel
Wrong:
```ts
import { AfriexSDK } from "@afriex/sdk";
import type { CheckoutChannel } from "@afriex/sdk";
const afriex = new AfriexSDK({ apiKey: process.env.AFRIEX_API_KEY! });
const channels: CheckoutChannel[] = ["CARD", "MOBILE_MONEY"];
await afriex.checkout.createSession({
amount: 250000,
currency: "NGN",
merchantReference: "order_9982",
redirectUrl: "https://shop.example.com/done",
channels,
customer: {
name: "Ada Lovelace",
email: "ada@example.com",
phone: "+2348012345678",
countryCode: "NG",
},
});
```
Correct:
```ts
import { AfriexSDK } from "@afriex/sdk";
const afriex = new AfriexSDK({ apiKey: process.env.AFRIEX_API_KEY! });
await afriex.checkout.createSession({
amount: 250000,
currency: "NGN",
merchantReference: "order_9982",
redirectUrl: "https://shop.example.com/done",
channels: ["VIRTUAL_BANK_ACCOUNT", "MOBILE_MONEY"],
customer: {
name: "Ada Lovelace",
email: "ada@example.com",
phone: "+2348012345678",
countryCode: "NG",
},
});
```
`CheckoutChannel` includes `CARD` because a session can report it, but
`createSession` validates against a narrower allowlist of
`VIRTUAL_BANK_ACCOUNT` and `MOBILE_MONEY`, so the request type-checks and then
throws a `ValidationError` at runtime.
Source: packages/checkout/src/CheckoutService.ts (`supportedChannels`)
### HIGH Putting non-string values in metadata
Wrong:
```ts
import { AfriexSDK } from "@afriex/sdk";
const afriex = new AfriexSDK({ apiKey: process.env.AFRIEX_API_KEY! });
await afriex.checkout.createSession({
amount: 100000,
currency: "NGN",
merchantReference: "order_9983",
redirectUrl: "https://shop.example.com/done",
metadata: { orderId: "9983", cartSize: 3 } as Record<string, string>,
customer: {
name: "Ada Lovelace",
email: "ada@example.com",
phone: "+2348012345678",
countryCode: "NG",
},
});
```
Correct:
```ts
import { AfriexSDK } from "@afriex/sdk";
const afriex = new AfriexSDK({ apiKey: process.env.AFRIEX_API_KEY! });
await afriex.checkout.createSession({
amount: 100000,
currency: "NGN",
merchantReference: "order_9983",
redirectUrl: "https://shop.example.com/done",
metadata: { orderId: "9983", cartSize: String(3) },
customer: {
name: "Ada Lovelace",
email: "ada@example.com",
phone: "+2348012345678",
countryCode: "NG",
},
});
```
`metadata` is `Record<string, string>` and every value is checked with
`typeof value !== "string"`, so a numeric or boolean value — common when
spreading an order object — fails validation for the whole session.
Source: packages/checkout/src/CheckoutService.ts (`hasInvalidMetadata`)
### MEDIUM Using an http redirectUrl in development
Wrong:
```ts
import { AfriexSDK } from "@afriex/sdk";
const afriex = new AfriexSDK({ apiKey: process.env.AFRIEX_API_KEY! });
await afriex.checkout.createSession({
amount: 100000,
currency: "NGN",
merchantReference: "order_9984",
redirectUrl: "http://localhost:3000/checkout/complete",
customer: {
name: "Ada Lovelace",
email: "ada@example.com",
phone: "+2348012345678",
countryCode: "NG",
},
});
```
Correct:
```ts
import { AfriexSDK } from "@afriex/sdk";
const afriex = new AfriexSDK({ apiKey: process.env.AFRIEX_API_KEY! });
await afriex.checkout.createSession({
amount: 100000,
currency: "NGN",
merchantReference: "order_9984",
redirectUrl: process.env.CHECKOUT_RETURN_URL ?? "https://staging.example.com/checkout/complete",
customer: {
name: "Ada Lovelace",
email: "ada@example.com",
phone: "+2348012345678",
countryCode: "NG",
},
});
```
`isHttpsUrl` requires the `https:` protocol, so a plain-HTTP localhost return
URL is rejected client-side and local checkout testing needs an HTTPS tunnel
or a deployed staging URL.
Source: packages/checkout/src/CheckoutService.ts (`isHttpsUrl`)
### MEDIUM Expecting a session id back from createSession
Wrong:
```ts
import { AfriexSDK } from "@afriex/sdk";
const afriex = new AfriexSDK({ apiKey: process.env.AFRIEX_API_KEY! });
const session = await afriex.checkout.createSession({
amount: 100000,
currency: "NGN",
merchantReference: "order_9985",
redirectUrl: "https://shop.example.com/done",
customer: {
name: "Ada Lovelace",
email: "ada@example.com",
phone: "+2348012345678",
countryCode: "NG",
},
});
await saveOrder("order_9985", (session as unknown as { id: string }).id);
async function saveOrder(orderId: string, sessionId: string): Promise<void> {
console.log(orderId, sessionId);
}
```
Correct:
```ts
import { AfriexSDK } from "@afriex/sdk";
const afriex = new AfriexSDK({ apiKey: process.env.AFRIEX_API_KEY! });
const merchantReference = "order_9985";
const session = await afriex.checkout.createSession({
amount: 100000,
currency: "NGN",
merchantReference,
redirectUrl: "https://shop.example.com/done",
customer: {
name: "Ada Lovelace",
email: "ada@example.com",
phone: "+2348012345678",
countryCode: "NG",
},
});
await saveOrder(merchantReference, session.checkoutUrl);
async function saveOrder(orderId: string, checkoutUrl: string): Promise<void> {
console.log(orderId, checkoutUrl);
}
```
`CheckoutSession` carries only `checkoutUrl`, so the stored session id is
`undefined` and later reconciliation has no key; `merchantReference` is the
identifier that ties the session back to your order.
Source: packages/checkout/src/types.ts (`CheckoutSession`)
See also: afriex-webhooks/SKILL.md — `CHECKOUT_SESSION.CREATED` delivery and
signature verification.