@vaam-apps/vpay-sdk 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +732 -0
- package/dist/auth.d.ts +180 -0
- package/dist/auth.js +297 -0
- package/dist/client.d.ts +75 -0
- package/dist/client.js +79 -0
- package/dist/errors.d.ts +91 -0
- package/dist/errors.js +112 -0
- package/dist/form.d.ts +47 -0
- package/dist/form.js +127 -0
- package/dist/http.d.ts +33 -0
- package/dist/http.js +115 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.js +14 -0
- package/dist/resources/account-holders.d.ts +36 -0
- package/dist/resources/account-holders.js +46 -0
- package/dist/resources/balance.d.ts +8 -0
- package/dist/resources/balance.js +10 -0
- package/dist/resources/checkout-sessions.d.ts +55 -0
- package/dist/resources/checkout-sessions.js +127 -0
- package/dist/resources/customers.d.ts +97 -0
- package/dist/resources/customers.js +157 -0
- package/dist/resources/events.d.ts +8 -0
- package/dist/resources/events.js +10 -0
- package/dist/resources/invoices.d.ts +193 -0
- package/dist/resources/invoices.js +275 -0
- package/dist/resources/payment-intents.d.ts +12 -0
- package/dist/resources/payment-intents.js +44 -0
- package/dist/resources/refunds.d.ts +92 -0
- package/dist/resources/refunds.js +106 -0
- package/dist/stripe-auth.d.ts +177 -0
- package/dist/stripe-auth.js +192 -0
- package/dist/types.d.ts +1268 -0
- package/dist/types.js +48 -0
- package/dist/validate.d.ts +16 -0
- package/dist/validate.js +20 -0
- package/dist/version.d.ts +12 -0
- package/dist/version.js +12 -0
- package/dist/webhooks.d.ts +22 -0
- package/dist/webhooks.js +94 -0
- package/package.json +63 -0
- package/scripts/mint-assertion.mjs +73 -0
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,1268 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wire types for vpay's `/v1` resource contract
|
|
3
|
+
* (docs/api/README.md, docs/flows/merchant-auth.md's "Objects" table).
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* The five-value state machine `vpay_core::state::IntentStatus` defines
|
|
7
|
+
* (docs/flows/payment-lifecycle.md). There is deliberately no `failed`
|
|
8
|
+
* status — a rail failure returns the intent to
|
|
9
|
+
* `requires_payment_method` with {@link LastPaymentError} populated.
|
|
10
|
+
*/
|
|
11
|
+
export type PaymentIntentStatus = "requires_payment_method" | "requires_action" | "processing" | "succeeded" | "canceled";
|
|
12
|
+
/** Rail codes this SDK's `payment_method_types` values may hold. */
|
|
13
|
+
export type PaymentMethodType = "mtn_momo" | "orange_money";
|
|
14
|
+
/** Redirect-rail next step, Stripe's own `next_action.redirect_to_url` shape. */
|
|
15
|
+
export interface NextAction {
|
|
16
|
+
type: "redirect_to_url";
|
|
17
|
+
redirect_to_url: {
|
|
18
|
+
url: string;
|
|
19
|
+
return_url: string | null;
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* The closed failure-code vocabulary `vpay_core::failure` owns
|
|
24
|
+
* (docs/flows/failures.md). Adapters map rail-specific error strings into
|
|
25
|
+
* this list; merchants integrate against it once.
|
|
26
|
+
*
|
|
27
|
+
* **Not every rail can produce every code.** The list is the *core's*, and it
|
|
28
|
+
* does not shrink when a rail is added — but a merchant branching on all
|
|
29
|
+
* eleven should know that on the two MVP rails, MTN MoMo reaches all of them
|
|
30
|
+
* and Orange Money reaches three (`payer_timeout`,
|
|
31
|
+
* `provider_account_blocked`, `provider_error`). Orange documents five
|
|
32
|
+
* statuses and no sub-reason for `FAILED`, so its protocol has no way to say
|
|
33
|
+
* "not enough funds"; a payer who cancels on its hosted page arrives as
|
|
34
|
+
* `payer_timeout`, because the rail does not distinguish that from an
|
|
35
|
+
* abandoned page. The table, code by code with the rail reason that produces
|
|
36
|
+
* it and the conformance case that proves it, is
|
|
37
|
+
* `docs/flows/failures.md` § "Which rail can produce which code".
|
|
38
|
+
*
|
|
39
|
+
* `payer_declined` in particular was produced by **no** adapter until
|
|
40
|
+
* 2026-09-10 (vpay issue #59), while being typed here and given buyer copy by
|
|
41
|
+
* `examples/shop`. It is now MTN's `PAYMENT_NOT_APPROVED` /
|
|
42
|
+
* `APPROVAL_REJECTED`.
|
|
43
|
+
*/
|
|
44
|
+
export type FailureCode = "insufficient_funds" | "payer_timeout" | "payer_declined" | "invalid_payer" | "payer_limit_reached" | "payer_account_blocked" | "invalid_payee" | "payee_account_blocked" | "provider_account_blocked" | "provider_unavailable" | "provider_error";
|
|
45
|
+
export interface LastPaymentError {
|
|
46
|
+
code: FailureCode;
|
|
47
|
+
message: string;
|
|
48
|
+
}
|
|
49
|
+
export interface PaymentIntent {
|
|
50
|
+
id: string;
|
|
51
|
+
object: "payment_intent";
|
|
52
|
+
/** Integer minor units. XAF is zero-decimal: 5000 means 5,000 FCFA. */
|
|
53
|
+
amount: number;
|
|
54
|
+
/** Lowercase ISO 4217 code, e.g. `xaf`. */
|
|
55
|
+
currency: string;
|
|
56
|
+
status: PaymentIntentStatus;
|
|
57
|
+
payment_method_types: string[];
|
|
58
|
+
next_action: NextAction | null;
|
|
59
|
+
last_payment_error: LastPaymentError | null;
|
|
60
|
+
metadata: Record<string, string>;
|
|
61
|
+
description: string | null;
|
|
62
|
+
/**
|
|
63
|
+
* The `cus_…` this intent is for, or `null` (S4a).
|
|
64
|
+
*
|
|
65
|
+
* The **id**, never the expanded object: vpay does not implement `expand`,
|
|
66
|
+
* and rendering the customer unasked would put a payer's name, email and
|
|
67
|
+
* phone number into every `payment_intent.*` webhook body. Read it with
|
|
68
|
+
* `client.customers.retrieve`.
|
|
69
|
+
*
|
|
70
|
+
* `?` because a vpay predating 2026-09-06 omits the key entirely — the
|
|
71
|
+
* same reason `Refund.fee` carries one, and with a *weaker* consequence:
|
|
72
|
+
* unlike `fee`, `undefined` and `null` mean the same thing here ("no
|
|
73
|
+
* customer"), so nothing is lost by conflating them.
|
|
74
|
+
*/
|
|
75
|
+
customer?: string | null;
|
|
76
|
+
/** Unix seconds. */
|
|
77
|
+
created: number;
|
|
78
|
+
livemode: boolean;
|
|
79
|
+
/**
|
|
80
|
+
* `pi_…_secret_…` — the payer credential `/v1/browser` accepts to confirm
|
|
81
|
+
* this intent from a browser (hand it to `@vaam-apps/vpay-stripe-js`).
|
|
82
|
+
*
|
|
83
|
+
* Present **only** on `create()` and `retrieve()` responses (Step 5c's
|
|
84
|
+
* D2, `vpay_api::model::PaymentIntentWithSecret`); absent — this property
|
|
85
|
+
* is missing entirely, not `null` — on every `list()` item and on
|
|
86
|
+
* `event.data.object`, so it never reaches a merchant's listing view or a
|
|
87
|
+
* webhook body that's stored and forwarded at-least-once. Never log this
|
|
88
|
+
* value.
|
|
89
|
+
*/
|
|
90
|
+
client_secret?: string;
|
|
91
|
+
}
|
|
92
|
+
export type RefundStatus = "pending" | "succeeded" | "failed" | "canceled";
|
|
93
|
+
export interface Refund {
|
|
94
|
+
id: string;
|
|
95
|
+
object: "refund";
|
|
96
|
+
/**
|
|
97
|
+
* Integer minor units of {@link Refund.currency}. **The payer's money** —
|
|
98
|
+
* never net of {@link Refund.fee}, which is a separate cost with a
|
|
99
|
+
* different owner.
|
|
100
|
+
*/
|
|
101
|
+
amount: number;
|
|
102
|
+
currency: string;
|
|
103
|
+
payment_intent: string;
|
|
104
|
+
status: RefundStatus;
|
|
105
|
+
reason: string | null;
|
|
106
|
+
metadata: Record<string, string>;
|
|
107
|
+
created: number;
|
|
108
|
+
/**
|
|
109
|
+
* What the rail charged to execute this refund, in minor units of
|
|
110
|
+
* {@link Refund.currency} — never a second currency, never a float.
|
|
111
|
+
*
|
|
112
|
+
* **Three answers, and they are not interchangeable.** `0` means the rail
|
|
113
|
+
* reported the movement was free. `null` means it reported no fee at all.
|
|
114
|
+
* `undefined` — the key absent — means the vpay that answered predates the
|
|
115
|
+
* field (added by issue #46). The last two are both *unknown*; only `0` is
|
|
116
|
+
* a measured zero, and substituting it for either is how an invented number
|
|
117
|
+
* reaches a merchant's settlement statement.
|
|
118
|
+
*
|
|
119
|
+
* Optional **and** `| null` on purpose: it is the `?` that puts
|
|
120
|
+
* `undefined` in the read type, so `Refund["fee"]` is exactly
|
|
121
|
+
* `number | null | undefined` and all three answers survive. That is
|
|
122
|
+
* pinned by a type-level assertion — see `types fee so that absent, null
|
|
123
|
+
* and a measured zero stay three different answers` in `types.test.ts`,
|
|
124
|
+
* which fails if either the `?` or the `| null` is dropped.
|
|
125
|
+
*
|
|
126
|
+
* This package's `exactOptionalPropertyTypes` is **not** what creates the
|
|
127
|
+
* distinction, although an earlier version of this comment said it was.
|
|
128
|
+
* Measured 2026-09-05 with the repo's own `tsc`: the read type is
|
|
129
|
+
* `number | null | undefined` with the flag and without it, and an absent
|
|
130
|
+
* property never reads as `null` in either. What the flag actually adds is
|
|
131
|
+
* narrower — `{ fee: undefined }` stops being a legal way to spell
|
|
132
|
+
* "absent".
|
|
133
|
+
*
|
|
134
|
+
* Narrow with `typeof refund.fee === "number"`; `refund.fee ?? 0` and
|
|
135
|
+
* `refund.fee || 0` are both the bug.
|
|
136
|
+
*
|
|
137
|
+
* **It is `null` from every vpay deployment today.** Neither rail reports a
|
|
138
|
+
* refund fee. `orange_money::refund` is an unbuilt `NotImplemented` token —
|
|
139
|
+
* since 2026-09-15 an Orange refund is an outbound transfer this repository
|
|
140
|
+
* has no specification for (RFC-0003 section 5). `mtn_momo::refund` is
|
|
141
|
+
* written, the Disbursements `transfer` call, and reports no fee either:
|
|
142
|
+
* MTN answers `202 ACCEPTED` with an empty body and documents no fee field,
|
|
143
|
+
* and vpay has never called that product. So nothing populates it. (This
|
|
144
|
+
* read "Orange has no refund API" until 2026-09-15, and "both `refund`
|
|
145
|
+
* implementations are unbuilt tokens" until MTN's was written the same
|
|
146
|
+
* day.) See `docs/status.md`.
|
|
147
|
+
*/
|
|
148
|
+
fee?: number | null;
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* The real Stripe event types docs/flows/webhooks.md commits to. A custom
|
|
152
|
+
* type would be silently dropped by a merchant's exhaustive `switch` over
|
|
153
|
+
* Stripe's own typed union — this SDK types `type` as `string` rather than
|
|
154
|
+
* this literal union so an event carrying a type it does not yet know about
|
|
155
|
+
* is still deliverable, and narrows with the exported guards below instead.
|
|
156
|
+
*
|
|
157
|
+
* `checkout.session.expired` is emitted when vpay's hourly sweep moves a
|
|
158
|
+
* session past its 24-hour horizon (Step 9's D10); its `data.object` is a
|
|
159
|
+
* {@link CheckoutSession}, which makes it the only member here whose payload
|
|
160
|
+
* is neither a {@link PaymentIntent} nor a {@link Refund}. Narrow it with
|
|
161
|
+
* {@link isCheckoutSessionEvent}.
|
|
162
|
+
*/
|
|
163
|
+
export type KnownEventType = "payment_intent.created" | "payment_intent.processing" | "payment_intent.succeeded" | "payment_intent.payment_failed" | "payment_intent.canceled" | "charge.refunded" | "charge.refund.updated" | "checkout.session.expired"
|
|
164
|
+
/**
|
|
165
|
+
* A Customer was created by `POST /v1/customers`. `data.object` is the
|
|
166
|
+
* {@link Customer} as the insert stored it — the canonical `phone`, and
|
|
167
|
+
* `metadata` as sent. Written in the same transaction as the row (vpay
|
|
168
|
+
* issue #66, 2026-09-10).
|
|
169
|
+
*/
|
|
170
|
+
| "customer.created"
|
|
171
|
+
/**
|
|
172
|
+
* A Customer was changed by `POST /v1/customers/{id}`. `data.object` is the
|
|
173
|
+
* {@link Customer} **after** the change, including the key-wise `metadata`
|
|
174
|
+
* merge — which vpay computes under the row's lock, so two concurrent
|
|
175
|
+
* updates produce two events describing the two committed states rather
|
|
176
|
+
* than one describing a merge that was lost.
|
|
177
|
+
*
|
|
178
|
+
* A request that changes nothing — a bodiless `POST`, which is what "touch
|
|
179
|
+
* this object" is on the wire — emits **no** event.
|
|
180
|
+
*/
|
|
181
|
+
| "customer.updated"
|
|
182
|
+
/**
|
|
183
|
+
* A Customer was deleted — by `DELETE /v1/customers/{id}` or by vpay's
|
|
184
|
+
* twelve-month retention sweep (S4a). `data.object` is a
|
|
185
|
+
* {@link Customer} as it stood immediately before the delete, which is the
|
|
186
|
+
* **only** way to learn its `name`, `email` and `phone`: the row is gone,
|
|
187
|
+
* and a `GET` afterwards is byte-identical to one for an id that never
|
|
188
|
+
* existed.
|
|
189
|
+
*/
|
|
190
|
+
| "customer.deleted"
|
|
191
|
+
/**
|
|
192
|
+
* A draft invoice was created by `POST /v1/invoices` (S4b). The insert and
|
|
193
|
+
* this event are one transaction, so an event you received describes a row
|
|
194
|
+
* that committed. `data.object` is an {@link Invoice}.
|
|
195
|
+
*/
|
|
196
|
+
| "invoice.created"
|
|
197
|
+
/**
|
|
198
|
+
* A draft was issued: it has its {@link Invoice.number}, its lines are
|
|
199
|
+
* frozen and its amounts are final. `data.object` is an {@link Invoice} in
|
|
200
|
+
* `"open"`.
|
|
201
|
+
*/
|
|
202
|
+
| "invoice.finalized"
|
|
203
|
+
/**
|
|
204
|
+
* A settlement paid an invoice in full. `data.object` is an
|
|
205
|
+
* {@link Invoice} in `"paid"`.
|
|
206
|
+
*
|
|
207
|
+
* Emitted inside the settlement transaction itself, **beside** the
|
|
208
|
+
* `payment_intent.succeeded` for the same money — a handler receives both
|
|
209
|
+
* and must not read them as two payments.
|
|
210
|
+
*/
|
|
211
|
+
| "invoice.paid"
|
|
212
|
+
/**
|
|
213
|
+
* The merchant cancelled an issued invoice. `data.object` is an
|
|
214
|
+
* {@link Invoice} in `"void"`, **keeping** its number.
|
|
215
|
+
*
|
|
216
|
+
* There is deliberately no `invoice.marked_uncollectible` beside this one:
|
|
217
|
+
* vpay does not write it, so a union entry would be a claim about vpay that
|
|
218
|
+
* is false. A write-off is learned from
|
|
219
|
+
* `client.invoices.list({ status: "uncollectible" })`. The same goes for
|
|
220
|
+
* Stripe's `invoice.payment_failed`, which a merchant meets as
|
|
221
|
+
* `payment_intent.payment_failed`.
|
|
222
|
+
*/
|
|
223
|
+
| "invoice.voided";
|
|
224
|
+
export interface Event {
|
|
225
|
+
id: string;
|
|
226
|
+
object: "event";
|
|
227
|
+
type: string;
|
|
228
|
+
/** Unix seconds. */
|
|
229
|
+
created: number;
|
|
230
|
+
livemode: boolean;
|
|
231
|
+
/**
|
|
232
|
+
* Kept as raw JSON with typed accessors below, rather than a typed union,
|
|
233
|
+
* so an event carrying an object shape this SDK does not model is still
|
|
234
|
+
* deliverable (docs/flows/merchant-auth.md, "Objects").
|
|
235
|
+
*/
|
|
236
|
+
data: {
|
|
237
|
+
object: unknown;
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
/** Narrows a {@link PaymentIntent}-shaped value out of `event.data.object`. */
|
|
241
|
+
export declare function isPaymentIntentEvent(event: Event): event is Event & {
|
|
242
|
+
data: {
|
|
243
|
+
object: PaymentIntent;
|
|
244
|
+
};
|
|
245
|
+
};
|
|
246
|
+
/** Narrows a {@link Refund}-shaped value out of `event.data.object`. */
|
|
247
|
+
export declare function isRefundEvent(event: Event): event is Event & {
|
|
248
|
+
data: {
|
|
249
|
+
object: Refund;
|
|
250
|
+
};
|
|
251
|
+
};
|
|
252
|
+
/**
|
|
253
|
+
* Narrows a {@link CheckoutSession}-shaped value out of `event.data.object`.
|
|
254
|
+
*
|
|
255
|
+
* Matched on the `checkout.session.` prefix rather than on the one literal
|
|
256
|
+
* type vpay emits today, exactly as the two guards above are: a later
|
|
257
|
+
* `checkout.session.*` type carries the same object, and a guard that had to
|
|
258
|
+
* be edited for each one is a guard that will be out of date before the
|
|
259
|
+
* `switch` that calls it is.
|
|
260
|
+
*
|
|
261
|
+
* The session on an event **never** carries `client_secret`, and its `url` is
|
|
262
|
+
* always `null`: both would put a live payer credential in a body that is
|
|
263
|
+
* stored, delivered at-least-once and replayed
|
|
264
|
+
* (`vpay_api::model::CheckoutSessionObject::expired_snapshot`). So
|
|
265
|
+
* {@link CheckoutSession.url} being `null` here does not mean the session was
|
|
266
|
+
* embedded — read {@link CheckoutSession.ui_mode} for that.
|
|
267
|
+
*/
|
|
268
|
+
export declare function isCheckoutSessionEvent(event: Event): event is Event & {
|
|
269
|
+
data: {
|
|
270
|
+
object: CheckoutSession;
|
|
271
|
+
};
|
|
272
|
+
};
|
|
273
|
+
/**
|
|
274
|
+
* Narrows an {@link Invoice}-shaped value out of `event.data.object`, for one
|
|
275
|
+
* of the four `invoice.*` events (S4b).
|
|
276
|
+
*
|
|
277
|
+
* Matched on the `invoice.` prefix rather than on the four literals, as every
|
|
278
|
+
* guard above is.
|
|
279
|
+
*
|
|
280
|
+
* **The invoice on an event carries no lines.** `data.object.lines.data` is
|
|
281
|
+
* empty on every `invoice.*` body — the event is rendered inside the
|
|
282
|
+
* transition's own transaction, where the lines are not read — so
|
|
283
|
+
* {@link Invoice.lines} here is an empty {@link List}, and that is not the
|
|
284
|
+
* same statement as "this invoice has no lines". A handler that needs them
|
|
285
|
+
* calls `client.invoices.retrieve`, whose response always carries them.
|
|
286
|
+
*/
|
|
287
|
+
export declare function isInvoiceEvent(event: Event): event is Event & {
|
|
288
|
+
data: {
|
|
289
|
+
object: Invoice;
|
|
290
|
+
};
|
|
291
|
+
};
|
|
292
|
+
/**
|
|
293
|
+
* A Checkout Session's lifecycle state (Step 9's D10) — vpay's own three
|
|
294
|
+
* values, not Stripe's. There is no `failed`: a session whose intent
|
|
295
|
+
* reached a terminal non-success state is `expired` with
|
|
296
|
+
* {@link CheckoutSession.payment_status} `failed`.
|
|
297
|
+
*/
|
|
298
|
+
export type CheckoutSessionStatus = "open" | "complete" | "expired";
|
|
299
|
+
/** Whether the session's intent has been paid. D10's second axis. */
|
|
300
|
+
export type CheckoutPaymentStatus = "unpaid" | "paid" | "failed";
|
|
301
|
+
/**
|
|
302
|
+
* Which surface the session is rendered on. `hosted` gets a
|
|
303
|
+
* {@link CheckoutSession.url} to send the payer to; `embedded` gets a
|
|
304
|
+
* `client_secret` to hand `@vaam-apps/vpay-stripe-js`'s `initEmbeddedCheckout`.
|
|
305
|
+
*/
|
|
306
|
+
export type CheckoutSessionUiMode = "hosted" | "embedded";
|
|
307
|
+
/**
|
|
308
|
+
* A `/v1/checkout/sessions` object (Step 9's D1).
|
|
309
|
+
*
|
|
310
|
+
* The session **references** an existing PaymentIntent; it never creates
|
|
311
|
+
* one. Amount, currency and the allowed rails stay on the intent, where
|
|
312
|
+
* every existing invariant already guards them — which is why there is no
|
|
313
|
+
* `line_items`, no `mode` and no `amount_total` here, however Stripe-shaped
|
|
314
|
+
* the rest of the field names are.
|
|
315
|
+
*/
|
|
316
|
+
export interface CheckoutSession {
|
|
317
|
+
/** `cs_…`. */
|
|
318
|
+
id: string;
|
|
319
|
+
object: "checkout.session";
|
|
320
|
+
livemode: boolean;
|
|
321
|
+
/**
|
|
322
|
+
* The `pi_…` this session drives — an **id**, on every `/v1` route.
|
|
323
|
+
*
|
|
324
|
+
* `@vaam-apps/vpay-stripe-js`'s `CheckoutSession` types the same field as the whole
|
|
325
|
+
* expanded {@link PaymentIntent}, because the *browser* session read
|
|
326
|
+
* expands it (the checkout page confirms and polls the intent through the
|
|
327
|
+
* browser routes and cannot fetch it separately). That is a deliberate
|
|
328
|
+
* per-route difference, ruled on 2026-09-04, not a skew between the
|
|
329
|
+
* SDKs: nothing on `/v1` ever expands it, so a merchant integration reads
|
|
330
|
+
* an id here and calls `paymentIntents.retrieve` if it wants the object.
|
|
331
|
+
*/
|
|
332
|
+
payment_intent: string;
|
|
333
|
+
ui_mode: CheckoutSessionUiMode;
|
|
334
|
+
status: CheckoutSessionStatus;
|
|
335
|
+
payment_status: CheckoutPaymentStatus;
|
|
336
|
+
/** Hosted mode only; `null` when embedded. May carry `{CHECKOUT_SESSION_ID}` (D5). */
|
|
337
|
+
success_url: string | null;
|
|
338
|
+
/** Hosted mode only; `null` when embedded. */
|
|
339
|
+
cancel_url: string | null;
|
|
340
|
+
/** Embedded mode only; `null` when hosted. */
|
|
341
|
+
return_url: string | null;
|
|
342
|
+
/** The page to send the payer to, hosted mode only; `null` when embedded. */
|
|
343
|
+
url: string | null;
|
|
344
|
+
/**
|
|
345
|
+
* The `cus_…` this session is for, or `null` (S4a). Copied from the
|
|
346
|
+
* session's intent at create when the intent has one. The id, never the
|
|
347
|
+
* expanded object — {@link PaymentIntent.customer}'s reason, unchanged.
|
|
348
|
+
*/
|
|
349
|
+
customer?: string | null;
|
|
350
|
+
/** Unix seconds. 24 h from create. */
|
|
351
|
+
expires_at: number;
|
|
352
|
+
/** Unix seconds. */
|
|
353
|
+
created: number;
|
|
354
|
+
/**
|
|
355
|
+
* `cs_…_secret_…` — the payer credential the browser presents to read
|
|
356
|
+
* this session, and what `initEmbeddedCheckout`'s `fetchClientSecret`
|
|
357
|
+
* must return.
|
|
358
|
+
*
|
|
359
|
+
* Present **only** on `create()` and `retrieve()` responses; absent —
|
|
360
|
+
* missing entirely, not `null` — on every `list()` item. It is a
|
|
361
|
+
* different credential from the intent's `client_secret`: it authorises
|
|
362
|
+
* reading this session, never confirming the intent.
|
|
363
|
+
*
|
|
364
|
+
* Never log this value. Unlike {@link PaymentIntent}, whose plain
|
|
365
|
+
* interface prints its secret through `console.log`, every
|
|
366
|
+
* `CheckoutSession` this SDK returns carries a custom
|
|
367
|
+
* `util.inspect` representation that redacts this field — see
|
|
368
|
+
* `src/resources/checkout-sessions.ts`. `JSON.stringify` is deliberately
|
|
369
|
+
* left faithful: an embedded integration has to serialise the secret to
|
|
370
|
+
* get it to the browser at all.
|
|
371
|
+
*/
|
|
372
|
+
client_secret?: string;
|
|
373
|
+
}
|
|
374
|
+
export interface BalanceAmount {
|
|
375
|
+
amount: number;
|
|
376
|
+
currency: string;
|
|
377
|
+
}
|
|
378
|
+
export interface Balance {
|
|
379
|
+
object: "balance";
|
|
380
|
+
available: BalanceAmount[];
|
|
381
|
+
pending: BalanceAmount[];
|
|
382
|
+
}
|
|
383
|
+
/**
|
|
384
|
+
* Who a mobile-money number is registered to, or the fact that the rail has
|
|
385
|
+
* no record of it — `GET /v1/account_holders` (issue #47).
|
|
386
|
+
*
|
|
387
|
+
* `name: null` means **the rail answered and does not know this number**. It
|
|
388
|
+
* does not mean "we could not ask": that throws. A caller matching a
|
|
389
|
+
* nominated refund destination against a buyer's verified name must refuse
|
|
390
|
+
* on both — but only one of them is the buyer's problem.
|
|
391
|
+
*
|
|
392
|
+
* A name and nothing else, by construction: the rail's body carries more (a
|
|
393
|
+
* birthdate, a locale, a gender) and vpay projects it away before it can be
|
|
394
|
+
* logged or stored. There is no `id`, because nothing is stored to address
|
|
395
|
+
* later.
|
|
396
|
+
*/
|
|
397
|
+
export interface AccountHolder {
|
|
398
|
+
object: "account_holder";
|
|
399
|
+
/**
|
|
400
|
+
* The rail the question was put to, echoed back.
|
|
401
|
+
*
|
|
402
|
+
* `string` and not {@link PaymentMethodType}, for the reason
|
|
403
|
+
* `PaymentIntent.payment_method_types` is `string[]`: the request side is
|
|
404
|
+
* closed and the response side must stay readable for a rail this SDK
|
|
405
|
+
* version predates.
|
|
406
|
+
*/
|
|
407
|
+
payment_method_type: string;
|
|
408
|
+
/** The registered holder's name, or `null` when the rail has no record. */
|
|
409
|
+
name: string | null;
|
|
410
|
+
/**
|
|
411
|
+
* `true` exactly when `name` is present.
|
|
412
|
+
*
|
|
413
|
+
* Not a claim that anything was cryptographically verified — it says the
|
|
414
|
+
* rail named a holder.
|
|
415
|
+
*/
|
|
416
|
+
verified: boolean;
|
|
417
|
+
}
|
|
418
|
+
/**
|
|
419
|
+
* `GET /v1/account_holders` request fields.
|
|
420
|
+
*
|
|
421
|
+
* A `type` alias for {@link ListParams}' index-signature reason. Both fields
|
|
422
|
+
* are required and neither is optional: a lookup with no number or no rail
|
|
423
|
+
* is not a narrower query, it is not a query.
|
|
424
|
+
*
|
|
425
|
+
* **`payment_method_type`, snake_case, like every other params type in this
|
|
426
|
+
* package** — the wire spelling, which is what the encoder walks and what
|
|
427
|
+
* `sdks/rust/tests/resources.rs` pins byte for byte. A camelCase
|
|
428
|
+
* `paymentMethodType` would be the only camelCase request field in the SDK
|
|
429
|
+
* and would need a translation step the others do not have.
|
|
430
|
+
*/
|
|
431
|
+
export type RetrieveAccountHolderParams = {
|
|
432
|
+
/**
|
|
433
|
+
* The mobile-money number, in any of the three shapes vpay accepts:
|
|
434
|
+
* `+2376XXXXXXXX`, `2376XXXXXXXX` or the national `6XXXXXXXX`. Validated
|
|
435
|
+
* by the server, not here — see `AccountHoldersResource.retrieve`.
|
|
436
|
+
*/
|
|
437
|
+
msisdn: string;
|
|
438
|
+
/** Which rail to ask. Closed on the request side, as a create's list is. */
|
|
439
|
+
payment_method_type: PaymentMethodType;
|
|
440
|
+
};
|
|
441
|
+
export interface List<T> {
|
|
442
|
+
object: "list";
|
|
443
|
+
data: T[];
|
|
444
|
+
has_more: boolean;
|
|
445
|
+
url: string;
|
|
446
|
+
}
|
|
447
|
+
/**
|
|
448
|
+
* A `type` alias, not an `interface`, on purpose: TypeScript gives an
|
|
449
|
+
* anonymous object type an *implicit index signature* and an interface none,
|
|
450
|
+
* so only this form is assignable to the encoder's
|
|
451
|
+
* `Record<string, FormValue>` without an `as unknown as` cast.
|
|
452
|
+
*
|
|
453
|
+
* Every optional property is written `?: T | undefined` rather than `?: T`,
|
|
454
|
+
* so that a consumer compiling with `exactOptionalPropertyTypes` (as this
|
|
455
|
+
* repo does) can pass a variable that is legitimately `T | undefined`.
|
|
456
|
+
*/
|
|
457
|
+
export type ListParams = {
|
|
458
|
+
limit?: number | undefined;
|
|
459
|
+
starting_after?: string | undefined;
|
|
460
|
+
ending_before?: string | undefined;
|
|
461
|
+
};
|
|
462
|
+
/**
|
|
463
|
+
* A postal address on a {@link Customer} — Stripe's six formal components
|
|
464
|
+
* **and** the GPS point (issue #67).
|
|
465
|
+
*
|
|
466
|
+
* # vpay's address is both halves, and this is where Stripe's is not
|
|
467
|
+
*
|
|
468
|
+
* `latitude_microdeg` and `longitude_microdeg` have no counterpart on
|
|
469
|
+
* Stripe's `address`. They are here because vpay's address *means* both
|
|
470
|
+
* halves (the maintainer, 2026-09-11): formal addressing is unreliable
|
|
471
|
+
* across the markets vpay serves, and a coordinate is how a place is actually
|
|
472
|
+
* found. A merchant porting Stripe code to vpay gains two fields; one porting
|
|
473
|
+
* the other way loses them, and should know that before they discover it.
|
|
474
|
+
*
|
|
475
|
+
* Every component is nullable and the server renders all eight even when they
|
|
476
|
+
* are `null`: an address is one fact about a payer, and a key that appeared
|
|
477
|
+
* and disappeared would change the shape of a signed `customer.*` webhook
|
|
478
|
+
* body per payer. The object as a whole is `null` when there is no address.
|
|
479
|
+
*
|
|
480
|
+
* The same shape is sent and received. On {@link UpdateCustomerParams} an
|
|
481
|
+
* address **replaces** the stored one rather than merging with it — a request
|
|
482
|
+
* that names `line1` and not `city` clears the city — because an address
|
|
483
|
+
* assembled by the server out of two requests is an address that was never
|
|
484
|
+
* anybody's. Sending `null` removes it entirely.
|
|
485
|
+
*/
|
|
486
|
+
export interface Address {
|
|
487
|
+
line1: string | null;
|
|
488
|
+
line2: string | null;
|
|
489
|
+
city: string | null;
|
|
490
|
+
state: string | null;
|
|
491
|
+
postal_code: string | null;
|
|
492
|
+
/**
|
|
493
|
+
* ISO 3166-1 alpha-2 — `CM`, `FR`, `NG`. Lower case is accepted and stored
|
|
494
|
+
* **upper case**, so what comes back may not be what was sent: the same
|
|
495
|
+
* wire contract {@link Customer.phone}'s canonicalisation is. A
|
|
496
|
+
* three-letter code or a country name is a `400` naming `address`.
|
|
497
|
+
*/
|
|
498
|
+
country: string | null;
|
|
499
|
+
/**
|
|
500
|
+
* Latitude in **microdegrees** — millionths of a degree, so 4.061°N is
|
|
501
|
+
* `4061000`.
|
|
502
|
+
*
|
|
503
|
+
* A whole number, always. vpay has no floating-point coordinate anywhere —
|
|
504
|
+
* not on the wire, not in its database — which is what the unit in the
|
|
505
|
+
* field name is for: a field called `latitude` would be read as degrees,
|
|
506
|
+
* and `4.061` is a value that cannot be stored without rounding it. The
|
|
507
|
+
* server answers `400` naming `address` for a decimal rather than
|
|
508
|
+
* approximating it.
|
|
509
|
+
*
|
|
510
|
+
* `null` unless {@link Address.longitude_microdeg} is also set: half a
|
|
511
|
+
* coordinate names no place, and the server refuses one half.
|
|
512
|
+
*
|
|
513
|
+
* On an **erased** customer this is `null` while the six formal components
|
|
514
|
+
* are `[redacted]`. That asymmetry is deliberate: there is no integer that
|
|
515
|
+
* is not a possible place, so the marker is not a value this field can
|
|
516
|
+
* take. See {@link Customer.deleted}.
|
|
517
|
+
*/
|
|
518
|
+
latitude_microdeg: number | null;
|
|
519
|
+
/** Longitude in **microdegrees**. See {@link Address.latitude_microdeg}. */
|
|
520
|
+
longitude_microdeg: number | null;
|
|
521
|
+
}
|
|
522
|
+
/**
|
|
523
|
+
* A `customer` — the merchant-owned record of a payer they expect to see
|
|
524
|
+
* again (S4a).
|
|
525
|
+
*
|
|
526
|
+
* Nine keys, plus `deleted` on an erased one. `last_used_at` — the clock
|
|
527
|
+
* vpay's twelve-month retention sweep reads — is deliberately **not** on the
|
|
528
|
+
* wire.
|
|
529
|
+
*
|
|
530
|
+
* At least one of `name`, `email` and `phone` is always present. A phone
|
|
531
|
+
* number **alone** is a complete customer, which is what the object is for
|
|
532
|
+
* on a mobile money rail.
|
|
533
|
+
*/
|
|
534
|
+
export interface Customer {
|
|
535
|
+
id: string;
|
|
536
|
+
object: "customer";
|
|
537
|
+
name: string | null;
|
|
538
|
+
email: string | null;
|
|
539
|
+
/**
|
|
540
|
+
* Echoed back **canonicalised** (`2376XXXXXXXX`, no `+`) rather than as it
|
|
541
|
+
* was sent, which is a wire contract and not a quirk: it is the value a
|
|
542
|
+
* rail is given, so a merchant comparing this against a charge's payer
|
|
543
|
+
* reference is comparing the same string.
|
|
544
|
+
*/
|
|
545
|
+
phone: string | null;
|
|
546
|
+
/**
|
|
547
|
+
* The payer's postal address **and** GPS point, or `null`. Two of its keys
|
|
548
|
+
* have no counterpart on Stripe's address — see {@link Address}.
|
|
549
|
+
*/
|
|
550
|
+
address: Address | null;
|
|
551
|
+
metadata: Record<string, string>;
|
|
552
|
+
/** Unix seconds. */
|
|
553
|
+
created: number;
|
|
554
|
+
livemode: boolean;
|
|
555
|
+
/**
|
|
556
|
+
* `true` on a customer vpay has **erased**, and absent on a live one — the
|
|
557
|
+
* server omits the key rather than sending `false`.
|
|
558
|
+
*
|
|
559
|
+
* `DELETE /v1/customers/{id}` removes a customer with no payment history
|
|
560
|
+
* outright, and a later retrieve is a `404`. A customer an intent, a
|
|
561
|
+
* checkout session or an invoice references cannot be removed — vpay never
|
|
562
|
+
* detaches a payment from the payer it was taken from — so it is
|
|
563
|
+
* **anonymised** instead: the row and the payment record stay, and every
|
|
564
|
+
* identifier on it comes back as `[redacted]`. `metadata` is untouched,
|
|
565
|
+
* because that is the merchant's own data.
|
|
566
|
+
*
|
|
567
|
+
* So a `cus_…` in your own records goes on resolving after an erasure,
|
|
568
|
+
* which is why this key exists. Such a customer cannot be updated or
|
|
569
|
+
* attached to a new payment — both answer `409`. See
|
|
570
|
+
* `docs/flows/customers.md`.
|
|
571
|
+
*/
|
|
572
|
+
deleted?: true | undefined;
|
|
573
|
+
}
|
|
574
|
+
/**
|
|
575
|
+
* What `client.customers.del(…)` answers with — Stripe's deleted-object
|
|
576
|
+
* shape.
|
|
577
|
+
*
|
|
578
|
+
* Deliberately **not** the customer that was removed: a merchant confirming
|
|
579
|
+
* an erasure is the caller most likely to log the whole response, and
|
|
580
|
+
* returning the payer's details in it would write them into a log *because*
|
|
581
|
+
* they were deleted.
|
|
582
|
+
*/
|
|
583
|
+
export interface DeletedCustomer {
|
|
584
|
+
id: string;
|
|
585
|
+
object: "customer";
|
|
586
|
+
deleted: true;
|
|
587
|
+
}
|
|
588
|
+
export interface CreateCustomerParams {
|
|
589
|
+
/**
|
|
590
|
+
* At least one of `name`, `email` and `phone` must be present, and this
|
|
591
|
+
* SDK deliberately does not check that: which identifiers a customer needs
|
|
592
|
+
* is a *market* rule vpay owns and may widen — the same line
|
|
593
|
+
* `RetrieveAccountHolderParams.msisdn` takes on the MSISDN — so a copy here
|
|
594
|
+
* would refuse offline a customer a later server version accepts. The
|
|
595
|
+
* server answers `400` naming the parameter. `sdks/rust` takes the
|
|
596
|
+
* identical line.
|
|
597
|
+
*/
|
|
598
|
+
name?: string | undefined;
|
|
599
|
+
email?: string | undefined;
|
|
600
|
+
/**
|
|
601
|
+
* In any spelling vpay's canonicaliser accepts (`+237 6 …`,
|
|
602
|
+
* `237600000200`, `600000200`). Stored and echoed back **canonical**, so
|
|
603
|
+
* what comes back may not be what was sent — see {@link Customer.phone}.
|
|
604
|
+
*/
|
|
605
|
+
phone?: string | undefined;
|
|
606
|
+
/**
|
|
607
|
+
* The payer's postal address, sent as `address[line1]=…`. An address alone
|
|
608
|
+
* does not name anybody, so it does not satisfy the one-of rule above: a
|
|
609
|
+
* create carrying only an address is refused exactly as one carrying
|
|
610
|
+
* nothing is.
|
|
611
|
+
*/
|
|
612
|
+
address?: AddressParams | undefined;
|
|
613
|
+
metadata?: Record<string, string> | undefined;
|
|
614
|
+
}
|
|
615
|
+
/**
|
|
616
|
+
* The address components, in the shape the wire spells them — Stripe's six
|
|
617
|
+
* **and** the GPS point.
|
|
618
|
+
*
|
|
619
|
+
* A `type` alias rather than an `interface` for {@link ListParams}' reason:
|
|
620
|
+
* only this form is assignable to the form encoder's
|
|
621
|
+
* `Record<string, FormValue>` without a cast.
|
|
622
|
+
*
|
|
623
|
+
* Separate from {@link Address} — which the *response* carries — because what
|
|
624
|
+
* a merchant may send and what the server returns are two contracts, and the
|
|
625
|
+
* second one grows keys the first must not accept. They happen to have the
|
|
626
|
+
* same eight fields today. This SDK deliberately does not validate `country`
|
|
627
|
+
* locally, exactly as it does not validate an MSISDN: which codes vpay
|
|
628
|
+
* accepts is a rule vpay owns and may widen. The same line is taken on the
|
|
629
|
+
* coordinate's range and its pair rule.
|
|
630
|
+
*/
|
|
631
|
+
export type AddressParams = {
|
|
632
|
+
line1?: string | undefined;
|
|
633
|
+
line2?: string | undefined;
|
|
634
|
+
city?: string | undefined;
|
|
635
|
+
state?: string | undefined;
|
|
636
|
+
postal_code?: string | undefined;
|
|
637
|
+
country?: string | undefined;
|
|
638
|
+
/**
|
|
639
|
+
* Latitude in **microdegrees** — `4061000` for 4.061°N, never `4.061`. The
|
|
640
|
+
* range (±90,000,000) and the rule that it is sent with
|
|
641
|
+
* {@link AddressParams.longitude_microdeg} or not at all are the server's,
|
|
642
|
+
* and are not checked here for the reason `country` is not: they are
|
|
643
|
+
* vpay's to widen, and a copy in this SDK would refuse offline an address a
|
|
644
|
+
* later server accepts.
|
|
645
|
+
*/
|
|
646
|
+
latitude_microdeg?: number | undefined;
|
|
647
|
+
/** Longitude in **microdegrees**. See {@link AddressParams.latitude_microdeg}. */
|
|
648
|
+
longitude_microdeg?: number | undefined;
|
|
649
|
+
};
|
|
650
|
+
/**
|
|
651
|
+
* `POST /v1/customers/{id}` request fields (S4a).
|
|
652
|
+
*
|
|
653
|
+
* # Why the three scalars are `string | null | undefined`
|
|
654
|
+
*
|
|
655
|
+
* An update has three answers per field and a merchant depends on all three:
|
|
656
|
+
* *leave it alone* (`undefined`, or the key absent), *set it* (a string), and
|
|
657
|
+
* **clear it** (`null`, which this SDK sends as `name=`). A `string |
|
|
658
|
+
* undefined` field collapses the first and the third, and a payer's email
|
|
659
|
+
* would then be unclearable through the API that documents how to clear it.
|
|
660
|
+
*
|
|
661
|
+
* It is the same three-state shape `UpdateCustomerParams` carries in
|
|
662
|
+
* `sdks/rust` (`Option<Option<String>>`) and `vpay_db::CustomerPatch` carries
|
|
663
|
+
* on the server, so all three layers express the distinction rather than two
|
|
664
|
+
* of them preserving it and one losing it.
|
|
665
|
+
*
|
|
666
|
+
* `metadata` has two states rather than three, and that is the wire contract:
|
|
667
|
+
* it is **merged** key-wise by the server, and a key whose value is the empty
|
|
668
|
+
* string is removed. So there is no separate "clear all metadata" — send each
|
|
669
|
+
* key empty.
|
|
670
|
+
*/
|
|
671
|
+
export interface UpdateCustomerParams {
|
|
672
|
+
name?: string | null | undefined;
|
|
673
|
+
email?: string | null | undefined;
|
|
674
|
+
phone?: string | null | undefined;
|
|
675
|
+
/**
|
|
676
|
+
* The address, in the same three states — with one difference worth
|
|
677
|
+
* stating, because it is the one a merchant can get wrong silently.
|
|
678
|
+
*
|
|
679
|
+
* `undefined` leaves the stored address alone. `null` removes it (sent as
|
|
680
|
+
* `address=`). An object **replaces** it whole: every component the request
|
|
681
|
+
* does not name is cleared, so correcting a street means sending the city
|
|
682
|
+
* again. vpay does not merge components, deliberately — an address
|
|
683
|
+
* assembled out of two requests is an address that was never anybody's, and
|
|
684
|
+
* its failure mode is a plausible wrong address rather than a visible one.
|
|
685
|
+
*/
|
|
686
|
+
address?: AddressParams | null | undefined;
|
|
687
|
+
metadata?: Record<string, string> | undefined;
|
|
688
|
+
}
|
|
689
|
+
/**
|
|
690
|
+
* `GET /v1/customers` query parameters.
|
|
691
|
+
*
|
|
692
|
+
* There is no `email` filter, and that is the server's shape rather than an
|
|
693
|
+
* omission here: a filter on a payer identifier turns the list into a lookup.
|
|
694
|
+
* See `docs/flows/customers.md`.
|
|
695
|
+
*/
|
|
696
|
+
export type ListCustomersParams = {
|
|
697
|
+
limit?: number | undefined;
|
|
698
|
+
starting_after?: string | undefined;
|
|
699
|
+
ending_before?: string | undefined;
|
|
700
|
+
};
|
|
701
|
+
/**
|
|
702
|
+
* An invoice's lifecycle state (S4b).
|
|
703
|
+
*
|
|
704
|
+
* Exactly the five labels the server's `invoices_status_enum_check` allows,
|
|
705
|
+
* and the transitions between them are the server's: `draft → open → paid |
|
|
706
|
+
* void | uncollectible`. A closed union rather than `string`, and
|
|
707
|
+
* `sdks/rust` spells the same five as a closed `enum` — a sixth label is a
|
|
708
|
+
* schema migration and a breaking wire change, not something a merchant's
|
|
709
|
+
* deployment can meet by surprise. That is the opposite of the split
|
|
710
|
+
* {@link KnownEventType} makes, and deliberately so.
|
|
711
|
+
*
|
|
712
|
+
* `"uncollectible"` is the one a merchant filters on: a write-off emits **no
|
|
713
|
+
* event**, so `invoices.list({ status: "uncollectible" })` is how they are
|
|
714
|
+
* found.
|
|
715
|
+
*/
|
|
716
|
+
export type InvoiceStatus = "draft" | "open" | "paid" | "void" | "uncollectible";
|
|
717
|
+
/**
|
|
718
|
+
* One line on an invoice — the `line_item` object.
|
|
719
|
+
*
|
|
720
|
+
* # Why the type is `InvoiceLine` and the resource is `client.invoiceItems`
|
|
721
|
+
*
|
|
722
|
+
* The wire has one object under two names and both are load-bearing: the
|
|
723
|
+
* `object` field is `"line_item"` and the route that creates, reads, changes
|
|
724
|
+
* and removes it is `/v1/invoice_items`. Stripe has two objects there
|
|
725
|
+
* (`invoiceitem` and `line_item`); vpay has one. This type is named for the
|
|
726
|
+
* object it decodes and the resource for the route it calls, so neither name
|
|
727
|
+
* is invented — `sdks/rust` spells them `InvoiceLine` and
|
|
728
|
+
* `client.invoice_items()` for the same reason.
|
|
729
|
+
*
|
|
730
|
+
* No price, no product, no proration and no period: a line is a description,
|
|
731
|
+
* a quantity and a unit amount.
|
|
732
|
+
*/
|
|
733
|
+
export interface InvoiceLine {
|
|
734
|
+
/** `ii_…` — the id `/v1/invoice_items/{id}` addresses. */
|
|
735
|
+
id: string;
|
|
736
|
+
/** Always `"line_item"`, and **not** `"invoice_item"`. See above. */
|
|
737
|
+
object: "line_item";
|
|
738
|
+
description: string;
|
|
739
|
+
quantity: number;
|
|
740
|
+
/** The price of one, in integer minor units. */
|
|
741
|
+
unit_amount: number;
|
|
742
|
+
/**
|
|
743
|
+
* `quantity * unit_amount`, in integer minor units.
|
|
744
|
+
*
|
|
745
|
+
* Computed and checked by the database, never sent: `amount` is not a
|
|
746
|
+
* parameter on {@link CreateInvoiceItemParams} or
|
|
747
|
+
* {@link UpdateInvoiceItemParams}, ever.
|
|
748
|
+
*/
|
|
749
|
+
amount: number;
|
|
750
|
+
/** Lower-case ISO-4217, always the parent invoice's. */
|
|
751
|
+
currency: string;
|
|
752
|
+
livemode: boolean;
|
|
753
|
+
}
|
|
754
|
+
/**
|
|
755
|
+
* When each of an invoice's transitions happened, in unix **seconds**.
|
|
756
|
+
*
|
|
757
|
+
* Every field is `null` until the transition happens, and at most two are
|
|
758
|
+
* ever non-null — `finalized_at` plus whichever terminal one applies.
|
|
759
|
+
*/
|
|
760
|
+
export interface InvoiceStatusTransitions {
|
|
761
|
+
finalized_at: number | null;
|
|
762
|
+
paid_at: number | null;
|
|
763
|
+
voided_at: number | null;
|
|
764
|
+
marked_uncollectible_at: number | null;
|
|
765
|
+
}
|
|
766
|
+
/**
|
|
767
|
+
* An `invoice` (S4b): a merchant's bill to one customer. **Eighteen keys.**
|
|
768
|
+
*
|
|
769
|
+
* # `lines` is always expanded, and always unpaged
|
|
770
|
+
*
|
|
771
|
+
* vpay does not implement Stripe's `expand[]` at all, and an invoice without
|
|
772
|
+
* its lines is a total with no explanation — so `lines` arrives populated on
|
|
773
|
+
* every `/v1/invoices` response, with `has_more` always `false`. Its `url` is
|
|
774
|
+
* `/v1/invoice_items`, a route that exists, rather than Stripe's
|
|
775
|
+
* `/v1/invoices/{id}/lines`, which vpay does not serve.
|
|
776
|
+
*
|
|
777
|
+
* **Inside an `invoice.*` webhook body `lines.data` is empty**, and that is
|
|
778
|
+
* the one place the shape differs: the event is rendered inside the
|
|
779
|
+
* transition's own transaction. Re-read the invoice with
|
|
780
|
+
* `client.invoices.retrieve` if a handler needs the lines.
|
|
781
|
+
*
|
|
782
|
+
* # `hosted_invoice_url` is a checkout session, not an invoice page
|
|
783
|
+
*
|
|
784
|
+
* vpay has no invoice page. A payer following it meets the existing hosted
|
|
785
|
+
* checkout, and it is `null` until `client.invoices.pay` has run.
|
|
786
|
+
*/
|
|
787
|
+
export interface Invoice {
|
|
788
|
+
/** `in_…`. The id an API call uses; {@link Invoice.number} is the one a human quotes. */
|
|
789
|
+
id: string;
|
|
790
|
+
object: "invoice";
|
|
791
|
+
/**
|
|
792
|
+
* The `cus_…` this invoice bills.
|
|
793
|
+
*
|
|
794
|
+
* `string` and not `string | null`, unlike {@link PaymentIntent.customer},
|
|
795
|
+
* and that is the wire contract rather than an assumption: the column is
|
|
796
|
+
* `NOT NULL`, because an invoice naming no payer is one nobody can be asked
|
|
797
|
+
* to pay.
|
|
798
|
+
*/
|
|
799
|
+
customer: string;
|
|
800
|
+
currency: string;
|
|
801
|
+
status: InvoiceStatus;
|
|
802
|
+
/**
|
|
803
|
+
* `{prefix}-{000001}` from this merchant's own sequence, or `null`
|
|
804
|
+
* **exactly** while the invoice is a `"draft"`.
|
|
805
|
+
*
|
|
806
|
+
* Assigned at finalize, never reused, and kept by a voided invoice.
|
|
807
|
+
*/
|
|
808
|
+
number: string | null;
|
|
809
|
+
/** The total, in integer minor units. Frozen at finalize. */
|
|
810
|
+
amount_due: number;
|
|
811
|
+
/** `0`, or {@link Invoice.amount_due}. Never between the two. */
|
|
812
|
+
amount_paid: number;
|
|
813
|
+
/** `amount_due - amount_paid`, and what `pay` mints an intent for. */
|
|
814
|
+
amount_remaining: number;
|
|
815
|
+
/**
|
|
816
|
+
* What has been given back out of {@link Invoice.amount_paid}, as a
|
|
817
|
+
* **gross** total.
|
|
818
|
+
*
|
|
819
|
+
* It is not subtracted from `amount_paid` and does not raise
|
|
820
|
+
* `amount_remaining`: a refunded invoice is still `"paid"` with nothing
|
|
821
|
+
* remaining, and vpay has no credit note object. `0` on every invoice today
|
|
822
|
+
* — no vpay rail can refund yet, so nothing can move it. Do not read a
|
|
823
|
+
* non-zero value as money being owed again.
|
|
824
|
+
*
|
|
825
|
+
* Optional in the type so a client of this version keeps compiling against
|
|
826
|
+
* a server that predates migration `0042`, where the key is absent.
|
|
827
|
+
*/
|
|
828
|
+
amount_refunded?: number;
|
|
829
|
+
/**
|
|
830
|
+
* Unix **seconds**, or `null`. **Advisory**: nothing in vpay reads it —
|
|
831
|
+
* there is no dunning, no reminder and no automatic transition.
|
|
832
|
+
*/
|
|
833
|
+
due_date: number | null;
|
|
834
|
+
description: string | null;
|
|
835
|
+
metadata: Record<string, string>;
|
|
836
|
+
/** The `pi_…` paying, or that paid, this invoice — `null` until `pay`. */
|
|
837
|
+
payment_intent: string | null;
|
|
838
|
+
/** The checkout session for {@link Invoice.payment_intent}. See above. */
|
|
839
|
+
hosted_invoice_url: string | null;
|
|
840
|
+
/** Every line, in the order they were added. See above for when this is empty. */
|
|
841
|
+
lines: List<InvoiceLine>;
|
|
842
|
+
status_transitions: InvoiceStatusTransitions;
|
|
843
|
+
/** Unix **seconds** — not milliseconds, and not RFC 3339. */
|
|
844
|
+
created: number;
|
|
845
|
+
livemode: boolean;
|
|
846
|
+
}
|
|
847
|
+
/**
|
|
848
|
+
* What `client.invoices.del(…)` answers with — Stripe's deleted-object shape.
|
|
849
|
+
*
|
|
850
|
+
* A **draft** is deleted and an issued invoice is voided: a voided draft
|
|
851
|
+
* would be a non-draft row with no number, a state the database refuses
|
|
852
|
+
* outright.
|
|
853
|
+
*/
|
|
854
|
+
export interface DeletedInvoice {
|
|
855
|
+
id: string;
|
|
856
|
+
object: "invoice";
|
|
857
|
+
deleted: true;
|
|
858
|
+
}
|
|
859
|
+
/**
|
|
860
|
+
* What `client.invoiceItems.del(…)` answers with.
|
|
861
|
+
*
|
|
862
|
+
* Its `object` is `"line_item"` and not `"invoice_item"`, matching
|
|
863
|
+
* {@link InvoiceLine.object} — the route and the object are named
|
|
864
|
+
* differently and both spellings are the wire's.
|
|
865
|
+
*/
|
|
866
|
+
export interface DeletedInvoiceItem {
|
|
867
|
+
id: string;
|
|
868
|
+
object: "line_item";
|
|
869
|
+
deleted: true;
|
|
870
|
+
}
|
|
871
|
+
/**
|
|
872
|
+
* `POST /v1/invoices` request fields (S4b).
|
|
873
|
+
*
|
|
874
|
+
* `customer` is the only required one, and it is required for a reason worth
|
|
875
|
+
* stating: an invoice is a bill to somebody, and one that names no payer is
|
|
876
|
+
* one nobody can be asked to pay. The server answers `400` naming `customer`
|
|
877
|
+
* for an absent, unknown *or other merchant's* `cus_…` — always the same
|
|
878
|
+
* sentence, so the parameter cannot be used to discover which customers exist
|
|
879
|
+
* under some other account.
|
|
880
|
+
*
|
|
881
|
+
* A created invoice is always a `"draft"` with no number, no lines and zero
|
|
882
|
+
* amounts. Add lines with `client.invoiceItems.create`, then issue it with
|
|
883
|
+
* `client.invoices.finalize`.
|
|
884
|
+
*/
|
|
885
|
+
export interface CreateInvoiceParams {
|
|
886
|
+
/** The `cus_…` this invoice bills. */
|
|
887
|
+
customer: string;
|
|
888
|
+
/**
|
|
889
|
+
* Lower-cased at encode time regardless of how it was supplied, as an
|
|
890
|
+
* intent's is.
|
|
891
|
+
*
|
|
892
|
+
* **Required**, as it is on an intent. This field was optional until
|
|
893
|
+
* 2026-09-08 and documented as letting "the server apply this deployment's
|
|
894
|
+
* own default"; measured against a running vpay, a create with no
|
|
895
|
+
* `currency` is `400 A three-letter \`currency\` code is required.` —
|
|
896
|
+
* there is no such default. A type that can express only sendable requests
|
|
897
|
+
* is why {@link CreatePaymentIntentParams.currency} is required too.
|
|
898
|
+
*/
|
|
899
|
+
currency: string;
|
|
900
|
+
/** At most 1000 characters, which the server checks. */
|
|
901
|
+
description?: string | undefined;
|
|
902
|
+
/**
|
|
903
|
+
* Unix **seconds**, as Stripe spells it — not milliseconds.
|
|
904
|
+
*
|
|
905
|
+
* **Advisory**: nothing in vpay reads it, so setting this changes nothing
|
|
906
|
+
* about what vpay does. It is a field the merchant's own systems may read
|
|
907
|
+
* back off {@link Invoice.due_date}.
|
|
908
|
+
*/
|
|
909
|
+
due_date?: number | undefined;
|
|
910
|
+
metadata?: Record<string, string> | undefined;
|
|
911
|
+
}
|
|
912
|
+
/**
|
|
913
|
+
* `POST /v1/invoices/{id}` request fields (S4b) — **draft only**.
|
|
914
|
+
*
|
|
915
|
+
* # Three states per field, exactly as {@link UpdateCustomerParams} has
|
|
916
|
+
*
|
|
917
|
+
* `undefined` (or an absent key) leaves the field alone, a value sets it, and
|
|
918
|
+
* **`null` clears it** — which this SDK sends as `description=`. Collapsing
|
|
919
|
+
* `null` and `undefined` makes a due date set by mistake unremovable, and
|
|
920
|
+
* nothing else notices. `sdks/rust` spells the same three states
|
|
921
|
+
* `Option<Option<T>>`.
|
|
922
|
+
*
|
|
923
|
+
* `customer` and `currency` are **not** patchable and deliberately absent
|
|
924
|
+
* from this type: an invoice that changed who it bills, or what it is
|
|
925
|
+
* denominated in, is a different document.
|
|
926
|
+
*
|
|
927
|
+
* `metadata` has two states rather than three and that is the wire contract:
|
|
928
|
+
* it is merged key-wise, and a key whose value is the empty string is
|
|
929
|
+
* removed.
|
|
930
|
+
*/
|
|
931
|
+
export interface UpdateInvoiceParams {
|
|
932
|
+
description?: string | null | undefined;
|
|
933
|
+
/** Unix **seconds**. `null` clears it. */
|
|
934
|
+
due_date?: number | null | undefined;
|
|
935
|
+
metadata?: Record<string, string> | undefined;
|
|
936
|
+
}
|
|
937
|
+
/**
|
|
938
|
+
* `GET /v1/invoices` query parameters (S4b).
|
|
939
|
+
*
|
|
940
|
+
* A `type` alias for {@link ListParams}' index-signature reason.
|
|
941
|
+
*/
|
|
942
|
+
export type ListInvoicesParams = {
|
|
943
|
+
limit?: number | undefined;
|
|
944
|
+
starting_after?: string | undefined;
|
|
945
|
+
ending_before?: string | undefined;
|
|
946
|
+
customer?: string | undefined;
|
|
947
|
+
/**
|
|
948
|
+
* Only invoices in this state.
|
|
949
|
+
*
|
|
950
|
+
* Typed as the closed {@link InvoiceStatus} rather than `string`, and this
|
|
951
|
+
* is the one filter where that earns its place: the server answers `400`
|
|
952
|
+
* naming `status` for a label it does not have rather than silently
|
|
953
|
+
* returning the whole first page, so a typo would cost a round trip and
|
|
954
|
+
* read like an empty result.
|
|
955
|
+
*/
|
|
956
|
+
status?: InvoiceStatus | undefined;
|
|
957
|
+
};
|
|
958
|
+
/**
|
|
959
|
+
* `POST /v1/invoices/{id}/pay` request fields (S4b).
|
|
960
|
+
*
|
|
961
|
+
* **Both URLs are required**, unlike on a checkout session where the pair is
|
|
962
|
+
* required only in hosted mode: `pay` mints a *hosted* session and there is
|
|
963
|
+
* no other mode to be in. They take the same rules a session's do — http(s),
|
|
964
|
+
* at most 2048 characters, `https` only under livemode, and `success_url` may
|
|
965
|
+
* carry the literal `{CHECKOUT_SESSION_ID}` — and this SDK deliberately does
|
|
966
|
+
* not duplicate them, for {@link CreateCheckoutSessionParams}' reason.
|
|
967
|
+
*/
|
|
968
|
+
export type PayInvoiceParams = {
|
|
969
|
+
success_url: string;
|
|
970
|
+
cancel_url: string;
|
|
971
|
+
};
|
|
972
|
+
/**
|
|
973
|
+
* `POST /v1/invoice_items` request fields (S4b).
|
|
974
|
+
*
|
|
975
|
+
* There is no `currency` parameter and no `amount` one, and both absences are
|
|
976
|
+
* the wire contract rather than an omission here: a line is always in its
|
|
977
|
+
* invoice's currency (copied off the parent in the statement that writes the
|
|
978
|
+
* row), and `amount` is `quantity * unit_amount` computed by the database. A
|
|
979
|
+
* merchant who could send `amount` could send one that did not match its own
|
|
980
|
+
* factors.
|
|
981
|
+
*/
|
|
982
|
+
export interface CreateInvoiceItemParams {
|
|
983
|
+
/**
|
|
984
|
+
* The `in_…` to add this line to, and it must be one of your **drafts** —
|
|
985
|
+
* a `400` naming `invoice` otherwise, including for a finalized invoice and
|
|
986
|
+
* for another merchant's.
|
|
987
|
+
*/
|
|
988
|
+
invoice: string;
|
|
989
|
+
/** At most 1000 characters. */
|
|
990
|
+
description: string;
|
|
991
|
+
/**
|
|
992
|
+
* At least 1. Omitted from the body when absent, so the server applies its
|
|
993
|
+
* own default of 1 rather than this SDK sending one.
|
|
994
|
+
*/
|
|
995
|
+
quantity?: number | undefined;
|
|
996
|
+
/**
|
|
997
|
+
* The price of one, in integer minor units. A non-integer, a negative, or
|
|
998
|
+
* anything past `Number.MAX_SAFE_INTEGER` throws `TypeError` before any
|
|
999
|
+
* request — `sdks/rust` refuses the identical set.
|
|
1000
|
+
*/
|
|
1001
|
+
unit_amount: number;
|
|
1002
|
+
}
|
|
1003
|
+
/**
|
|
1004
|
+
* `POST /v1/invoice_items/{id}` request fields (S4b) — **draft parent only**.
|
|
1005
|
+
*
|
|
1006
|
+
* # Why these are two-state and {@link UpdateInvoiceParams}' are three-state
|
|
1007
|
+
*
|
|
1008
|
+
* All three columns are `NOT NULL`, so there is no "clear it" state to carry:
|
|
1009
|
+
* an absent key leaves the field alone and a value sets it. Sending
|
|
1010
|
+
* `description=` is a `400` naming the parameter rather than a clear, which
|
|
1011
|
+
* is why this type cannot express it — no `| null`.
|
|
1012
|
+
*
|
|
1013
|
+
* `amount` is not here, ever — see {@link CreateInvoiceItemParams}.
|
|
1014
|
+
*/
|
|
1015
|
+
export interface UpdateInvoiceItemParams {
|
|
1016
|
+
description?: string | undefined;
|
|
1017
|
+
quantity?: number | undefined;
|
|
1018
|
+
unit_amount?: number | undefined;
|
|
1019
|
+
}
|
|
1020
|
+
export interface CreatePaymentIntentParams {
|
|
1021
|
+
/**
|
|
1022
|
+
* Integer minor units. A non-integer, a negative, or anything past
|
|
1023
|
+
* `Number.MAX_SAFE_INTEGER` throws `TypeError` before any request.
|
|
1024
|
+
*/
|
|
1025
|
+
amount: number;
|
|
1026
|
+
currency: string;
|
|
1027
|
+
payment_method_types: PaymentMethodType[];
|
|
1028
|
+
metadata?: Record<string, string> | undefined;
|
|
1029
|
+
description?: string | undefined;
|
|
1030
|
+
/**
|
|
1031
|
+
* The `cus_…` this intent is for (S4a).
|
|
1032
|
+
*
|
|
1033
|
+
* Accepted and **dropped** by any vpay predating 2026-09-06, which is worth
|
|
1034
|
+
* knowing before relying on it: an older server answers `200` with
|
|
1035
|
+
* `customer: null` rather than refusing. Check the response.
|
|
1036
|
+
*/
|
|
1037
|
+
customer?: string | undefined;
|
|
1038
|
+
}
|
|
1039
|
+
/** A `type` alias for the same index-signature reason as {@link ListParams}. */
|
|
1040
|
+
export type MtnMomoPaymentMethodData = {
|
|
1041
|
+
type: "mtn_momo";
|
|
1042
|
+
mtn_momo: {
|
|
1043
|
+
msisdn: string;
|
|
1044
|
+
};
|
|
1045
|
+
};
|
|
1046
|
+
/** A `type` alias for the same index-signature reason as {@link ListParams}. */
|
|
1047
|
+
export type OrangeMoneyPaymentMethodData = {
|
|
1048
|
+
type: "orange_money";
|
|
1049
|
+
};
|
|
1050
|
+
export type ConfirmPaymentIntentParams = {
|
|
1051
|
+
payment_method_data: MtnMomoPaymentMethodData;
|
|
1052
|
+
} | {
|
|
1053
|
+
payment_method_data: OrangeMoneyPaymentMethodData;
|
|
1054
|
+
return_url: string;
|
|
1055
|
+
};
|
|
1056
|
+
/**
|
|
1057
|
+
* `POST /v1/checkout/sessions` request fields.
|
|
1058
|
+
*
|
|
1059
|
+
* The URL rules are the server's and are not duplicated here: `success_url`
|
|
1060
|
+
* and `cancel_url` are required for `hosted` and refused for `embedded`,
|
|
1061
|
+
* `return_url` the other way round, all http(s) and at most 2048
|
|
1062
|
+
* characters, `https` only under livemode. This SDK sends what it is given
|
|
1063
|
+
* and lets the server say no — a second copy of those rules here would be a
|
|
1064
|
+
* second thing to keep in step with them, and would refuse a combination a
|
|
1065
|
+
* later server version allows.
|
|
1066
|
+
*/
|
|
1067
|
+
export interface CreateCheckoutSessionParams {
|
|
1068
|
+
/** The `pi_…` to drive. Required; the session never creates one. */
|
|
1069
|
+
payment_intent: string;
|
|
1070
|
+
/** Defaults to `hosted` server-side when omitted. */
|
|
1071
|
+
ui_mode?: CheckoutSessionUiMode | undefined;
|
|
1072
|
+
/** Where to send a payer who paid. Hosted mode. May contain `{CHECKOUT_SESSION_ID}`. */
|
|
1073
|
+
success_url?: string | undefined;
|
|
1074
|
+
/** Where to send a payer who gave up. Hosted mode. */
|
|
1075
|
+
cancel_url?: string | undefined;
|
|
1076
|
+
/** Where vpay's framed page forwards the payer at the end. Embedded mode. */
|
|
1077
|
+
return_url?: string | undefined;
|
|
1078
|
+
/**
|
|
1079
|
+
* The `cus_…` this session is for (issue #70), or omit to inherit the
|
|
1080
|
+
* intent's customer — see {@link CheckoutSession.customer}.
|
|
1081
|
+
*
|
|
1082
|
+
* Refused `409` naming `customer` when the session's PaymentIntent already
|
|
1083
|
+
* names a *different* customer; the server never lets the two disagree.
|
|
1084
|
+
*/
|
|
1085
|
+
customer?: string | undefined;
|
|
1086
|
+
}
|
|
1087
|
+
/**
|
|
1088
|
+
* `GET /v1/checkout/sessions` query parameters. A `type` alias for the same
|
|
1089
|
+
* index-signature reason as {@link ListParams}.
|
|
1090
|
+
*/
|
|
1091
|
+
export type ListCheckoutSessionsParams = {
|
|
1092
|
+
limit?: number | undefined;
|
|
1093
|
+
starting_after?: string | undefined;
|
|
1094
|
+
ending_before?: string | undefined;
|
|
1095
|
+
/** Only sessions for this `pi_…`. */
|
|
1096
|
+
payment_intent?: string | undefined;
|
|
1097
|
+
};
|
|
1098
|
+
/**
|
|
1099
|
+
* Where a refund's money goes, on a rail that cannot send it back the way it
|
|
1100
|
+
* came.
|
|
1101
|
+
*
|
|
1102
|
+
* # The rail's code is the outer key, and that is the whole point
|
|
1103
|
+
*
|
|
1104
|
+
* On the wire this is `destination[<payment_method_type>][…]` — a
|
|
1105
|
+
* rail-agnostic container with a rail-specific interior, exactly as
|
|
1106
|
+
* `payment_method_data[<payment_method_type>][…]` is on the confirm path. The
|
|
1107
|
+
* server strips the outer key and hands the interior to that rail's own
|
|
1108
|
+
* adapter, which is the only thing that knows what a payee looks like there.
|
|
1109
|
+
* An SDK that flattened this, or that wrote one rail's code into the body
|
|
1110
|
+
* whatever rail the charge was on, would be deciding a rail's payee shape
|
|
1111
|
+
* from the client.
|
|
1112
|
+
*
|
|
1113
|
+
* The container is built for you: give the rail and the number, and
|
|
1114
|
+
* `refunds.create` writes `destination[<rail>][msisdn]`.
|
|
1115
|
+
*
|
|
1116
|
+
* # Which rail, and why you do not choose it
|
|
1117
|
+
*
|
|
1118
|
+
* A refund goes back on the rail the charge was made on. The server reads
|
|
1119
|
+
* that off the charge and refuses a `destination` naming any other rail with
|
|
1120
|
+
* a `400` naming `destination`, so `payment_method_type` here must be the one
|
|
1121
|
+
* the intent was confirmed on — not a preference.
|
|
1122
|
+
*
|
|
1123
|
+
* # The `+` is required, and this SDK does not add it for you
|
|
1124
|
+
*
|
|
1125
|
+
* The server canonicalises the number and **refuses one that does not start
|
|
1126
|
+
* with `+`**: `"+237600000200"` is a payee and the bare national
|
|
1127
|
+
* `"600000200"` is a `400` naming `destination`. That is not the rule
|
|
1128
|
+
* `GET /v1/account_holders` applies — that route knows it is in Cameroon and
|
|
1129
|
+
* the refund path deliberately does not, because read as an international
|
|
1130
|
+
* number `600000200` names a different country. It is asymmetric in the safe
|
|
1131
|
+
* direction: a lookup that guesses wrong returns the wrong name, a transfer
|
|
1132
|
+
* that guesses wrong sends the money.
|
|
1133
|
+
*
|
|
1134
|
+
* This SDK sends the string it is given, unchanged: it does not add a `+`,
|
|
1135
|
+
* strip one, or rewrite the number into any other form, for the reason
|
|
1136
|
+
* {@link RetrieveAccountHolderParams} gives about validating numbers locally
|
|
1137
|
+
* — the rule is the server's and it may widen it. Separators a caller types
|
|
1138
|
+
* (`"+237 600 000 200"`, `"+237-600-000-200"`) are accepted by the server and
|
|
1139
|
+
* are not this type's business either.
|
|
1140
|
+
*
|
|
1141
|
+
* # Do not log it
|
|
1142
|
+
*
|
|
1143
|
+
* The payee is a third party and the number is their personal data. vpay's
|
|
1144
|
+
* own handler logs it masked and never raw, and renders it in no response and
|
|
1145
|
+
* in no webhook body.
|
|
1146
|
+
*
|
|
1147
|
+
* A single-member union rather than an object type, so that a destination
|
|
1148
|
+
* which is not a phone number is a sibling member — discriminated on `kind`
|
|
1149
|
+
* rather than on the rail, because the rail is a *value* here and not a
|
|
1150
|
+
* shape.
|
|
1151
|
+
*/
|
|
1152
|
+
export type RefundDestination = {
|
|
1153
|
+
/**
|
|
1154
|
+
* The shape of this payee. `"mobile_money"` is the only one today; it is
|
|
1155
|
+
* present so that a future non-phone destination narrows rather than
|
|
1156
|
+
* widening this one.
|
|
1157
|
+
*/
|
|
1158
|
+
kind: "mobile_money";
|
|
1159
|
+
/** The rail the charge was made on — its code becomes the outer key. */
|
|
1160
|
+
payment_method_type: PaymentMethodType;
|
|
1161
|
+
/**
|
|
1162
|
+
* The payee's number, **in international form, starting with `+`**. Sent as
|
|
1163
|
+
* `destination[<payment_method_type>][msisdn]`.
|
|
1164
|
+
*/
|
|
1165
|
+
msisdn: string;
|
|
1166
|
+
};
|
|
1167
|
+
/**
|
|
1168
|
+
* `POST /v1/refunds` request fields.
|
|
1169
|
+
*
|
|
1170
|
+
* # No refund this creates has ever moved money
|
|
1171
|
+
*
|
|
1172
|
+
* The route is real and so is every refusal it makes, but nothing on the
|
|
1173
|
+
* other side of it pays anybody: `orange_money`'s refund is a declared
|
|
1174
|
+
* `NotImplemented` token, `mtn_momo`'s is MTN's Disbursements `transfer`
|
|
1175
|
+
* which is WireMock-proven and for which **no real MTN credential exists in
|
|
1176
|
+
* this project** — the only `disbursement_subscription_key` anywhere is the
|
|
1177
|
+
* stub the e2e/demo stack points at a WireMock container, and the product has
|
|
1178
|
+
* never been called —
|
|
1179
|
+
* and **nothing settles a `pending` refund** — there is no refund poll
|
|
1180
|
+
* ladder, so a refund this creates stays `"pending"` until an operator moves
|
|
1181
|
+
* it. `docs/status.md` carries all three. Do not read a `200` here as money
|
|
1182
|
+
* on its way back.
|
|
1183
|
+
*/
|
|
1184
|
+
export interface CreateRefundParams {
|
|
1185
|
+
payment_intent: string;
|
|
1186
|
+
/**
|
|
1187
|
+
* Integer minor units. Omit for a full refund — `amount=` and no `amount`
|
|
1188
|
+
* are different requests, and only the second means "all of it".
|
|
1189
|
+
*
|
|
1190
|
+
* A rail that refunds in full only refuses a partial amount with a `400`
|
|
1191
|
+
* naming `amount`, and it measures "in full" against the intent's own
|
|
1192
|
+
* amount rather than against what is left.
|
|
1193
|
+
*/
|
|
1194
|
+
amount?: number | undefined;
|
|
1195
|
+
reason?: string | undefined;
|
|
1196
|
+
/**
|
|
1197
|
+
* The payee, on a rail that cannot return money to the instrument that
|
|
1198
|
+
* paid. See {@link RefundDestination}.
|
|
1199
|
+
*
|
|
1200
|
+
* Optional because the port allows a rail that refunds to the instrument
|
|
1201
|
+
* that paid, and such a rail refuses a `destination` with a `400`.
|
|
1202
|
+
* **Neither rail vpay carries today is one**: `mtn_momo` and `orange_money`
|
|
1203
|
+
* both declare that a refund needs a payee, so a create with no
|
|
1204
|
+
* `destination` is a `400` naming `destination` against every deployment
|
|
1205
|
+
* this repository can build.
|
|
1206
|
+
*/
|
|
1207
|
+
destination?: RefundDestination | undefined;
|
|
1208
|
+
metadata?: Record<string, string> | undefined;
|
|
1209
|
+
}
|
|
1210
|
+
/**
|
|
1211
|
+
* `POST /v1/refunds/{id}` request fields — **metadata, and nothing else**.
|
|
1212
|
+
*
|
|
1213
|
+
* There is no `amount`, no `reason`, no `payment_intent` and no `destination`
|
|
1214
|
+
* here, and their absence is this type saying what the server says: all four
|
|
1215
|
+
* are refused with a `400` naming the parameter once a refund exists. A
|
|
1216
|
+
* refund whose amount or payee is wrong is cancelled while it is still
|
|
1217
|
+
* `"pending"` and created again.
|
|
1218
|
+
*
|
|
1219
|
+
* # The merge is key-wise, and an empty value deletes a key
|
|
1220
|
+
*
|
|
1221
|
+
* Stripe's rule, which vpay follows: the map sent here is merged into the
|
|
1222
|
+
* stored one key by key — keys not mentioned are left alone — and a key sent
|
|
1223
|
+
* with an **empty string** value is removed. Sending no `metadata` at all is
|
|
1224
|
+
* a no-op read: the refund comes back unchanged and **no event is written**.
|
|
1225
|
+
*/
|
|
1226
|
+
export interface UpdateRefundParams {
|
|
1227
|
+
/** The keys to set, and the keys to delete (an empty value deletes). */
|
|
1228
|
+
metadata?: Record<string, string> | undefined;
|
|
1229
|
+
}
|
|
1230
|
+
/**
|
|
1231
|
+
* `GET /v1/refunds` query parameters.
|
|
1232
|
+
*
|
|
1233
|
+
* Written flat rather than as `ListParams & { … }` for the same
|
|
1234
|
+
* index-signature reason as {@link ListParams}.
|
|
1235
|
+
*/
|
|
1236
|
+
export type ListRefundsParams = {
|
|
1237
|
+
limit?: number | undefined;
|
|
1238
|
+
/** Cursor: refunds after this `re_…`. */
|
|
1239
|
+
starting_after?: string | undefined;
|
|
1240
|
+
/** Cursor: refunds before this `re_…`. */
|
|
1241
|
+
ending_before?: string | undefined;
|
|
1242
|
+
/**
|
|
1243
|
+
* Only refunds against this `pi_…`.
|
|
1244
|
+
*
|
|
1245
|
+
* Held to the PaymentIntent id shape by the server, which answers `400`
|
|
1246
|
+
* naming `payment_intent` for anything else — a `re_…` sent here is the
|
|
1247
|
+
* easy mistake, since both ids are on the refund object. An id of the right
|
|
1248
|
+
* shape that names nothing, or another merchant's intent, is an **empty
|
|
1249
|
+
* page** and not a `404`.
|
|
1250
|
+
*/
|
|
1251
|
+
payment_intent?: string | undefined;
|
|
1252
|
+
};
|
|
1253
|
+
/**
|
|
1254
|
+
* Written flat rather than as `ListParams & { … }` for the same
|
|
1255
|
+
* index-signature reason as {@link ListParams}: only an anonymous object type
|
|
1256
|
+
* gets an implicit index signature, and an intersection of aliases does not.
|
|
1257
|
+
*/
|
|
1258
|
+
export type ListEventsParams = {
|
|
1259
|
+
limit?: number | undefined;
|
|
1260
|
+
starting_after?: string | undefined;
|
|
1261
|
+
ending_before?: string | undefined;
|
|
1262
|
+
type?: string | undefined;
|
|
1263
|
+
};
|
|
1264
|
+
/** Per-call options accepted by every resource method that issues a `POST`. */
|
|
1265
|
+
export interface RequestOptions {
|
|
1266
|
+
/** Caller-supplied idempotency key; a fresh UUIDv4 is generated when omitted. */
|
|
1267
|
+
idempotencyKey?: string | undefined;
|
|
1268
|
+
}
|