@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.
Files changed (42) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +732 -0
  3. package/dist/auth.d.ts +180 -0
  4. package/dist/auth.js +297 -0
  5. package/dist/client.d.ts +75 -0
  6. package/dist/client.js +79 -0
  7. package/dist/errors.d.ts +91 -0
  8. package/dist/errors.js +112 -0
  9. package/dist/form.d.ts +47 -0
  10. package/dist/form.js +127 -0
  11. package/dist/http.d.ts +33 -0
  12. package/dist/http.js +115 -0
  13. package/dist/index.d.ts +15 -0
  14. package/dist/index.js +14 -0
  15. package/dist/resources/account-holders.d.ts +36 -0
  16. package/dist/resources/account-holders.js +46 -0
  17. package/dist/resources/balance.d.ts +8 -0
  18. package/dist/resources/balance.js +10 -0
  19. package/dist/resources/checkout-sessions.d.ts +55 -0
  20. package/dist/resources/checkout-sessions.js +127 -0
  21. package/dist/resources/customers.d.ts +97 -0
  22. package/dist/resources/customers.js +157 -0
  23. package/dist/resources/events.d.ts +8 -0
  24. package/dist/resources/events.js +10 -0
  25. package/dist/resources/invoices.d.ts +193 -0
  26. package/dist/resources/invoices.js +275 -0
  27. package/dist/resources/payment-intents.d.ts +12 -0
  28. package/dist/resources/payment-intents.js +44 -0
  29. package/dist/resources/refunds.d.ts +92 -0
  30. package/dist/resources/refunds.js +106 -0
  31. package/dist/stripe-auth.d.ts +177 -0
  32. package/dist/stripe-auth.js +192 -0
  33. package/dist/types.d.ts +1268 -0
  34. package/dist/types.js +48 -0
  35. package/dist/validate.d.ts +16 -0
  36. package/dist/validate.js +20 -0
  37. package/dist/version.d.ts +12 -0
  38. package/dist/version.js +12 -0
  39. package/dist/webhooks.d.ts +22 -0
  40. package/dist/webhooks.js +94 -0
  41. package/package.json +63 -0
  42. package/scripts/mint-assertion.mjs +73 -0
@@ -0,0 +1,275 @@
1
+ import { assertIntegerAmount } from "../validate.js";
2
+ /**
3
+ * One patch field, in the wire's own three-state encoding.
4
+ *
5
+ * Returns `undefined` for "the request did not mention this", which the
6
+ * caller drops from the body entirely, and the empty string for "clear it".
7
+ * The twin of `customers.ts`'s `patch`, kept per file rather than shared
8
+ * because the two differ in the value type they carry and merging them would
9
+ * cost a generic that says less than the two lines it replaced.
10
+ */
11
+ function patch(value) {
12
+ if (value === undefined) {
13
+ return undefined;
14
+ }
15
+ return value === null ? "" : value;
16
+ }
17
+ /** `client.invoices` — see the module doc. */
18
+ export class InvoicesResource {
19
+ #http;
20
+ constructor(http) {
21
+ this.#http = http;
22
+ }
23
+ /**
24
+ * `POST /v1/invoices` — creates a **draft**, with no number and zero
25
+ * amounts.
26
+ *
27
+ * Field order is the wire order `sdks/rust/tests/resources.rs` pins byte
28
+ * for byte; an unset field is omitted from the body entirely, because
29
+ * `description=` and no `description` are different requests and only the
30
+ * second means "not applicable".
31
+ *
32
+ * A `400` naming `customer` when it is absent, unknown, or another
33
+ * merchant's — one sentence for all three, so the parameter is not an
34
+ * oracle; and a `400` naming `currency` for one this deployment does not
35
+ * settle.
36
+ */
37
+ async create(params, options) {
38
+ const body = {
39
+ customer: params.customer,
40
+ currency: params.currency.toLowerCase(),
41
+ };
42
+ if (params.description !== undefined) {
43
+ body["description"] = params.description;
44
+ }
45
+ if (params.due_date !== undefined) {
46
+ body["due_date"] = params.due_date;
47
+ }
48
+ if (params.metadata !== undefined) {
49
+ body["metadata"] = params.metadata;
50
+ }
51
+ return this.#http.request("POST", "/invoices", body, options);
52
+ }
53
+ /**
54
+ * `GET /v1/invoices/{id}` — the invoice **with its lines**.
55
+ *
56
+ * This is the read a webhook handler falls back to: an `invoice.*` event
57
+ * body carries an empty `lines.data`, and this response always carries the
58
+ * lines.
59
+ *
60
+ * Another merchant's `in_…` is the same `404`, byte for byte, as one that
61
+ * never existed.
62
+ */
63
+ async retrieve(id) {
64
+ return this.#http.request("GET", `/invoices/${encodeURIComponent(id)}`);
65
+ }
66
+ /**
67
+ * `POST /v1/invoices/{id}` — patches a **draft**.
68
+ *
69
+ * A `POST` and not a `PATCH`, for `customers.update`'s reason, even though
70
+ * the server mounts both verbs on the one handler: `POST` is what a
71
+ * merchant's existing Stripe client sends, so it is the one that has to
72
+ * work.
73
+ *
74
+ * A `409` naming the status once the invoice is no longer a draft — an
75
+ * issued document's terms are what was sent to the payer. Void it and issue
76
+ * a new one.
77
+ */
78
+ async update(id, params, options) {
79
+ const body = {};
80
+ const description = patch(params.description);
81
+ if (description !== undefined) {
82
+ body["description"] = description;
83
+ }
84
+ const dueDate = patch(params.due_date);
85
+ if (dueDate !== undefined) {
86
+ body["due_date"] = dueDate;
87
+ }
88
+ if (params.metadata !== undefined) {
89
+ body["metadata"] = params.metadata;
90
+ }
91
+ return this.#http.request("POST", `/invoices/${encodeURIComponent(id)}`, body, options);
92
+ }
93
+ /** `GET /v1/invoices` — newest first. */
94
+ async list(params) {
95
+ return this.#http.request("GET", "/invoices", params);
96
+ }
97
+ /**
98
+ * `DELETE /v1/invoices/{id}` — **draft only**, and its lines go with it.
99
+ *
100
+ * An issued invoice is {@link InvoicesResource.void}ed, never deleted: a
101
+ * voided draft would be a non-draft row with no number, which the database
102
+ * refuses outright, and a document that vanished is a hole an accountant
103
+ * reads as a destroyed one.
104
+ *
105
+ * `del` and not `delete`, for `customers.del`'s reason. Carries an
106
+ * `Idempotency-Key` like every other write, which is what makes a retried
107
+ * delete answer the original `{deleted: true}` rather than a `404`.
108
+ */
109
+ async del(id, options) {
110
+ return this.#http.request("DELETE", `/invoices/${encodeURIComponent(id)}`, undefined, options);
111
+ }
112
+ /**
113
+ * `POST /v1/invoices/{id}/finalize` — issues the document. **Draft only.**
114
+ *
115
+ * The money transition: the invoice takes the next number out of this
116
+ * merchant's own sequence under a row lock, its lines freeze, its amounts
117
+ * are computed once, and it moves to `"open"` — all in one transaction,
118
+ * with `invoice.finalized` inside it. Numbers are consecutive and have no
119
+ * holes, unlike Stripe's.
120
+ *
121
+ * A `400` naming `invoice` when it has no lines, or when its total is past
122
+ * `Number.MAX_SAFE_INTEGER` minor units; a `409` when it is not a draft.
123
+ */
124
+ async finalize(id, options) {
125
+ return this.#transition(id, "finalize", options);
126
+ }
127
+ /**
128
+ * `POST /v1/invoices/{id}/void` — cancels an issued document. **Open
129
+ * only.**
130
+ *
131
+ * The invoice **keeps its number**. Terminal.
132
+ *
133
+ * A `409` when it is not open, and a `409` while a payment intent is
134
+ * attached and not yet canceled — cancel that intent first.
135
+ */
136
+ async void(id, options) {
137
+ return this.#transition(id, "void", options);
138
+ }
139
+ /**
140
+ * `POST /v1/invoices/{id}/mark_uncollectible` — writes an issued document
141
+ * off. **Open only.**
142
+ *
143
+ * Still owed, never expected. Terminal, and it **emits no event**: vpay
144
+ * does not write `invoice.marked_uncollectible`, so nothing arrives on a
145
+ * webhook and a merchant reconciling write-offs reads them with
146
+ * `list({ status: "uncollectible" })`.
147
+ *
148
+ * # `markUncollectible` here, `mark_uncollectible` in `sdks/rust`
149
+ *
150
+ * The only capability the two SDKs spell differently, and the difference is
151
+ * each language's own casing rather than a divergence: `sdks/rust` may not
152
+ * spell `markUncollectible` (`non_snake_case` is a rustc lint, not a
153
+ * preference) and this package should not spell `mark_uncollectible` —
154
+ * Stripe's own Node SDK, which merchants arrive from, says
155
+ * `markUncollectible`, and the other spelling would be the one snake_case
156
+ * method in a camelCase package.
157
+ *
158
+ * [ADR-0015](../../../docs/adr/0015-sdk-parity.md) decision 1 is that
159
+ * parity is **per capability, not per method name**, and its alternatives
160
+ * reject method-name parity in as many words. Until 2026-09-08
161
+ * `verify-sdk-parity` could not express that — it keyed a row on one
162
+ * spelling — and this method was named `mark_uncollectible` to satisfy the
163
+ * gate. It is `markUncollectible` now because the gate learned the
164
+ * distinction instead: `PARITY_COLUMN_SPELLINGS` in `.xtask/src/main.rs`
165
+ * carries the one entry, and an entry that exempts nothing fails the build.
166
+ */
167
+ async markUncollectible(id, options) {
168
+ return this.#transition(id, "mark_uncollectible", options);
169
+ }
170
+ /**
171
+ * `POST /v1/invoices/{id}/pay` — mints a payment intent and a hosted
172
+ * checkout for what is left. **Open only.**
173
+ *
174
+ * **This does not charge anybody.** Stripe's `pay` charges a stored payment
175
+ * method; vpay has none, so this answers the invoice with
176
+ * {@link Invoice.payment_intent} and {@link Invoice.hosted_invoice_url}
177
+ * set — a page to send the payer to. The invoice stays `"open"` until the
178
+ * settlement moves it, which is when `invoice.paid` is emitted.
179
+ *
180
+ * One payment at a time: calling this again while the attached intent is
181
+ * live is a `409`, and cancelling that intent is the way back. Two
182
+ * concurrent calls attach exactly one intent.
183
+ *
184
+ * A `500 checkout_not_configured` when this deployment serves no checkout
185
+ * page.
186
+ */
187
+ async pay(id, params, options) {
188
+ return this.#http.request("POST", `/invoices/${encodeURIComponent(id)}/pay`, { success_url: params.success_url, cancel_url: params.cancel_url }, options);
189
+ }
190
+ /**
191
+ * The three transitions that take no parameters, which are one shape:
192
+ * a `POST` with an **empty body** to the invoice's own sub-path, carrying
193
+ * an `Idempotency-Key` like every other write.
194
+ *
195
+ * Private, so it is not a capability the parity gate has to record — and
196
+ * written once rather than three times, because the empty body is the part
197
+ * that is easy to get wrong in only one of them.
198
+ */
199
+ async #transition(id, name, options) {
200
+ return this.#http.request("POST", `/invoices/${encodeURIComponent(id)}/${name}`, {}, options);
201
+ }
202
+ }
203
+ /**
204
+ * `client.invoiceItems` — the four operations on `/v1/invoice_items`: the
205
+ * lines of a **draft** invoice.
206
+ *
207
+ * There is deliberately no `list`: an invoice's lines are read off the
208
+ * invoice ({@link Invoice.lines}, expanded on every render), and a
209
+ * merchant-wide list of lines across invoices answers no question anybody
210
+ * asked. The server mounts no collection `GET` either, so this is not an
211
+ * omission here.
212
+ *
213
+ * Every write requires the parent to still be a `"draft"`;
214
+ * {@link InvoiceItemsResource.retrieve} does not.
215
+ */
216
+ export class InvoiceItemsResource {
217
+ #http;
218
+ constructor(http) {
219
+ this.#http = http;
220
+ }
221
+ /**
222
+ * `POST /v1/invoice_items` — adds a line to a draft, and the invoice's
223
+ * `amount_due` moves with it.
224
+ *
225
+ * Throws `TypeError` before any request when `unit_amount` is not a
226
+ * non-negative safe integer; a `400` naming `invoice` when it is not one of
227
+ * your drafts.
228
+ */
229
+ async create(params, options) {
230
+ assertIntegerAmount(params.unit_amount, "unit_amount");
231
+ const body = {
232
+ invoice: params.invoice,
233
+ description: params.description,
234
+ };
235
+ if (params.quantity !== undefined) {
236
+ body["quantity"] = params.quantity;
237
+ }
238
+ body["unit_amount"] = params.unit_amount;
239
+ return this.#http.request("POST", "/invoice_items", body, options);
240
+ }
241
+ /** `GET /v1/invoice_items/{id}` — readable whatever the parent's status. */
242
+ async retrieve(id) {
243
+ return this.#http.request("GET", `/invoice_items/${encodeURIComponent(id)}`);
244
+ }
245
+ /**
246
+ * `POST /v1/invoice_items/{id}` — **draft parent only**, with `amount`
247
+ * recomputed.
248
+ *
249
+ * Throws `TypeError` before any request for a `unit_amount` outside the
250
+ * bound; a `409` once the parent is no longer a draft.
251
+ */
252
+ async update(id, params, options) {
253
+ if (params.unit_amount !== undefined) {
254
+ assertIntegerAmount(params.unit_amount, "unit_amount");
255
+ }
256
+ const body = {};
257
+ if (params.description !== undefined) {
258
+ body["description"] = params.description;
259
+ }
260
+ if (params.quantity !== undefined) {
261
+ body["quantity"] = params.quantity;
262
+ }
263
+ if (params.unit_amount !== undefined) {
264
+ body["unit_amount"] = params.unit_amount;
265
+ }
266
+ return this.#http.request("POST", `/invoice_items/${encodeURIComponent(id)}`, body, options);
267
+ }
268
+ /**
269
+ * `DELETE /v1/invoice_items/{id}` — **draft parent only**, and the invoice
270
+ * re-totals.
271
+ */
272
+ async del(id, options) {
273
+ return this.#http.request("DELETE", `/invoice_items/${encodeURIComponent(id)}`, undefined, options);
274
+ }
275
+ }
@@ -0,0 +1,12 @@
1
+ import type { HttpClient } from "../http.js";
2
+ import type { ConfirmPaymentIntentParams, CreatePaymentIntentParams, List, ListParams, PaymentIntent, RequestOptions } from "../types.js";
3
+ /** `/v1/payment_intents` — docs/flows/merchant-auth.md, "Resources". */
4
+ export declare class PaymentIntentsResource {
5
+ #private;
6
+ constructor(http: HttpClient);
7
+ create(params: CreatePaymentIntentParams, options?: RequestOptions): Promise<PaymentIntent>;
8
+ retrieve(id: string): Promise<PaymentIntent>;
9
+ confirm(id: string, params: ConfirmPaymentIntentParams, options?: RequestOptions): Promise<PaymentIntent>;
10
+ cancel(id: string, options?: RequestOptions): Promise<PaymentIntent>;
11
+ list(params?: ListParams): Promise<List<PaymentIntent>>;
12
+ }
@@ -0,0 +1,44 @@
1
+ import { assertIntegerAmount } from "../validate.js";
2
+ /** `/v1/payment_intents` — docs/flows/merchant-auth.md, "Resources". */
3
+ export class PaymentIntentsResource {
4
+ #http;
5
+ constructor(http) {
6
+ this.#http = http;
7
+ }
8
+ async create(params, options) {
9
+ assertIntegerAmount(params.amount);
10
+ const body = {
11
+ amount: params.amount,
12
+ currency: params.currency.toLowerCase(),
13
+ payment_method_types: params.payment_method_types,
14
+ };
15
+ if (params.metadata !== undefined) {
16
+ body["metadata"] = params.metadata;
17
+ }
18
+ if (params.description !== undefined) {
19
+ body["description"] = params.description;
20
+ }
21
+ if (params.customer !== undefined) {
22
+ body["customer"] = params.customer;
23
+ }
24
+ return this.#http.request("POST", "/payment_intents", body, options);
25
+ }
26
+ async retrieve(id) {
27
+ return this.#http.request("GET", `/payment_intents/${encodeURIComponent(id)}`);
28
+ }
29
+ async confirm(id, params, options) {
30
+ const body = {
31
+ payment_method_data: params.payment_method_data,
32
+ };
33
+ if ("return_url" in params) {
34
+ body["return_url"] = params.return_url;
35
+ }
36
+ return this.#http.request("POST", `/payment_intents/${encodeURIComponent(id)}/confirm`, body, options);
37
+ }
38
+ async cancel(id, options) {
39
+ return this.#http.request("POST", `/payment_intents/${encodeURIComponent(id)}/cancel`, {}, options);
40
+ }
41
+ async list(params) {
42
+ return this.#http.request("GET", "/payment_intents", params);
43
+ }
44
+ }
@@ -0,0 +1,92 @@
1
+ /**
2
+ * `/v1/refunds` — the five merchant operations on the Refund object
3
+ * (RFC-0003 § 2).
4
+ *
5
+ * # No refund any of these creates has ever moved money
6
+ *
7
+ * Read this before any sentence elsewhere that says a refund "works". The
8
+ * routes are real and so is every refusal they make, but nothing on the other
9
+ * side of them pays anybody:
10
+ *
11
+ * - `orange_money`'s refund is a declared `NotImplemented` token — an Orange
12
+ * refund is an outbound transfer this repository has no specification for;
13
+ * - `mtn_momo`'s is MTN's Disbursements `transfer`, WireMock-proven and
14
+ * rail-unproven: **no real MTN Disbursements credential exists in this
15
+ * project** — the only `disbursement_subscription_key` anywhere is the stub
16
+ * the e2e/demo stack points at a WireMock container — and the product has
17
+ * never been called;
18
+ * - **nothing settles a `pending` refund.** There is no refund poll ladder,
19
+ * so a refund created here stays `"pending"` until an operator or the
20
+ * settlement path moves it.
21
+ *
22
+ * `docs/status.md` carries all three. A `200` from
23
+ * {@link RefundsResource.create} means a refund row exists and its amount is
24
+ * reserved against the intent. It does not mean the payee has been paid.
25
+ *
26
+ * # The state machine is the server's `WHERE` clause
27
+ *
28
+ * {@link RefundsResource.cancel} works while the refund is `"pending"` and
29
+ * answers a `409` naming the current status otherwise. This SDK does not try
30
+ * to predict which — reading the refund first to decide whether to call is a
31
+ * race. Call, and read the refusal.
32
+ */
33
+ import type { HttpClient } from "../http.js";
34
+ import type { CreateRefundParams, List, ListRefundsParams, Refund, RequestOptions, UpdateRefundParams } from "../types.js";
35
+ /** `client.refunds` — see the module doc. */
36
+ export declare class RefundsResource {
37
+ #private;
38
+ constructor(http: HttpClient);
39
+ /**
40
+ * `POST /v1/refunds`.
41
+ *
42
+ * Field order is the wire order `sdks/rust/tests/resources.rs` pins byte
43
+ * for byte; an unset field is omitted from the body entirely.
44
+ *
45
+ * A `400` naming `destination` for a payee this rail will not take — and
46
+ * for **no** payee at all, since both rails vpay carries need one; a `400`
47
+ * naming `amount` for a partial refund on a rail that has no partials; a
48
+ * `409` for an intent with nothing left to refund.
49
+ *
50
+ * See the module doc for what a `200` here does and does not mean.
51
+ */
52
+ create(params: CreateRefundParams, options?: RequestOptions): Promise<Refund>;
53
+ /**
54
+ * `GET /v1/refunds/{id}`.
55
+ *
56
+ * A refund is asynchronous on this rail — `RefundStatus` has a
57
+ * non-terminal `"pending"` — and webhook delivery is at-least-once and
58
+ * unordered, so this is the authoritative read a reconciliation job falls
59
+ * back to when a `charge.refund.updated` was missed or arrived out of
60
+ * order.
61
+ */
62
+ retrieve(id: string): Promise<Refund>;
63
+ /**
64
+ * `POST /v1/refunds/{id}` — the **metadata** update, and nothing else.
65
+ *
66
+ * See {@link UpdateRefundParams}: the other four fields of a refund are
67
+ * fixed once it exists, the merge is key-wise, and an empty value deletes a
68
+ * key. A refund whose metadata actually changes emits
69
+ * `charge.refund.updated`; one sent no `metadata` is read back unchanged
70
+ * and emits nothing.
71
+ *
72
+ * A `404` for a refund this merchant has none of, and a `400` naming the
73
+ * parameter for anything but `metadata` — which this params type has no way
74
+ * to send.
75
+ */
76
+ update(id: string, params: UpdateRefundParams, options?: RequestOptions): Promise<Refund>;
77
+ /** `GET /v1/refunds` — this merchant's refunds, newest first. */
78
+ list(params?: ListRefundsParams): Promise<List<Refund>>;
79
+ /**
80
+ * `POST /v1/refunds/{id}/cancel` — **while it is still pending**. No
81
+ * request fields.
82
+ *
83
+ * The reservation this refund holds against its intent is released and
84
+ * `charge.refund.updated` is written. A `404` for a refund this merchant
85
+ * has none of, and a `409` naming the current status for one that is no
86
+ * longer `"pending"`.
87
+ *
88
+ * Nothing here reaches a rail: a cancel is a vpay-side release, because no
89
+ * rail was ever told to start. See the module doc.
90
+ */
91
+ cancel(id: string, options?: RequestOptions): Promise<Refund>;
92
+ }
@@ -0,0 +1,106 @@
1
+ import { assertIntegerAmount } from "../validate.js";
2
+ /**
3
+ * The rail-agnostic envelope, built around the rail-specific interior.
4
+ *
5
+ * The rail's own code is the **outer key** and the interior is what that
6
+ * rail's adapter parses; see {@link RefundDestination}. One function so the
7
+ * envelope is written in one place and no caller can flatten it, and so the
8
+ * `msisdn` goes to the wire byte for byte as the merchant wrote it — this SDK
9
+ * does not canonicalise a payee's number (the server does, and it requires a
10
+ * leading `+`).
11
+ */
12
+ function destinationForm(destination) {
13
+ return { [destination.payment_method_type]: { msisdn: destination.msisdn } };
14
+ }
15
+ /** `client.refunds` — see the module doc. */
16
+ export class RefundsResource {
17
+ #http;
18
+ constructor(http) {
19
+ this.#http = http;
20
+ }
21
+ /**
22
+ * `POST /v1/refunds`.
23
+ *
24
+ * Field order is the wire order `sdks/rust/tests/resources.rs` pins byte
25
+ * for byte; an unset field is omitted from the body entirely.
26
+ *
27
+ * A `400` naming `destination` for a payee this rail will not take — and
28
+ * for **no** payee at all, since both rails vpay carries need one; a `400`
29
+ * naming `amount` for a partial refund on a rail that has no partials; a
30
+ * `409` for an intent with nothing left to refund.
31
+ *
32
+ * See the module doc for what a `200` here does and does not mean.
33
+ */
34
+ async create(params, options) {
35
+ if (params.amount !== undefined) {
36
+ assertIntegerAmount(params.amount);
37
+ }
38
+ const body = {
39
+ payment_intent: params.payment_intent,
40
+ };
41
+ if (params.amount !== undefined) {
42
+ body["amount"] = params.amount;
43
+ }
44
+ if (params.reason !== undefined) {
45
+ body["reason"] = params.reason;
46
+ }
47
+ if (params.destination !== undefined) {
48
+ body["destination"] = destinationForm(params.destination);
49
+ }
50
+ if (params.metadata !== undefined) {
51
+ body["metadata"] = params.metadata;
52
+ }
53
+ return this.#http.request("POST", "/refunds", body, options);
54
+ }
55
+ /**
56
+ * `GET /v1/refunds/{id}`.
57
+ *
58
+ * A refund is asynchronous on this rail — `RefundStatus` has a
59
+ * non-terminal `"pending"` — and webhook delivery is at-least-once and
60
+ * unordered, so this is the authoritative read a reconciliation job falls
61
+ * back to when a `charge.refund.updated` was missed or arrived out of
62
+ * order.
63
+ */
64
+ async retrieve(id) {
65
+ return this.#http.request("GET", `/refunds/${encodeURIComponent(id)}`);
66
+ }
67
+ /**
68
+ * `POST /v1/refunds/{id}` — the **metadata** update, and nothing else.
69
+ *
70
+ * See {@link UpdateRefundParams}: the other four fields of a refund are
71
+ * fixed once it exists, the merge is key-wise, and an empty value deletes a
72
+ * key. A refund whose metadata actually changes emits
73
+ * `charge.refund.updated`; one sent no `metadata` is read back unchanged
74
+ * and emits nothing.
75
+ *
76
+ * A `404` for a refund this merchant has none of, and a `400` naming the
77
+ * parameter for anything but `metadata` — which this params type has no way
78
+ * to send.
79
+ */
80
+ async update(id, params, options) {
81
+ const body = {};
82
+ if (params.metadata !== undefined) {
83
+ body["metadata"] = params.metadata;
84
+ }
85
+ return this.#http.request("POST", `/refunds/${encodeURIComponent(id)}`, body, options);
86
+ }
87
+ /** `GET /v1/refunds` — this merchant's refunds, newest first. */
88
+ async list(params) {
89
+ return this.#http.request("GET", "/refunds", params);
90
+ }
91
+ /**
92
+ * `POST /v1/refunds/{id}/cancel` — **while it is still pending**. No
93
+ * request fields.
94
+ *
95
+ * The reservation this refund holds against its intent is released and
96
+ * `charge.refund.updated` is written. A `404` for a refund this merchant
97
+ * has none of, and a `409` naming the current status for one that is no
98
+ * longer `"pending"`.
99
+ *
100
+ * Nothing here reaches a rail: a cancel is a vpay-side release, because no
101
+ * rail was ever told to start. See the module doc.
102
+ */
103
+ async cancel(id, options) {
104
+ return this.#http.request("POST", `/refunds/${encodeURIComponent(id)}/cancel`, {}, options);
105
+ }
106
+ }