@colixsystems/payments-client
v0.1.0
Published
Scoped client for the AppStudio widget one-time-charge API. Used by widgets through the injected WidgetContext.payments.
Readme
@colixsystems/payments-client
Scoped client for the AppStudio widget one-time-charge API (REQ-BILL-07-WIDGETPAY). It is the billing-plane sibling of @colixsystems/datastore-client, @colixsystems/assets-client, and @colixsystems/directory-client. It is a standalone fetch-based client you instantiate with createPaymentsClient({ baseUrl, getToken, getTenantId }).
Two surfaces, one package. This client serves two callers:
- External / server-side integrations instantiate it directly with an API key and call the methods below.
- The widget runtime — the Player and exported Expo app instantiate the same package and inject it into
WidgetContext.payments(a flat{ requestPayment, getPayment }namespace, with the widget install id bound by the host). Widgets never import this package; they call the SDK hookusePayments()from@colixsystems/widget-sdk, which readsctx.payments. Both surfaces speak the identical snake_case REST contract.
Status
v0.1.0 — pre-publish. Not yet published to npm.
Wire format — snake_case, no transform
The wire format is snake_case in both directions (REQ-GEN-09) and that is the client contract. The SDK does no camelCase↔snake_case transform. Request bodies are sent snake_case verbatim ({ widget_installation_id, amount_cents, currency, description, return_path }); response objects are returned snake_case verbatim ({ id, status, created_at, … }). The only caller-facing camelCase is the JS method names (requestPayment, getPayment) and the factory option names.
Public API
import {
createPaymentsClient,
PaymentsError,
NotFoundError,
ForbiddenError,
ValidationError,
RateLimitedError,
ServerError,
} from "@colixsystems/payments-client";
const client = createPaymentsClient({
baseUrl: "https://api.appstudio.io",
getToken: () => "Bearer ...",
getTenantId: () => "tenant_abc",
// Optional: per-request extra headers (e.g. a per-widget scope token). Called
// with { namespace: "payments", operation } and merged into the request.
getRequestHeaders: ({ namespace, operation }) => ({ "X-Widget-Scopes": "..." }),
// fetchImpl defaults to globalThis.fetch
});
// Trigger a one-time charge. The host binds widget_installation_id; without it,
// requestPayment rejects locally (err.response = { status: 400,
// data: { code: "PAYMENTS_UNAVAILABLE" } }) rather than calling the API.
const result = await client.requestPayment({
widget_installation_id: "wi_123",
amount_cents: 1999,
currency: "usd",
description: "Pro upgrade",
return_path: "/billing/done",
});
// → { ...payment, checkout_url: "https://checkout.stripe.com/..." }
// Poll the payment status (scoped server-side to the calling app user).
const payment = await client.getPayment(result.id);Surface
| Method | HTTP | Returns |
| --- | --- | --- |
| requestPayment(body) | POST /payments/widget-charge | PaymentResult ({ ...payment, checkout_url }) |
| getPayment(id) | GET /payments/{id} | Payment (snake_case row) |
requestPayment body (snake_case verbatim): { widget_installation_id, amount_cents, currency, description, return_path }.
Factory options
| Option | Type | Notes |
| --- | --- | --- |
| baseUrl | string | Required. API root, e.g. https://api.appstudio.io/api/v1. |
| getToken | () => string \| Promise<string> | Required. The Authorization header value; "Bearer " prefix added if missing. |
| getTenantId | () => string \| Promise<string> | Required. The x-tenant-id header value. |
| getRequestHeaders | ({ namespace, operation }) => object \| Promise<object> | Optional. Extra headers merged per request. |
| fetchImpl | typeof fetch | Optional. Defaults to globalThis.fetch. |
Transport
- Idempotent GETs retried 3× with exponential backoff (200/400/800 ms); non-idempotent verbs are not retried.
- 10 s default per-call timeout via
AbortController. - Typed
PaymentsErrorhierarchy (NotFoundError,ForbiddenError,ValidationError,RateLimitedError,ServerError) pluserrorFromResponse.
Dependencies
None. The client uses only platform fetch and AbortController, available in modern browsers, Node 18+, and React Native.
