@chargezen/sdk 0.0.0-stage → 0.1.0
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/README.md +32 -2
- package/dist/index.d.ts +151 -0
- package/dist/index.js +51 -0
- package/dist/schema.d.ts +1980 -0
- package/dist/schema.js +5 -0
- package/package.json +36 -4
package/README.md
CHANGED
|
@@ -1,3 +1,33 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Chargezen Node.js SDK
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Typed access to the Chargezen API (`https://api.chargezen.com/v1`) from your backend: customers,
|
|
4
|
+
subscriptions, products, plans and email. Keys are secret; never use them in a browser.
|
|
5
|
+
|
|
6
|
+
```ts
|
|
7
|
+
import { randomUUID } from "node:crypto";
|
|
8
|
+
import { createChargezen } from "@chargezen/sdk";
|
|
9
|
+
|
|
10
|
+
const chargezen = createChargezen({ keyId: process.env.CHARGEZEN_KEY_ID!, secret: process.env.CHARGEZEN_SECRET! });
|
|
11
|
+
|
|
12
|
+
const { data } = await chargezen.GET("/customers", { params: { query: { query: "ada@example.com" } } });
|
|
13
|
+
const customerId = data!.customers[0].customer_ref.id;
|
|
14
|
+
|
|
15
|
+
// Every change takes an Idempotency-Key. Keep it: sending the same key again retries safely.
|
|
16
|
+
await chargezen.POST("/customers/{customer_id}/subscriptions/{id}/pause", {
|
|
17
|
+
params: { path: { customer_id: customerId, id: "123" }, header: { "Idempotency-Key": randomUUID() } },
|
|
18
|
+
body: { resume_on: "2027-01-12" },
|
|
19
|
+
});
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Errors come back as `error` with `{ error: { code, message, failure } }`; `failure: "temporary"` means a
|
|
23
|
+
retry with the same Idempotency-Key can succeed.
|
|
24
|
+
|
|
25
|
+
`orderNow` repeats Order now while the charge is still processing, and `waitForSubscriptionCreation`
|
|
26
|
+
follows a new subscription until it is ready.
|
|
27
|
+
|
|
28
|
+
## Development
|
|
29
|
+
|
|
30
|
+
`src/schema.ts` is generated from the API contract, `Chargezen/public_api/openapi.yaml`
|
|
31
|
+
(`npm run generate` with the Chargezen repo checked out beside this one). Don't edit it by hand.
|
|
32
|
+
The journey test runs against a real Chargezen API on Factory:
|
|
33
|
+
`../scripts/cz-test playwright --config playwright.journey.config.js`.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import type { components, paths } from "./schema.js";
|
|
2
|
+
export type { components, paths };
|
|
3
|
+
export interface ChargezenOptions {
|
|
4
|
+
/** The key id shown in Settings > API keys (ekid_...). */
|
|
5
|
+
keyId: string;
|
|
6
|
+
/** The key's secret (esk_...), shown once when the key is created. Never use it in a browser. */
|
|
7
|
+
secret: string;
|
|
8
|
+
baseUrl?: string;
|
|
9
|
+
fetch?: typeof globalThis.fetch;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* A typed client for every path in the Chargezen API, for example
|
|
13
|
+
* `chargezen.GET("/customers", { params: { query: { query: "ada@example.com" } } })`.
|
|
14
|
+
* Every POST takes an Idempotency-Key header: make one per change (crypto.randomUUID()) and send the
|
|
15
|
+
* same key again to retry that change safely.
|
|
16
|
+
*/
|
|
17
|
+
export declare function createChargezen({ keyId, secret, baseUrl, fetch }: ChargezenOptions): import("openapi-fetch").Client<paths, `${string}/${string}`>;
|
|
18
|
+
export type Chargezen = ReturnType<typeof createChargezen>;
|
|
19
|
+
/** How long to keep asking, and how often. */
|
|
20
|
+
export interface Polling {
|
|
21
|
+
intervalMs: number;
|
|
22
|
+
timeoutMs: number;
|
|
23
|
+
}
|
|
24
|
+
type OrderNowBody = paths["/customers/{customer_id}/subscriptions/{id}/order_now"]["post"]["requestBody"]["content"]["application/json"];
|
|
25
|
+
/**
|
|
26
|
+
* Places the subscription's next coming order now. While the charge is still settling the API answers
|
|
27
|
+
* 202 "processing"; this repeats the request with the same Idempotency-Key, which never charges twice,
|
|
28
|
+
* until the order is placed or needs the customer's payment approval, or `polling.timeoutMs` passes
|
|
29
|
+
* (then it returns the last "processing" answer: call again with the same key to keep waiting).
|
|
30
|
+
*/
|
|
31
|
+
export declare function orderNow(chargezen: Chargezen, request: {
|
|
32
|
+
customerId: string;
|
|
33
|
+
subscriptionId: string;
|
|
34
|
+
billingOccurrence: OrderNowBody["billing_occurrence"];
|
|
35
|
+
idempotencyKey: string;
|
|
36
|
+
}, polling: Polling): Promise<import("openapi-fetch").FetchResponse<{
|
|
37
|
+
parameters: {
|
|
38
|
+
query?: never;
|
|
39
|
+
header: {
|
|
40
|
+
"Idempotency-Key": components["parameters"]["IdempotencyKey"];
|
|
41
|
+
};
|
|
42
|
+
path: {
|
|
43
|
+
customer_id: string;
|
|
44
|
+
id: string;
|
|
45
|
+
};
|
|
46
|
+
cookie?: never;
|
|
47
|
+
};
|
|
48
|
+
requestBody: {
|
|
49
|
+
content: {
|
|
50
|
+
"application/json": {
|
|
51
|
+
billing_occurrence: {
|
|
52
|
+
cycle_index: number;
|
|
53
|
+
scheduled_at: string;
|
|
54
|
+
};
|
|
55
|
+
};
|
|
56
|
+
};
|
|
57
|
+
};
|
|
58
|
+
responses: {
|
|
59
|
+
200: {
|
|
60
|
+
headers: {
|
|
61
|
+
[name: string]: unknown;
|
|
62
|
+
};
|
|
63
|
+
content: {
|
|
64
|
+
"application/json": {
|
|
65
|
+
status: "placed";
|
|
66
|
+
next_charge_at: string | null;
|
|
67
|
+
} | {
|
|
68
|
+
status: "needs_payment_approval";
|
|
69
|
+
payment_approval_url: string;
|
|
70
|
+
};
|
|
71
|
+
};
|
|
72
|
+
};
|
|
73
|
+
202: {
|
|
74
|
+
headers: {
|
|
75
|
+
[name: string]: unknown;
|
|
76
|
+
};
|
|
77
|
+
content: {
|
|
78
|
+
"application/json": {
|
|
79
|
+
status: "processing";
|
|
80
|
+
};
|
|
81
|
+
};
|
|
82
|
+
};
|
|
83
|
+
401: components["responses"]["Error"];
|
|
84
|
+
403: components["responses"]["Error"];
|
|
85
|
+
404: components["responses"]["Error"];
|
|
86
|
+
409: components["responses"]["Error"];
|
|
87
|
+
422: components["responses"]["Error"];
|
|
88
|
+
503: components["responses"]["Error"];
|
|
89
|
+
};
|
|
90
|
+
}, {
|
|
91
|
+
params: {
|
|
92
|
+
path: {
|
|
93
|
+
customer_id: string;
|
|
94
|
+
id: string;
|
|
95
|
+
};
|
|
96
|
+
header: {
|
|
97
|
+
"Idempotency-Key": string;
|
|
98
|
+
};
|
|
99
|
+
};
|
|
100
|
+
body: {
|
|
101
|
+
billing_occurrence: {
|
|
102
|
+
cycle_index: number;
|
|
103
|
+
scheduled_at: string;
|
|
104
|
+
};
|
|
105
|
+
};
|
|
106
|
+
}, `${string}/${string}`>>;
|
|
107
|
+
/**
|
|
108
|
+
* Follows a subscription creation (the `id` POST /customers/{customer_id}/subscriptions returned) until
|
|
109
|
+
* it is "ready", or `polling.timeoutMs` passes (then it returns the last "pending" answer).
|
|
110
|
+
*/
|
|
111
|
+
export declare function waitForSubscriptionCreation(chargezen: Chargezen, creationId: string, polling: Polling): Promise<import("openapi-fetch").FetchResponse<{
|
|
112
|
+
parameters: {
|
|
113
|
+
query?: never;
|
|
114
|
+
header?: never;
|
|
115
|
+
path: {
|
|
116
|
+
id: string;
|
|
117
|
+
};
|
|
118
|
+
cookie?: never;
|
|
119
|
+
};
|
|
120
|
+
requestBody?: never;
|
|
121
|
+
responses: {
|
|
122
|
+
200: {
|
|
123
|
+
headers: {
|
|
124
|
+
[name: string]: unknown;
|
|
125
|
+
};
|
|
126
|
+
content: {
|
|
127
|
+
"application/json": {
|
|
128
|
+
status: "pending" | "ready";
|
|
129
|
+
subscription_contract_id: string;
|
|
130
|
+
subscription_ref: {
|
|
131
|
+
type: "customer_subscription_contract";
|
|
132
|
+
id: string;
|
|
133
|
+
} | null;
|
|
134
|
+
observed_at: string;
|
|
135
|
+
};
|
|
136
|
+
};
|
|
137
|
+
};
|
|
138
|
+
401: components["responses"]["Error"];
|
|
139
|
+
403: components["responses"]["Error"];
|
|
140
|
+
404: components["responses"]["Error"];
|
|
141
|
+
409: components["responses"]["Error"];
|
|
142
|
+
422: components["responses"]["Error"];
|
|
143
|
+
503: components["responses"]["Error"];
|
|
144
|
+
};
|
|
145
|
+
}, {
|
|
146
|
+
params: {
|
|
147
|
+
path: {
|
|
148
|
+
id: string;
|
|
149
|
+
};
|
|
150
|
+
};
|
|
151
|
+
}, `${string}/${string}`>>;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import createClient from "openapi-fetch";
|
|
2
|
+
/**
|
|
3
|
+
* A typed client for every path in the Chargezen API, for example
|
|
4
|
+
* `chargezen.GET("/customers", { params: { query: { query: "ada@example.com" } } })`.
|
|
5
|
+
* Every POST takes an Idempotency-Key header: make one per change (crypto.randomUUID()) and send the
|
|
6
|
+
* same key again to retry that change safely.
|
|
7
|
+
*/
|
|
8
|
+
export function createChargezen({ keyId, secret, baseUrl = "https://api.chargezen.com/v1", fetch }) {
|
|
9
|
+
return createClient({
|
|
10
|
+
baseUrl,
|
|
11
|
+
fetch,
|
|
12
|
+
headers: { "X-Chargezen-Key-Id": keyId, Authorization: `Bearer ${secret}` },
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Places the subscription's next coming order now. While the charge is still settling the API answers
|
|
17
|
+
* 202 "processing"; this repeats the request with the same Idempotency-Key, which never charges twice,
|
|
18
|
+
* until the order is placed or needs the customer's payment approval, or `polling.timeoutMs` passes
|
|
19
|
+
* (then it returns the last "processing" answer: call again with the same key to keep waiting).
|
|
20
|
+
*/
|
|
21
|
+
export async function orderNow(chargezen, request, polling) {
|
|
22
|
+
const deadline = Date.now() + polling.timeoutMs;
|
|
23
|
+
for (;;) {
|
|
24
|
+
const result = await chargezen.POST("/customers/{customer_id}/subscriptions/{id}/order_now", {
|
|
25
|
+
params: {
|
|
26
|
+
path: { customer_id: request.customerId, id: request.subscriptionId },
|
|
27
|
+
header: { "Idempotency-Key": request.idempotencyKey },
|
|
28
|
+
},
|
|
29
|
+
body: { billing_occurrence: request.billingOccurrence },
|
|
30
|
+
});
|
|
31
|
+
if (result.response.status !== 202 || Date.now() + polling.intervalMs > deadline)
|
|
32
|
+
return result;
|
|
33
|
+
await sleep(polling.intervalMs);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Follows a subscription creation (the `id` POST /customers/{customer_id}/subscriptions returned) until
|
|
38
|
+
* it is "ready", or `polling.timeoutMs` passes (then it returns the last "pending" answer).
|
|
39
|
+
*/
|
|
40
|
+
export async function waitForSubscriptionCreation(chargezen, creationId, polling) {
|
|
41
|
+
const deadline = Date.now() + polling.timeoutMs;
|
|
42
|
+
for (;;) {
|
|
43
|
+
const result = await chargezen.GET("/subscription_creations/{id}", { params: { path: { id: creationId } } });
|
|
44
|
+
if (result.data?.status !== "pending" || Date.now() + polling.intervalMs > deadline)
|
|
45
|
+
return result;
|
|
46
|
+
await sleep(polling.intervalMs);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
function sleep(ms) {
|
|
50
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
51
|
+
}
|