@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
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `/v1/checkout/sessions` — the four merchant operations Step 9's wire
|
|
3
|
+
* contract defines, and the one thing this file does that no other resource
|
|
4
|
+
* does: it redacts the object's `client_secret` from `util.inspect`.
|
|
5
|
+
*
|
|
6
|
+
* ADR-0015's matrix records, as a dated gap, that `PaymentIntent` is a plain
|
|
7
|
+
* interface here and so `console.log(intent)` prints a live payer
|
|
8
|
+
* credential, while the Rust SDK's hand-written `Debug` redacts it. That gap
|
|
9
|
+
* is not repeated for a **new** object: every `CheckoutSession` this SDK
|
|
10
|
+
* hands back carries a custom inspect representation that prints
|
|
11
|
+
* `[N chars redacted]` in place of the secret, which is exactly what
|
|
12
|
+
* `vpay_sdk::CheckoutSession`'s `impl Debug` prints.
|
|
13
|
+
*
|
|
14
|
+
* `JSON.stringify` is deliberately **not** redacted, and that asymmetry is
|
|
15
|
+
* the same one Rust has (`Debug` redacts, `Serialize` does not): an embedded
|
|
16
|
+
* integration must serialise the secret to get it to the browser at all, so
|
|
17
|
+
* a redacting `toJSON` would break the one thing the field exists for. The
|
|
18
|
+
* hazard being closed here is the accidental one — a session that reaches a
|
|
19
|
+
* log line because something logged the whole object.
|
|
20
|
+
*/
|
|
21
|
+
import type { HttpClient } from "../http.js";
|
|
22
|
+
import type { CheckoutSession, CreateCheckoutSessionParams, List, ListCheckoutSessionsParams, RequestOptions } from "../types.js";
|
|
23
|
+
/** `client.checkout.sessions` — see {@link CheckoutResource}. */
|
|
24
|
+
export declare class CheckoutSessionsResource {
|
|
25
|
+
#private;
|
|
26
|
+
constructor(http: HttpClient);
|
|
27
|
+
/**
|
|
28
|
+
* `POST /v1/checkout/sessions`. Answers the session **with**
|
|
29
|
+
* `client_secret`, and with `url` when `ui_mode` is `hosted`.
|
|
30
|
+
*/
|
|
31
|
+
create(params: CreateCheckoutSessionParams, options?: RequestOptions): Promise<CheckoutSession>;
|
|
32
|
+
/** `GET /v1/checkout/sessions/{id}`. Answers the session with `client_secret`. */
|
|
33
|
+
retrieve(id: string): Promise<CheckoutSession>;
|
|
34
|
+
/** `GET /v1/checkout/sessions`. List items never carry `client_secret`. */
|
|
35
|
+
list(params?: ListCheckoutSessionsParams): Promise<List<CheckoutSession>>;
|
|
36
|
+
/**
|
|
37
|
+
* `POST /v1/checkout/sessions/{id}/expire`. `open` → `expired`; a session
|
|
38
|
+
* whose intent already has a live charge is refused `409`.
|
|
39
|
+
*/
|
|
40
|
+
expire(id: string, options?: RequestOptions): Promise<CheckoutSession>;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* `client.checkout` — a namespace, not a resource with operations of its
|
|
44
|
+
* own.
|
|
45
|
+
*
|
|
46
|
+
* It exists so the call reads `client.checkout.sessions.create(…)`, which is
|
|
47
|
+
* the path the wire contract uses (`/v1/checkout/sessions`) and the shape
|
|
48
|
+
* `vpay_sdk`'s `client.checkout().sessions()` mirrors. A flat
|
|
49
|
+
* `client.checkoutSessions` would have been shorter and would have made the
|
|
50
|
+
* two SDKs read differently for the same route.
|
|
51
|
+
*/
|
|
52
|
+
export declare class CheckoutResource {
|
|
53
|
+
readonly sessions: CheckoutSessionsResource;
|
|
54
|
+
constructor(http: HttpClient);
|
|
55
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
const INSPECT_CUSTOM = Symbol.for("nodejs.util.inspect.custom");
|
|
2
|
+
/**
|
|
3
|
+
* The `url` of a hosted session with its fragment redacted.
|
|
4
|
+
*
|
|
5
|
+
* D6 puts the session's `client_secret` in the hosted page's URL fragment
|
|
6
|
+
* (`https://checkout.example/c/cs_1#cs_1_secret_…`), so redacting
|
|
7
|
+
* `client_secret` alone and printing `url` verbatim would leak exactly the
|
|
8
|
+
* value the redaction exists to hide — measured, not assumed: the first
|
|
9
|
+
* version of this file did that, and the test that now pins it failed.
|
|
10
|
+
*/
|
|
11
|
+
function redactUrlFragment(url) {
|
|
12
|
+
const hash = url.indexOf("#");
|
|
13
|
+
if (hash === -1) {
|
|
14
|
+
return url;
|
|
15
|
+
}
|
|
16
|
+
const fragment = url.slice(hash + 1);
|
|
17
|
+
return `${url.slice(0, hash + 1)}[${fragment.length} chars redacted]`;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Attaches a redacting `util.inspect` representation to one decoded
|
|
21
|
+
* session, in place.
|
|
22
|
+
*
|
|
23
|
+
* Non-enumerable, so the property is invisible to `Object.keys`,
|
|
24
|
+
* `JSON.stringify`, spread and deep-equality — the object a merchant
|
|
25
|
+
* receives is still, in every observable way, the decoded response body.
|
|
26
|
+
* The representation itself is built from a **spread copy**, which does not
|
|
27
|
+
* carry the symbol, so `inspect` inside it cannot recurse.
|
|
28
|
+
*/
|
|
29
|
+
function withRedactedInspect(session) {
|
|
30
|
+
if (typeof session !== "object" || session === null) {
|
|
31
|
+
return session;
|
|
32
|
+
}
|
|
33
|
+
Object.defineProperty(session, INSPECT_CUSTOM, {
|
|
34
|
+
value: function redactedInspect(depth, options, inspect) {
|
|
35
|
+
const secret = this.client_secret;
|
|
36
|
+
const copy = { ...this };
|
|
37
|
+
if (typeof secret === "string") {
|
|
38
|
+
copy["client_secret"] = `[${secret.length} chars redacted]`;
|
|
39
|
+
}
|
|
40
|
+
const url = copy["url"];
|
|
41
|
+
if (typeof url === "string") {
|
|
42
|
+
copy["url"] = redactUrlFragment(url);
|
|
43
|
+
}
|
|
44
|
+
// `inspect` is handed in by `node:util` itself, so this works
|
|
45
|
+
// whatever options the caller passed (colours, depth, breakLength)
|
|
46
|
+
// without this file importing `node:util` at all.
|
|
47
|
+
return `CheckoutSession ${inspect(copy, options)}`;
|
|
48
|
+
},
|
|
49
|
+
enumerable: false,
|
|
50
|
+
writable: false,
|
|
51
|
+
configurable: true,
|
|
52
|
+
});
|
|
53
|
+
return session;
|
|
54
|
+
}
|
|
55
|
+
/** `client.checkout.sessions` — see {@link CheckoutResource}. */
|
|
56
|
+
export class CheckoutSessionsResource {
|
|
57
|
+
#http;
|
|
58
|
+
constructor(http) {
|
|
59
|
+
this.#http = http;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* `POST /v1/checkout/sessions`. Answers the session **with**
|
|
63
|
+
* `client_secret`, and with `url` when `ui_mode` is `hosted`.
|
|
64
|
+
*/
|
|
65
|
+
async create(params, options) {
|
|
66
|
+
// Field order is the wire order the Rust SDK pins byte for byte
|
|
67
|
+
// (`sdks/rust/tests/resources.rs`); an unset field is omitted from the
|
|
68
|
+
// body entirely, because `success_url=` and no `success_url` are
|
|
69
|
+
// different requests and only the second means "not applicable".
|
|
70
|
+
const body = {
|
|
71
|
+
payment_intent: params.payment_intent,
|
|
72
|
+
};
|
|
73
|
+
if (params.ui_mode !== undefined) {
|
|
74
|
+
body["ui_mode"] = params.ui_mode;
|
|
75
|
+
}
|
|
76
|
+
if (params.success_url !== undefined) {
|
|
77
|
+
body["success_url"] = params.success_url;
|
|
78
|
+
}
|
|
79
|
+
if (params.cancel_url !== undefined) {
|
|
80
|
+
body["cancel_url"] = params.cancel_url;
|
|
81
|
+
}
|
|
82
|
+
if (params.return_url !== undefined) {
|
|
83
|
+
body["return_url"] = params.return_url;
|
|
84
|
+
}
|
|
85
|
+
if (params.customer !== undefined) {
|
|
86
|
+
body["customer"] = params.customer;
|
|
87
|
+
}
|
|
88
|
+
return withRedactedInspect(await this.#http.request("POST", "/checkout/sessions", body, options));
|
|
89
|
+
}
|
|
90
|
+
/** `GET /v1/checkout/sessions/{id}`. Answers the session with `client_secret`. */
|
|
91
|
+
async retrieve(id) {
|
|
92
|
+
return withRedactedInspect(await this.#http.request("GET", `/checkout/sessions/${encodeURIComponent(id)}`));
|
|
93
|
+
}
|
|
94
|
+
/** `GET /v1/checkout/sessions`. List items never carry `client_secret`. */
|
|
95
|
+
async list(params) {
|
|
96
|
+
const page = await this.#http.request("GET", "/checkout/sessions", params);
|
|
97
|
+
if (Array.isArray(page?.data)) {
|
|
98
|
+
for (const session of page.data) {
|
|
99
|
+
withRedactedInspect(session);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return page;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* `POST /v1/checkout/sessions/{id}/expire`. `open` → `expired`; a session
|
|
106
|
+
* whose intent already has a live charge is refused `409`.
|
|
107
|
+
*/
|
|
108
|
+
async expire(id, options) {
|
|
109
|
+
return withRedactedInspect(await this.#http.request("POST", `/checkout/sessions/${encodeURIComponent(id)}/expire`, {}, options));
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* `client.checkout` — a namespace, not a resource with operations of its
|
|
114
|
+
* own.
|
|
115
|
+
*
|
|
116
|
+
* It exists so the call reads `client.checkout.sessions.create(…)`, which is
|
|
117
|
+
* the path the wire contract uses (`/v1/checkout/sessions`) and the shape
|
|
118
|
+
* `vpay_sdk`'s `client.checkout().sessions()` mirrors. A flat
|
|
119
|
+
* `client.checkoutSessions` would have been shorter and would have made the
|
|
120
|
+
* two SDKs read differently for the same route.
|
|
121
|
+
*/
|
|
122
|
+
export class CheckoutResource {
|
|
123
|
+
sessions;
|
|
124
|
+
constructor(http) {
|
|
125
|
+
this.sessions = new CheckoutSessionsResource(http);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `/v1/customers` — the five merchant operations on the Customer object
|
|
3
|
+
* (S4a), and the one thing this file does that no other resource does: it
|
|
4
|
+
* distinguishes *clear this field* from *leave it alone* — for four fields
|
|
5
|
+
* now, `address` included. `address` is also the one field on this API that
|
|
6
|
+
* is WIDER than Stripe's: it carries the GPS point beside the six formal
|
|
7
|
+
* components (the maintainer, 2026-09-11), and `Address` in `types.ts` says
|
|
8
|
+
* why — including why the coordinate is a whole count of microdegrees and
|
|
9
|
+
* never a decimal degree.
|
|
10
|
+
*
|
|
11
|
+
* # The three states, and why `update` looks the way it does
|
|
12
|
+
*
|
|
13
|
+
* `undefined` (or an absent key) means "leave it alone"; a string means "set
|
|
14
|
+
* it"; **`null` means "clear it"**, and this file sends that as `name=`,
|
|
15
|
+
* which is what the wire spells "clear". Collapsing `null` and `undefined` —
|
|
16
|
+
* the obvious simplification, and what a `params.name !== undefined` guard
|
|
17
|
+
* alone would do — makes a payer's email unclearable through the API that
|
|
18
|
+
* documents how to clear it. `sdks/rust`'s `UpdateCustomerParams` uses
|
|
19
|
+
* `Option<Option<String>>` for exactly this and the server carries the same
|
|
20
|
+
* three states in `vpay_db::CustomerPatch`.
|
|
21
|
+
*
|
|
22
|
+
* # No redacting `util.inspect`, unlike `checkout-sessions.ts`
|
|
23
|
+
*
|
|
24
|
+
* That file redacts because a `CheckoutSession` carries a *credential*:
|
|
25
|
+
* printing one is a compromise, and the merchant who logged it can do nothing
|
|
26
|
+
* about it afterwards. A `Customer` carries a payer's own name, email and
|
|
27
|
+
* phone number — data the merchant collected, already holds and is
|
|
28
|
+
* responsible for. Redacting it here would hide their own data from them
|
|
29
|
+
* while doing nothing about the copy in their database.
|
|
30
|
+
*
|
|
31
|
+
* The place that judgement is made *for* them is the server: vpay's
|
|
32
|
+
* `CustomerRow` redacts all three in its `Debug`, because vpay's logs are not
|
|
33
|
+
* the merchant's. `docs/flows/customers.md` records the asymmetry so it reads
|
|
34
|
+
* as a decision rather than an oversight, and `sdks/rust`'s `Customer`
|
|
35
|
+
* derives `Debug` for the same reason.
|
|
36
|
+
*/
|
|
37
|
+
import type { HttpClient } from "../http.js";
|
|
38
|
+
import type { CreateCustomerParams, Customer, DeletedCustomer, List, ListCustomersParams, RequestOptions, UpdateCustomerParams } from "../types.js";
|
|
39
|
+
/** `client.customers` — see {@link CustomersResource}. */
|
|
40
|
+
export declare class CustomersResource {
|
|
41
|
+
#private;
|
|
42
|
+
constructor(http: HttpClient);
|
|
43
|
+
/**
|
|
44
|
+
* `POST /v1/customers`.
|
|
45
|
+
*
|
|
46
|
+
* Field order is the wire order the Rust SDK pins byte for byte
|
|
47
|
+
* (`sdks/rust/tests/resources.rs`); an unset field is omitted from the body
|
|
48
|
+
* entirely, because `name=` and no `name` are different requests and only
|
|
49
|
+
* the second means "not applicable".
|
|
50
|
+
*/
|
|
51
|
+
create(params: CreateCustomerParams, options?: RequestOptions): Promise<Customer>;
|
|
52
|
+
/** `GET /v1/customers/{id}`. */
|
|
53
|
+
retrieve(id: string): Promise<Customer>;
|
|
54
|
+
/**
|
|
55
|
+
* `POST /v1/customers/{id}` — the update.
|
|
56
|
+
*
|
|
57
|
+
* A `POST` and not a `PUT`/`PATCH`, because that is what the API is:
|
|
58
|
+
* Stripe has neither verb, and a merchant's existing client sends a `POST`.
|
|
59
|
+
*
|
|
60
|
+
* A patch that would clear the customer's **last** identifier is refused
|
|
61
|
+
* `400` naming `name` — a customer must always have one of `name`, `email`
|
|
62
|
+
* and `phone`.
|
|
63
|
+
*/
|
|
64
|
+
update(id: string, params: UpdateCustomerParams, options?: RequestOptions): Promise<Customer>;
|
|
65
|
+
/** `GET /v1/customers`. */
|
|
66
|
+
list(params?: ListCustomersParams): Promise<List<Customer>>;
|
|
67
|
+
/**
|
|
68
|
+
* `DELETE /v1/customers/{id}` — an **erasure**, in one of two shapes.
|
|
69
|
+
*
|
|
70
|
+
* `del` and not `delete`: `delete` is a reserved word in older JavaScript
|
|
71
|
+
* object literals, which is why Stripe's own SDKs spell it this way, and
|
|
72
|
+
* `sdks/rust`'s `CustomersResource::del` matches so a merchant who read one
|
|
73
|
+
* SDK's docs can use the other.
|
|
74
|
+
*
|
|
75
|
+
* A customer with **no payment history** is removed outright: a subsequent
|
|
76
|
+
* {@link retrieve} answers the same 404 as an id that never existed.
|
|
77
|
+
*
|
|
78
|
+
* A customer any PaymentIntent, Checkout Session or Invoice references
|
|
79
|
+
* cannot be removed — vpay keeps a payment attached to the payer it was
|
|
80
|
+
* taken from — so it is **anonymised** instead. The row and the payment
|
|
81
|
+
* record stay; every identifier on the customer becomes `[redacted]`, and
|
|
82
|
+
* so does every copy vpay kept elsewhere. A later {@link retrieve} answers
|
|
83
|
+
* `200` with {@link Customer.deleted} `true`, so your own stored `cus_…`
|
|
84
|
+
* keeps resolving. `metadata` is untouched: that is your data, not the
|
|
85
|
+
* payer's.
|
|
86
|
+
*
|
|
87
|
+
* Either way this answers `{deleted: true}`, and a second call is a no-op
|
|
88
|
+
* rather than a second erasure. Until 2026-09-10 the second case answered
|
|
89
|
+
* `409` instead, with advice — clear `name`, `email` and `phone` — that the
|
|
90
|
+
* server's own one-of rule refuses.
|
|
91
|
+
*
|
|
92
|
+
* Carries an `Idempotency-Key` like every other write on this API, which is
|
|
93
|
+
* what makes a retried delete answer the original `{deleted: true}` rather
|
|
94
|
+
* than a `404`.
|
|
95
|
+
*/
|
|
96
|
+
del(id: string, options?: RequestOptions): Promise<DeletedCustomer>;
|
|
97
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One patch field, in the wire's own three-state encoding.
|
|
3
|
+
*
|
|
4
|
+
* Returns `undefined` for "the request did not mention this", which the
|
|
5
|
+
* caller drops from the body entirely, and the empty string for "clear it".
|
|
6
|
+
*/
|
|
7
|
+
function patch(value) {
|
|
8
|
+
if (value === undefined) {
|
|
9
|
+
return undefined;
|
|
10
|
+
}
|
|
11
|
+
return value === null ? "" : value;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* The eight address components as a nested body value, with every unset one
|
|
15
|
+
* omitted entirely.
|
|
16
|
+
*
|
|
17
|
+
* Omitted and not sent empty, because on this API those are different
|
|
18
|
+
* requests everywhere else — and because the server replaces the address
|
|
19
|
+
* whole, so an omitted component is cleared either way and `address[city]=`
|
|
20
|
+
* would only add a pair saying the same thing.
|
|
21
|
+
*
|
|
22
|
+
* The two coordinate keys go through the same loop as the six formal ones and
|
|
23
|
+
* are typed `number` rather than `string`: the form encoder renders a number
|
|
24
|
+
* as a decimal integer with no separators and refuses a non-finite one
|
|
25
|
+
* (`form.ts`), so there is no path from this function to a body carrying a
|
|
26
|
+
* float — which is the whole of why the wire field is named for its unit.
|
|
27
|
+
*/
|
|
28
|
+
function addressBody(address) {
|
|
29
|
+
const body = {};
|
|
30
|
+
for (const key of [
|
|
31
|
+
"line1",
|
|
32
|
+
"line2",
|
|
33
|
+
"city",
|
|
34
|
+
"state",
|
|
35
|
+
"postal_code",
|
|
36
|
+
"country",
|
|
37
|
+
"latitude_microdeg",
|
|
38
|
+
"longitude_microdeg",
|
|
39
|
+
]) {
|
|
40
|
+
const value = address[key];
|
|
41
|
+
if (value !== undefined) {
|
|
42
|
+
body[key] = value;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return body;
|
|
46
|
+
}
|
|
47
|
+
/** `client.customers` — see {@link CustomersResource}. */
|
|
48
|
+
export class CustomersResource {
|
|
49
|
+
#http;
|
|
50
|
+
constructor(http) {
|
|
51
|
+
this.#http = http;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* `POST /v1/customers`.
|
|
55
|
+
*
|
|
56
|
+
* Field order is the wire order the Rust SDK pins byte for byte
|
|
57
|
+
* (`sdks/rust/tests/resources.rs`); an unset field is omitted from the body
|
|
58
|
+
* entirely, because `name=` and no `name` are different requests and only
|
|
59
|
+
* the second means "not applicable".
|
|
60
|
+
*/
|
|
61
|
+
async create(params, options) {
|
|
62
|
+
const body = {};
|
|
63
|
+
if (params.name !== undefined) {
|
|
64
|
+
body["name"] = params.name;
|
|
65
|
+
}
|
|
66
|
+
if (params.email !== undefined) {
|
|
67
|
+
body["email"] = params.email;
|
|
68
|
+
}
|
|
69
|
+
if (params.phone !== undefined) {
|
|
70
|
+
body["phone"] = params.phone;
|
|
71
|
+
}
|
|
72
|
+
if (params.address !== undefined) {
|
|
73
|
+
body["address"] = addressBody(params.address);
|
|
74
|
+
}
|
|
75
|
+
if (params.metadata !== undefined) {
|
|
76
|
+
body["metadata"] = params.metadata;
|
|
77
|
+
}
|
|
78
|
+
return this.#http.request("POST", "/customers", body, options);
|
|
79
|
+
}
|
|
80
|
+
/** `GET /v1/customers/{id}`. */
|
|
81
|
+
async retrieve(id) {
|
|
82
|
+
return this.#http.request("GET", `/customers/${encodeURIComponent(id)}`);
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* `POST /v1/customers/{id}` — the update.
|
|
86
|
+
*
|
|
87
|
+
* A `POST` and not a `PUT`/`PATCH`, because that is what the API is:
|
|
88
|
+
* Stripe has neither verb, and a merchant's existing client sends a `POST`.
|
|
89
|
+
*
|
|
90
|
+
* A patch that would clear the customer's **last** identifier is refused
|
|
91
|
+
* `400` naming `name` — a customer must always have one of `name`, `email`
|
|
92
|
+
* and `phone`.
|
|
93
|
+
*/
|
|
94
|
+
async update(id, params, options) {
|
|
95
|
+
const body = {};
|
|
96
|
+
const name = patch(params.name);
|
|
97
|
+
if (name !== undefined) {
|
|
98
|
+
body["name"] = name;
|
|
99
|
+
}
|
|
100
|
+
const email = patch(params.email);
|
|
101
|
+
if (email !== undefined) {
|
|
102
|
+
body["email"] = email;
|
|
103
|
+
}
|
|
104
|
+
const phone = patch(params.phone);
|
|
105
|
+
if (phone !== undefined) {
|
|
106
|
+
body["phone"] = phone;
|
|
107
|
+
}
|
|
108
|
+
if (params.address !== undefined) {
|
|
109
|
+
// `address=` — the wire's own "remove this", the same spelling `name=`
|
|
110
|
+
// uses. NOT eight empty components: the server reads a *scalar* `address`
|
|
111
|
+
// as the clear, and one spelling means a merchant reading the body sees
|
|
112
|
+
// what they asked for.
|
|
113
|
+
body["address"] =
|
|
114
|
+
params.address === null ? "" : addressBody(params.address);
|
|
115
|
+
}
|
|
116
|
+
if (params.metadata !== undefined) {
|
|
117
|
+
body["metadata"] = params.metadata;
|
|
118
|
+
}
|
|
119
|
+
return this.#http.request("POST", `/customers/${encodeURIComponent(id)}`, body, options);
|
|
120
|
+
}
|
|
121
|
+
/** `GET /v1/customers`. */
|
|
122
|
+
async list(params) {
|
|
123
|
+
return this.#http.request("GET", "/customers", params);
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* `DELETE /v1/customers/{id}` — an **erasure**, in one of two shapes.
|
|
127
|
+
*
|
|
128
|
+
* `del` and not `delete`: `delete` is a reserved word in older JavaScript
|
|
129
|
+
* object literals, which is why Stripe's own SDKs spell it this way, and
|
|
130
|
+
* `sdks/rust`'s `CustomersResource::del` matches so a merchant who read one
|
|
131
|
+
* SDK's docs can use the other.
|
|
132
|
+
*
|
|
133
|
+
* A customer with **no payment history** is removed outright: a subsequent
|
|
134
|
+
* {@link retrieve} answers the same 404 as an id that never existed.
|
|
135
|
+
*
|
|
136
|
+
* A customer any PaymentIntent, Checkout Session or Invoice references
|
|
137
|
+
* cannot be removed — vpay keeps a payment attached to the payer it was
|
|
138
|
+
* taken from — so it is **anonymised** instead. The row and the payment
|
|
139
|
+
* record stay; every identifier on the customer becomes `[redacted]`, and
|
|
140
|
+
* so does every copy vpay kept elsewhere. A later {@link retrieve} answers
|
|
141
|
+
* `200` with {@link Customer.deleted} `true`, so your own stored `cus_…`
|
|
142
|
+
* keeps resolving. `metadata` is untouched: that is your data, not the
|
|
143
|
+
* payer's.
|
|
144
|
+
*
|
|
145
|
+
* Either way this answers `{deleted: true}`, and a second call is a no-op
|
|
146
|
+
* rather than a second erasure. Until 2026-09-10 the second case answered
|
|
147
|
+
* `409` instead, with advice — clear `name`, `email` and `phone` — that the
|
|
148
|
+
* server's own one-of rule refuses.
|
|
149
|
+
*
|
|
150
|
+
* Carries an `Idempotency-Key` like every other write on this API, which is
|
|
151
|
+
* what makes a retried delete answer the original `{deleted: true}` rather
|
|
152
|
+
* than a `404`.
|
|
153
|
+
*/
|
|
154
|
+
async del(id, options) {
|
|
155
|
+
return this.#http.request("DELETE", `/customers/${encodeURIComponent(id)}`, undefined, options);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { HttpClient } from "../http.js";
|
|
2
|
+
import type { Event, List, ListEventsParams } from "../types.js";
|
|
3
|
+
/** `/v1/events` — docs/flows/merchant-auth.md, "Resources". */
|
|
4
|
+
export declare class EventsResource {
|
|
5
|
+
#private;
|
|
6
|
+
constructor(http: HttpClient);
|
|
7
|
+
list(params?: ListEventsParams): Promise<List<Event>>;
|
|
8
|
+
}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `/v1/invoices` and `/v1/invoice_items` — the thirteen merchant operations
|
|
3
|
+
* on the Invoice object and its lines (S4b).
|
|
4
|
+
*
|
|
5
|
+
* # The one thing to know before calling any of them
|
|
6
|
+
*
|
|
7
|
+
* **The state machine is the server's `WHERE` clause, not a check this SDK
|
|
8
|
+
* makes.** A transition that does not apply comes back a `409` naming the
|
|
9
|
+
* invoice's current status, and this SDK does not try to predict which —
|
|
10
|
+
* `draft → open → paid | void | uncollectible`, and every method below says
|
|
11
|
+
* which statuses it accepts. Reading the invoice first to decide whether to
|
|
12
|
+
* call is a race; call, and read the refusal.
|
|
13
|
+
*
|
|
14
|
+
* # Two resources, one file
|
|
15
|
+
*
|
|
16
|
+
* A line has no life of its own: it belongs to exactly one invoice, its
|
|
17
|
+
* currency and tenant are copied off that invoice, and every write to it is
|
|
18
|
+
* refused once the invoice is issued. Splitting the two would put that guard
|
|
19
|
+
* in a file that does not mention the thing it guards. `sdks/rust` keeps them
|
|
20
|
+
* in one module for the same reason.
|
|
21
|
+
*/
|
|
22
|
+
import type { HttpClient } from "../http.js";
|
|
23
|
+
import type { CreateInvoiceItemParams, CreateInvoiceParams, DeletedInvoice, DeletedInvoiceItem, Invoice, InvoiceLine, List, ListInvoicesParams, PayInvoiceParams, RequestOptions, UpdateInvoiceItemParams, UpdateInvoiceParams } from "../types.js";
|
|
24
|
+
/** `client.invoices` — see the module doc. */
|
|
25
|
+
export declare class InvoicesResource {
|
|
26
|
+
#private;
|
|
27
|
+
constructor(http: HttpClient);
|
|
28
|
+
/**
|
|
29
|
+
* `POST /v1/invoices` — creates a **draft**, with no number and zero
|
|
30
|
+
* amounts.
|
|
31
|
+
*
|
|
32
|
+
* Field order is the wire order `sdks/rust/tests/resources.rs` pins byte
|
|
33
|
+
* for byte; an unset field is omitted from the body entirely, because
|
|
34
|
+
* `description=` and no `description` are different requests and only the
|
|
35
|
+
* second means "not applicable".
|
|
36
|
+
*
|
|
37
|
+
* A `400` naming `customer` when it is absent, unknown, or another
|
|
38
|
+
* merchant's — one sentence for all three, so the parameter is not an
|
|
39
|
+
* oracle; and a `400` naming `currency` for one this deployment does not
|
|
40
|
+
* settle.
|
|
41
|
+
*/
|
|
42
|
+
create(params: CreateInvoiceParams, options?: RequestOptions): Promise<Invoice>;
|
|
43
|
+
/**
|
|
44
|
+
* `GET /v1/invoices/{id}` — the invoice **with its lines**.
|
|
45
|
+
*
|
|
46
|
+
* This is the read a webhook handler falls back to: an `invoice.*` event
|
|
47
|
+
* body carries an empty `lines.data`, and this response always carries the
|
|
48
|
+
* lines.
|
|
49
|
+
*
|
|
50
|
+
* Another merchant's `in_…` is the same `404`, byte for byte, as one that
|
|
51
|
+
* never existed.
|
|
52
|
+
*/
|
|
53
|
+
retrieve(id: string): Promise<Invoice>;
|
|
54
|
+
/**
|
|
55
|
+
* `POST /v1/invoices/{id}` — patches a **draft**.
|
|
56
|
+
*
|
|
57
|
+
* A `POST` and not a `PATCH`, for `customers.update`'s reason, even though
|
|
58
|
+
* the server mounts both verbs on the one handler: `POST` is what a
|
|
59
|
+
* merchant's existing Stripe client sends, so it is the one that has to
|
|
60
|
+
* work.
|
|
61
|
+
*
|
|
62
|
+
* A `409` naming the status once the invoice is no longer a draft — an
|
|
63
|
+
* issued document's terms are what was sent to the payer. Void it and issue
|
|
64
|
+
* a new one.
|
|
65
|
+
*/
|
|
66
|
+
update(id: string, params: UpdateInvoiceParams, options?: RequestOptions): Promise<Invoice>;
|
|
67
|
+
/** `GET /v1/invoices` — newest first. */
|
|
68
|
+
list(params?: ListInvoicesParams): Promise<List<Invoice>>;
|
|
69
|
+
/**
|
|
70
|
+
* `DELETE /v1/invoices/{id}` — **draft only**, and its lines go with it.
|
|
71
|
+
*
|
|
72
|
+
* An issued invoice is {@link InvoicesResource.void}ed, never deleted: a
|
|
73
|
+
* voided draft would be a non-draft row with no number, which the database
|
|
74
|
+
* refuses outright, and a document that vanished is a hole an accountant
|
|
75
|
+
* reads as a destroyed one.
|
|
76
|
+
*
|
|
77
|
+
* `del` and not `delete`, for `customers.del`'s reason. Carries an
|
|
78
|
+
* `Idempotency-Key` like every other write, which is what makes a retried
|
|
79
|
+
* delete answer the original `{deleted: true}` rather than a `404`.
|
|
80
|
+
*/
|
|
81
|
+
del(id: string, options?: RequestOptions): Promise<DeletedInvoice>;
|
|
82
|
+
/**
|
|
83
|
+
* `POST /v1/invoices/{id}/finalize` — issues the document. **Draft only.**
|
|
84
|
+
*
|
|
85
|
+
* The money transition: the invoice takes the next number out of this
|
|
86
|
+
* merchant's own sequence under a row lock, its lines freeze, its amounts
|
|
87
|
+
* are computed once, and it moves to `"open"` — all in one transaction,
|
|
88
|
+
* with `invoice.finalized` inside it. Numbers are consecutive and have no
|
|
89
|
+
* holes, unlike Stripe's.
|
|
90
|
+
*
|
|
91
|
+
* A `400` naming `invoice` when it has no lines, or when its total is past
|
|
92
|
+
* `Number.MAX_SAFE_INTEGER` minor units; a `409` when it is not a draft.
|
|
93
|
+
*/
|
|
94
|
+
finalize(id: string, options?: RequestOptions): Promise<Invoice>;
|
|
95
|
+
/**
|
|
96
|
+
* `POST /v1/invoices/{id}/void` — cancels an issued document. **Open
|
|
97
|
+
* only.**
|
|
98
|
+
*
|
|
99
|
+
* The invoice **keeps its number**. Terminal.
|
|
100
|
+
*
|
|
101
|
+
* A `409` when it is not open, and a `409` while a payment intent is
|
|
102
|
+
* attached and not yet canceled — cancel that intent first.
|
|
103
|
+
*/
|
|
104
|
+
void(id: string, options?: RequestOptions): Promise<Invoice>;
|
|
105
|
+
/**
|
|
106
|
+
* `POST /v1/invoices/{id}/mark_uncollectible` — writes an issued document
|
|
107
|
+
* off. **Open only.**
|
|
108
|
+
*
|
|
109
|
+
* Still owed, never expected. Terminal, and it **emits no event**: vpay
|
|
110
|
+
* does not write `invoice.marked_uncollectible`, so nothing arrives on a
|
|
111
|
+
* webhook and a merchant reconciling write-offs reads them with
|
|
112
|
+
* `list({ status: "uncollectible" })`.
|
|
113
|
+
*
|
|
114
|
+
* # `markUncollectible` here, `mark_uncollectible` in `sdks/rust`
|
|
115
|
+
*
|
|
116
|
+
* The only capability the two SDKs spell differently, and the difference is
|
|
117
|
+
* each language's own casing rather than a divergence: `sdks/rust` may not
|
|
118
|
+
* spell `markUncollectible` (`non_snake_case` is a rustc lint, not a
|
|
119
|
+
* preference) and this package should not spell `mark_uncollectible` —
|
|
120
|
+
* Stripe's own Node SDK, which merchants arrive from, says
|
|
121
|
+
* `markUncollectible`, and the other spelling would be the one snake_case
|
|
122
|
+
* method in a camelCase package.
|
|
123
|
+
*
|
|
124
|
+
* [ADR-0015](../../../docs/adr/0015-sdk-parity.md) decision 1 is that
|
|
125
|
+
* parity is **per capability, not per method name**, and its alternatives
|
|
126
|
+
* reject method-name parity in as many words. Until 2026-09-08
|
|
127
|
+
* `verify-sdk-parity` could not express that — it keyed a row on one
|
|
128
|
+
* spelling — and this method was named `mark_uncollectible` to satisfy the
|
|
129
|
+
* gate. It is `markUncollectible` now because the gate learned the
|
|
130
|
+
* distinction instead: `PARITY_COLUMN_SPELLINGS` in `.xtask/src/main.rs`
|
|
131
|
+
* carries the one entry, and an entry that exempts nothing fails the build.
|
|
132
|
+
*/
|
|
133
|
+
markUncollectible(id: string, options?: RequestOptions): Promise<Invoice>;
|
|
134
|
+
/**
|
|
135
|
+
* `POST /v1/invoices/{id}/pay` — mints a payment intent and a hosted
|
|
136
|
+
* checkout for what is left. **Open only.**
|
|
137
|
+
*
|
|
138
|
+
* **This does not charge anybody.** Stripe's `pay` charges a stored payment
|
|
139
|
+
* method; vpay has none, so this answers the invoice with
|
|
140
|
+
* {@link Invoice.payment_intent} and {@link Invoice.hosted_invoice_url}
|
|
141
|
+
* set — a page to send the payer to. The invoice stays `"open"` until the
|
|
142
|
+
* settlement moves it, which is when `invoice.paid` is emitted.
|
|
143
|
+
*
|
|
144
|
+
* One payment at a time: calling this again while the attached intent is
|
|
145
|
+
* live is a `409`, and cancelling that intent is the way back. Two
|
|
146
|
+
* concurrent calls attach exactly one intent.
|
|
147
|
+
*
|
|
148
|
+
* A `500 checkout_not_configured` when this deployment serves no checkout
|
|
149
|
+
* page.
|
|
150
|
+
*/
|
|
151
|
+
pay(id: string, params: PayInvoiceParams, options?: RequestOptions): Promise<Invoice>;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* `client.invoiceItems` — the four operations on `/v1/invoice_items`: the
|
|
155
|
+
* lines of a **draft** invoice.
|
|
156
|
+
*
|
|
157
|
+
* There is deliberately no `list`: an invoice's lines are read off the
|
|
158
|
+
* invoice ({@link Invoice.lines}, expanded on every render), and a
|
|
159
|
+
* merchant-wide list of lines across invoices answers no question anybody
|
|
160
|
+
* asked. The server mounts no collection `GET` either, so this is not an
|
|
161
|
+
* omission here.
|
|
162
|
+
*
|
|
163
|
+
* Every write requires the parent to still be a `"draft"`;
|
|
164
|
+
* {@link InvoiceItemsResource.retrieve} does not.
|
|
165
|
+
*/
|
|
166
|
+
export declare class InvoiceItemsResource {
|
|
167
|
+
#private;
|
|
168
|
+
constructor(http: HttpClient);
|
|
169
|
+
/**
|
|
170
|
+
* `POST /v1/invoice_items` — adds a line to a draft, and the invoice's
|
|
171
|
+
* `amount_due` moves with it.
|
|
172
|
+
*
|
|
173
|
+
* Throws `TypeError` before any request when `unit_amount` is not a
|
|
174
|
+
* non-negative safe integer; a `400` naming `invoice` when it is not one of
|
|
175
|
+
* your drafts.
|
|
176
|
+
*/
|
|
177
|
+
create(params: CreateInvoiceItemParams, options?: RequestOptions): Promise<InvoiceLine>;
|
|
178
|
+
/** `GET /v1/invoice_items/{id}` — readable whatever the parent's status. */
|
|
179
|
+
retrieve(id: string): Promise<InvoiceLine>;
|
|
180
|
+
/**
|
|
181
|
+
* `POST /v1/invoice_items/{id}` — **draft parent only**, with `amount`
|
|
182
|
+
* recomputed.
|
|
183
|
+
*
|
|
184
|
+
* Throws `TypeError` before any request for a `unit_amount` outside the
|
|
185
|
+
* bound; a `409` once the parent is no longer a draft.
|
|
186
|
+
*/
|
|
187
|
+
update(id: string, params: UpdateInvoiceItemParams, options?: RequestOptions): Promise<InvoiceLine>;
|
|
188
|
+
/**
|
|
189
|
+
* `DELETE /v1/invoice_items/{id}` — **draft parent only**, and the invoice
|
|
190
|
+
* re-totals.
|
|
191
|
+
*/
|
|
192
|
+
del(id: string, options?: RequestOptions): Promise<DeletedInvoiceItem>;
|
|
193
|
+
}
|