@sphoq/payments 0.1.0 → 0.2.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/dist/client.d.ts +215 -2
- package/dist/client.js +217 -3
- package/dist/index.d.ts +1 -1
- package/dist/types.d.ts +317 -0
- package/dist/types.js +20 -0
- package/package.json +1 -1
package/dist/client.d.ts
CHANGED
|
@@ -1,13 +1,152 @@
|
|
|
1
1
|
import type { SphoqPaymentsOptions, CreateChargeOptions, Charge, GetChargeOptions, CancelChargeOptions, CancelChargeResult, VerifyWebhookOptions } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Sphoq Payments SDK client.
|
|
4
|
+
*
|
|
5
|
+
* Provides methods for creating and managing PIX charges, and verifying webhook signatures.
|
|
6
|
+
* Zero dependencies — uses native `fetch` and Web Crypto API.
|
|
7
|
+
*
|
|
8
|
+
* @example Basic setup
|
|
9
|
+
* ```ts
|
|
10
|
+
* import { SphoqPayments } from "@sphoq/payments";
|
|
11
|
+
*
|
|
12
|
+
* const sphoq = new SphoqPayments({
|
|
13
|
+
* apiKey: "spk_live_abc123...",
|
|
14
|
+
* baseUrl: "https://your-deployment.convex.site",
|
|
15
|
+
* });
|
|
16
|
+
* ```
|
|
17
|
+
*
|
|
18
|
+
* @example Create a PIX charge and display QR code
|
|
19
|
+
* ```ts
|
|
20
|
+
* const charge = await sphoq.charges.create({
|
|
21
|
+
* amount: 49.90,
|
|
22
|
+
* description: "Premium Plan",
|
|
23
|
+
* externalId: "order_123",
|
|
24
|
+
* customer: { name: "João Silva", cpf: "12345678900" },
|
|
25
|
+
* });
|
|
26
|
+
*
|
|
27
|
+
* // Show QR code to user (base64 PNG)
|
|
28
|
+
* document.getElementById("qr").src = `data:image/png;base64,${charge.qrCode}`;
|
|
29
|
+
*
|
|
30
|
+
* // Or show the copy-paste PIX string
|
|
31
|
+
* console.log(charge.pixCopiaECola);
|
|
32
|
+
* ```
|
|
33
|
+
*
|
|
34
|
+
* @example Convex integration — reactive payment status
|
|
35
|
+
* ```ts
|
|
36
|
+
* // convex/http.ts — Receive Sphoq webhooks
|
|
37
|
+
* import { SphoqPayments, type WebhookPayload } from "@sphoq/payments";
|
|
38
|
+
*
|
|
39
|
+
* http.route({
|
|
40
|
+
* path: "/webhooks/sphoq",
|
|
41
|
+
* method: "POST",
|
|
42
|
+
* handler: httpAction(async (ctx, req) => {
|
|
43
|
+
* const body = await req.text();
|
|
44
|
+
* const signature = req.headers.get("x-sphoq-signature")!;
|
|
45
|
+
*
|
|
46
|
+
* const isValid = await SphoqPayments.verifyWebhookSignature({
|
|
47
|
+
* payload: body,
|
|
48
|
+
* signature,
|
|
49
|
+
* secret: process.env.SPHOQ_WEBHOOK_SECRET!,
|
|
50
|
+
* });
|
|
51
|
+
* if (!isValid) return new Response("Invalid signature", { status: 401 });
|
|
52
|
+
*
|
|
53
|
+
* const event: WebhookPayload = JSON.parse(body);
|
|
54
|
+
* if (event.type === "charge.paid") {
|
|
55
|
+
* await ctx.runMutation(internal.orders.markAsPaid, {
|
|
56
|
+
* orderId: event.data.externalId,
|
|
57
|
+
* });
|
|
58
|
+
* }
|
|
59
|
+
* return new Response("OK");
|
|
60
|
+
* }),
|
|
61
|
+
* });
|
|
62
|
+
*
|
|
63
|
+
* // React component — auto-updates when webhook triggers the mutation
|
|
64
|
+
* function CheckoutPage({ orderId }: { orderId: string }) {
|
|
65
|
+
* const order = useQuery(api.orders.get, { orderId });
|
|
66
|
+
* // order.status reactively updates from "pending" to "paid"
|
|
67
|
+
* // when the Sphoq webhook triggers markAsPaid mutation
|
|
68
|
+
* }
|
|
69
|
+
* ```
|
|
70
|
+
*/
|
|
2
71
|
export declare class SphoqPayments {
|
|
3
72
|
private readonly apiKey;
|
|
4
73
|
private readonly baseUrl;
|
|
74
|
+
/**
|
|
75
|
+
* Client for creating, querying, and cancelling PIX charges.
|
|
76
|
+
*
|
|
77
|
+
* @example
|
|
78
|
+
* ```ts
|
|
79
|
+
* // Create a charge
|
|
80
|
+
* const charge = await sphoq.charges.create({
|
|
81
|
+
* amount: 29.90,
|
|
82
|
+
* description: "Monthly subscription",
|
|
83
|
+
* });
|
|
84
|
+
*
|
|
85
|
+
* // Query a charge
|
|
86
|
+
* const status = await sphoq.charges.get({ id: charge.id });
|
|
87
|
+
*
|
|
88
|
+
* // Cancel a pending charge
|
|
89
|
+
* await sphoq.charges.cancel({ id: charge.id });
|
|
90
|
+
* ```
|
|
91
|
+
*/
|
|
5
92
|
readonly charges: ChargesClient;
|
|
93
|
+
/**
|
|
94
|
+
* Creates a new Sphoq Payments client.
|
|
95
|
+
*
|
|
96
|
+
* @param options - Client configuration (API key and base URL).
|
|
97
|
+
*
|
|
98
|
+
* @example
|
|
99
|
+
* ```ts
|
|
100
|
+
* const sphoq = new SphoqPayments({
|
|
101
|
+
* apiKey: process.env.SPHOQ_API_KEY!,
|
|
102
|
+
* baseUrl: process.env.SPHOQ_BASE_URL!,
|
|
103
|
+
* });
|
|
104
|
+
* ```
|
|
105
|
+
*/
|
|
6
106
|
constructor(options: SphoqPaymentsOptions);
|
|
7
107
|
/**
|
|
8
|
-
* Verify a webhook signature to ensure
|
|
108
|
+
* Verify a webhook signature to ensure the request came from Sphoq.
|
|
109
|
+
*
|
|
110
|
+
* Uses HMAC-SHA256 with constant-time comparison to prevent timing attacks.
|
|
111
|
+
* Works in all JavaScript runtimes: Node.js 18+, Deno, Cloudflare Workers,
|
|
112
|
+
* Vercel Edge, and Convex.
|
|
113
|
+
*
|
|
114
|
+
* **Important:** Always verify signatures before processing webhook payloads.
|
|
115
|
+
* Use the raw request body string — do not parse it before verification.
|
|
116
|
+
*
|
|
117
|
+
* @param options - The raw payload, signature header, and your webhook secret.
|
|
118
|
+
* @returns `true` if the signature is valid, `false` otherwise.
|
|
9
119
|
*
|
|
10
|
-
*
|
|
120
|
+
* @example Node.js / Express
|
|
121
|
+
* ```ts
|
|
122
|
+
* app.post("/webhooks/sphoq", express.raw({ type: "*\/*" }), async (req, res) => {
|
|
123
|
+
* const isValid = await SphoqPayments.verifyWebhookSignature({
|
|
124
|
+
* payload: req.body.toString(),
|
|
125
|
+
* signature: req.headers["x-sphoq-signature"] as string,
|
|
126
|
+
* secret: process.env.SPHOQ_WEBHOOK_SECRET!,
|
|
127
|
+
* });
|
|
128
|
+
*
|
|
129
|
+
* if (!isValid) return res.status(401).send("Invalid signature");
|
|
130
|
+
*
|
|
131
|
+
* const event = JSON.parse(req.body.toString());
|
|
132
|
+
* // Handle event...
|
|
133
|
+
* res.sendStatus(200);
|
|
134
|
+
* });
|
|
135
|
+
* ```
|
|
136
|
+
*
|
|
137
|
+
* @example Convex httpAction
|
|
138
|
+
* ```ts
|
|
139
|
+
* const handler = httpAction(async (ctx, req) => {
|
|
140
|
+
* const body = await req.text();
|
|
141
|
+
* const isValid = await SphoqPayments.verifyWebhookSignature({
|
|
142
|
+
* payload: body,
|
|
143
|
+
* signature: req.headers.get("x-sphoq-signature")!,
|
|
144
|
+
* secret: process.env.SPHOQ_WEBHOOK_SECRET!,
|
|
145
|
+
* });
|
|
146
|
+
* if (!isValid) return new Response("Invalid", { status: 401 });
|
|
147
|
+
* // Process the webhook...
|
|
148
|
+
* });
|
|
149
|
+
* ```
|
|
11
150
|
*/
|
|
12
151
|
static verifyWebhookSignature(options: VerifyWebhookOptions): Promise<boolean>;
|
|
13
152
|
}
|
|
@@ -15,8 +154,82 @@ declare class ChargesClient {
|
|
|
15
154
|
private readonly apiKey;
|
|
16
155
|
private readonly baseUrl;
|
|
17
156
|
constructor(apiKey: string, baseUrl: string);
|
|
157
|
+
/**
|
|
158
|
+
* Create a new PIX charge.
|
|
159
|
+
*
|
|
160
|
+
* Generates a PIX QR code and copy-paste string that the customer can use to pay.
|
|
161
|
+
* The charge starts with `status: "pending"` and transitions to `"paid"` when
|
|
162
|
+
* the customer completes the payment.
|
|
163
|
+
*
|
|
164
|
+
* @param options - Charge details (amount, description, customer info, etc.).
|
|
165
|
+
* @returns The created charge with QR code data.
|
|
166
|
+
* @throws {@link SphoqApiError} if the API returns an error (invalid params, rate limit, etc.).
|
|
167
|
+
*
|
|
168
|
+
* @example Simple charge
|
|
169
|
+
* ```ts
|
|
170
|
+
* const charge = await sphoq.charges.create({
|
|
171
|
+
* amount: 29.90,
|
|
172
|
+
* description: "Monthly subscription",
|
|
173
|
+
* });
|
|
174
|
+
* console.log(charge.pixCopiaECola); // PIX copy-paste string
|
|
175
|
+
* ```
|
|
176
|
+
*
|
|
177
|
+
* @example Full options
|
|
178
|
+
* ```ts
|
|
179
|
+
* const charge = await sphoq.charges.create({
|
|
180
|
+
* amount: 149.90,
|
|
181
|
+
* description: "Annual Plan",
|
|
182
|
+
* externalId: "order_abc123",
|
|
183
|
+
* customer: {
|
|
184
|
+
* name: "Maria Santos",
|
|
185
|
+
* email: "maria@example.com",
|
|
186
|
+
* cpf: "12345678900",
|
|
187
|
+
* },
|
|
188
|
+
* expirationSeconds: 1800, // 30 minutes
|
|
189
|
+
* metadata: { planId: "annual", coupon: "SAVE20" },
|
|
190
|
+
* });
|
|
191
|
+
* ```
|
|
192
|
+
*/
|
|
18
193
|
create(options: CreateChargeOptions): Promise<Charge>;
|
|
194
|
+
/**
|
|
195
|
+
* Retrieve an existing charge by Sphoq ID or your external ID.
|
|
196
|
+
*
|
|
197
|
+
* At least one of `id` or `externalId` must be provided.
|
|
198
|
+
*
|
|
199
|
+
* @param options - Lookup criteria (Sphoq ID or external ID).
|
|
200
|
+
* @returns The charge object with current status.
|
|
201
|
+
* @throws {@link SphoqApiError} with status 404 if the charge is not found.
|
|
202
|
+
*
|
|
203
|
+
* @example By Sphoq ID
|
|
204
|
+
* ```ts
|
|
205
|
+
* const charge = await sphoq.charges.get({ id: "ch_abc123" });
|
|
206
|
+
* console.log(charge.status); // "pending" | "paid" | "expired" | "cancelled"
|
|
207
|
+
* ```
|
|
208
|
+
*
|
|
209
|
+
* @example By external ID
|
|
210
|
+
* ```ts
|
|
211
|
+
* const charge = await sphoq.charges.get({ externalId: "order_456" });
|
|
212
|
+
* ```
|
|
213
|
+
*/
|
|
19
214
|
get(options: GetChargeOptions): Promise<Charge>;
|
|
215
|
+
/**
|
|
216
|
+
* Cancel a pending PIX charge.
|
|
217
|
+
*
|
|
218
|
+
* Only charges with `status: "pending"` can be cancelled.
|
|
219
|
+
* After cancellation, the QR code is invalidated and the charge
|
|
220
|
+
* cannot be paid.
|
|
221
|
+
*
|
|
222
|
+
* @param options - The charge ID to cancel.
|
|
223
|
+
* @returns Confirmation with the cancelled charge ID and status.
|
|
224
|
+
* @throws {@link SphoqApiError} with status 400 if the charge is not pending.
|
|
225
|
+
* @throws {@link SphoqApiError} with status 404 if the charge is not found.
|
|
226
|
+
*
|
|
227
|
+
* @example
|
|
228
|
+
* ```ts
|
|
229
|
+
* const result = await sphoq.charges.cancel({ id: "ch_abc123" });
|
|
230
|
+
* console.log(result.status); // "cancelled"
|
|
231
|
+
* ```
|
|
232
|
+
*/
|
|
20
233
|
cancel(options: CancelChargeOptions): Promise<CancelChargeResult>;
|
|
21
234
|
private request;
|
|
22
235
|
}
|
package/dist/client.js
CHANGED
|
@@ -1,25 +1,163 @@
|
|
|
1
1
|
import { SphoqApiError } from "./types.js";
|
|
2
2
|
const DEFAULT_BASE_URL = "https://your-convex-deployment.convex.site";
|
|
3
|
+
/**
|
|
4
|
+
* Sphoq Payments SDK client.
|
|
5
|
+
*
|
|
6
|
+
* Provides methods for creating and managing PIX charges, and verifying webhook signatures.
|
|
7
|
+
* Zero dependencies — uses native `fetch` and Web Crypto API.
|
|
8
|
+
*
|
|
9
|
+
* @example Basic setup
|
|
10
|
+
* ```ts
|
|
11
|
+
* import { SphoqPayments } from "@sphoq/payments";
|
|
12
|
+
*
|
|
13
|
+
* const sphoq = new SphoqPayments({
|
|
14
|
+
* apiKey: "spk_live_abc123...",
|
|
15
|
+
* baseUrl: "https://your-deployment.convex.site",
|
|
16
|
+
* });
|
|
17
|
+
* ```
|
|
18
|
+
*
|
|
19
|
+
* @example Create a PIX charge and display QR code
|
|
20
|
+
* ```ts
|
|
21
|
+
* const charge = await sphoq.charges.create({
|
|
22
|
+
* amount: 49.90,
|
|
23
|
+
* description: "Premium Plan",
|
|
24
|
+
* externalId: "order_123",
|
|
25
|
+
* customer: { name: "João Silva", cpf: "12345678900" },
|
|
26
|
+
* });
|
|
27
|
+
*
|
|
28
|
+
* // Show QR code to user (base64 PNG)
|
|
29
|
+
* document.getElementById("qr").src = `data:image/png;base64,${charge.qrCode}`;
|
|
30
|
+
*
|
|
31
|
+
* // Or show the copy-paste PIX string
|
|
32
|
+
* console.log(charge.pixCopiaECola);
|
|
33
|
+
* ```
|
|
34
|
+
*
|
|
35
|
+
* @example Convex integration — reactive payment status
|
|
36
|
+
* ```ts
|
|
37
|
+
* // convex/http.ts — Receive Sphoq webhooks
|
|
38
|
+
* import { SphoqPayments, type WebhookPayload } from "@sphoq/payments";
|
|
39
|
+
*
|
|
40
|
+
* http.route({
|
|
41
|
+
* path: "/webhooks/sphoq",
|
|
42
|
+
* method: "POST",
|
|
43
|
+
* handler: httpAction(async (ctx, req) => {
|
|
44
|
+
* const body = await req.text();
|
|
45
|
+
* const signature = req.headers.get("x-sphoq-signature")!;
|
|
46
|
+
*
|
|
47
|
+
* const isValid = await SphoqPayments.verifyWebhookSignature({
|
|
48
|
+
* payload: body,
|
|
49
|
+
* signature,
|
|
50
|
+
* secret: process.env.SPHOQ_WEBHOOK_SECRET!,
|
|
51
|
+
* });
|
|
52
|
+
* if (!isValid) return new Response("Invalid signature", { status: 401 });
|
|
53
|
+
*
|
|
54
|
+
* const event: WebhookPayload = JSON.parse(body);
|
|
55
|
+
* if (event.type === "charge.paid") {
|
|
56
|
+
* await ctx.runMutation(internal.orders.markAsPaid, {
|
|
57
|
+
* orderId: event.data.externalId,
|
|
58
|
+
* });
|
|
59
|
+
* }
|
|
60
|
+
* return new Response("OK");
|
|
61
|
+
* }),
|
|
62
|
+
* });
|
|
63
|
+
*
|
|
64
|
+
* // React component — auto-updates when webhook triggers the mutation
|
|
65
|
+
* function CheckoutPage({ orderId }: { orderId: string }) {
|
|
66
|
+
* const order = useQuery(api.orders.get, { orderId });
|
|
67
|
+
* // order.status reactively updates from "pending" to "paid"
|
|
68
|
+
* // when the Sphoq webhook triggers markAsPaid mutation
|
|
69
|
+
* }
|
|
70
|
+
* ```
|
|
71
|
+
*/
|
|
3
72
|
export class SphoqPayments {
|
|
4
73
|
apiKey;
|
|
5
74
|
baseUrl;
|
|
75
|
+
/**
|
|
76
|
+
* Client for creating, querying, and cancelling PIX charges.
|
|
77
|
+
*
|
|
78
|
+
* @example
|
|
79
|
+
* ```ts
|
|
80
|
+
* // Create a charge
|
|
81
|
+
* const charge = await sphoq.charges.create({
|
|
82
|
+
* amount: 29.90,
|
|
83
|
+
* description: "Monthly subscription",
|
|
84
|
+
* });
|
|
85
|
+
*
|
|
86
|
+
* // Query a charge
|
|
87
|
+
* const status = await sphoq.charges.get({ id: charge.id });
|
|
88
|
+
*
|
|
89
|
+
* // Cancel a pending charge
|
|
90
|
+
* await sphoq.charges.cancel({ id: charge.id });
|
|
91
|
+
* ```
|
|
92
|
+
*/
|
|
6
93
|
charges;
|
|
94
|
+
/**
|
|
95
|
+
* Creates a new Sphoq Payments client.
|
|
96
|
+
*
|
|
97
|
+
* @param options - Client configuration (API key and base URL).
|
|
98
|
+
*
|
|
99
|
+
* @example
|
|
100
|
+
* ```ts
|
|
101
|
+
* const sphoq = new SphoqPayments({
|
|
102
|
+
* apiKey: process.env.SPHOQ_API_KEY!,
|
|
103
|
+
* baseUrl: process.env.SPHOQ_BASE_URL!,
|
|
104
|
+
* });
|
|
105
|
+
* ```
|
|
106
|
+
*/
|
|
7
107
|
constructor(options) {
|
|
8
108
|
this.apiKey = options.apiKey;
|
|
9
109
|
this.baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/$/, "");
|
|
10
110
|
this.charges = new ChargesClient(this.apiKey, this.baseUrl);
|
|
11
111
|
}
|
|
12
112
|
/**
|
|
13
|
-
* Verify a webhook signature to ensure
|
|
113
|
+
* Verify a webhook signature to ensure the request came from Sphoq.
|
|
114
|
+
*
|
|
115
|
+
* Uses HMAC-SHA256 with constant-time comparison to prevent timing attacks.
|
|
116
|
+
* Works in all JavaScript runtimes: Node.js 18+, Deno, Cloudflare Workers,
|
|
117
|
+
* Vercel Edge, and Convex.
|
|
118
|
+
*
|
|
119
|
+
* **Important:** Always verify signatures before processing webhook payloads.
|
|
120
|
+
* Use the raw request body string — do not parse it before verification.
|
|
121
|
+
*
|
|
122
|
+
* @param options - The raw payload, signature header, and your webhook secret.
|
|
123
|
+
* @returns `true` if the signature is valid, `false` otherwise.
|
|
14
124
|
*
|
|
15
|
-
*
|
|
125
|
+
* @example Node.js / Express
|
|
126
|
+
* ```ts
|
|
127
|
+
* app.post("/webhooks/sphoq", express.raw({ type: "*\/*" }), async (req, res) => {
|
|
128
|
+
* const isValid = await SphoqPayments.verifyWebhookSignature({
|
|
129
|
+
* payload: req.body.toString(),
|
|
130
|
+
* signature: req.headers["x-sphoq-signature"] as string,
|
|
131
|
+
* secret: process.env.SPHOQ_WEBHOOK_SECRET!,
|
|
132
|
+
* });
|
|
133
|
+
*
|
|
134
|
+
* if (!isValid) return res.status(401).send("Invalid signature");
|
|
135
|
+
*
|
|
136
|
+
* const event = JSON.parse(req.body.toString());
|
|
137
|
+
* // Handle event...
|
|
138
|
+
* res.sendStatus(200);
|
|
139
|
+
* });
|
|
140
|
+
* ```
|
|
141
|
+
*
|
|
142
|
+
* @example Convex httpAction
|
|
143
|
+
* ```ts
|
|
144
|
+
* const handler = httpAction(async (ctx, req) => {
|
|
145
|
+
* const body = await req.text();
|
|
146
|
+
* const isValid = await SphoqPayments.verifyWebhookSignature({
|
|
147
|
+
* payload: body,
|
|
148
|
+
* signature: req.headers.get("x-sphoq-signature")!,
|
|
149
|
+
* secret: process.env.SPHOQ_WEBHOOK_SECRET!,
|
|
150
|
+
* });
|
|
151
|
+
* if (!isValid) return new Response("Invalid", { status: 401 });
|
|
152
|
+
* // Process the webhook...
|
|
153
|
+
* });
|
|
154
|
+
* ```
|
|
16
155
|
*/
|
|
17
156
|
static async verifyWebhookSignature(options) {
|
|
18
157
|
const { payload, signature, secret } = options;
|
|
19
158
|
if (!signature.startsWith("sha256="))
|
|
20
159
|
return false;
|
|
21
160
|
const expectedHex = signature.slice(7);
|
|
22
|
-
// Use Web Crypto API (works in Node 18+, Deno, Cloudflare Workers, Vercel Edge)
|
|
23
161
|
const encoder = new TextEncoder();
|
|
24
162
|
const key = await globalThis.crypto.subtle.importKey("raw", encoder.encode(secret), { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
|
|
25
163
|
const signatureBuffer = await globalThis.crypto.subtle.sign("HMAC", key, encoder.encode(payload));
|
|
@@ -43,6 +181,42 @@ class ChargesClient {
|
|
|
43
181
|
this.apiKey = apiKey;
|
|
44
182
|
this.baseUrl = baseUrl;
|
|
45
183
|
}
|
|
184
|
+
/**
|
|
185
|
+
* Create a new PIX charge.
|
|
186
|
+
*
|
|
187
|
+
* Generates a PIX QR code and copy-paste string that the customer can use to pay.
|
|
188
|
+
* The charge starts with `status: "pending"` and transitions to `"paid"` when
|
|
189
|
+
* the customer completes the payment.
|
|
190
|
+
*
|
|
191
|
+
* @param options - Charge details (amount, description, customer info, etc.).
|
|
192
|
+
* @returns The created charge with QR code data.
|
|
193
|
+
* @throws {@link SphoqApiError} if the API returns an error (invalid params, rate limit, etc.).
|
|
194
|
+
*
|
|
195
|
+
* @example Simple charge
|
|
196
|
+
* ```ts
|
|
197
|
+
* const charge = await sphoq.charges.create({
|
|
198
|
+
* amount: 29.90,
|
|
199
|
+
* description: "Monthly subscription",
|
|
200
|
+
* });
|
|
201
|
+
* console.log(charge.pixCopiaECola); // PIX copy-paste string
|
|
202
|
+
* ```
|
|
203
|
+
*
|
|
204
|
+
* @example Full options
|
|
205
|
+
* ```ts
|
|
206
|
+
* const charge = await sphoq.charges.create({
|
|
207
|
+
* amount: 149.90,
|
|
208
|
+
* description: "Annual Plan",
|
|
209
|
+
* externalId: "order_abc123",
|
|
210
|
+
* customer: {
|
|
211
|
+
* name: "Maria Santos",
|
|
212
|
+
* email: "maria@example.com",
|
|
213
|
+
* cpf: "12345678900",
|
|
214
|
+
* },
|
|
215
|
+
* expirationSeconds: 1800, // 30 minutes
|
|
216
|
+
* metadata: { planId: "annual", coupon: "SAVE20" },
|
|
217
|
+
* });
|
|
218
|
+
* ```
|
|
219
|
+
*/
|
|
46
220
|
async create(options) {
|
|
47
221
|
const body = {
|
|
48
222
|
amount: options.amount,
|
|
@@ -54,10 +228,32 @@ class ChargesClient {
|
|
|
54
228
|
body.customer = options.customer;
|
|
55
229
|
if (options.expirationSeconds)
|
|
56
230
|
body.expirationSeconds = options.expirationSeconds;
|
|
231
|
+
if (options.items)
|
|
232
|
+
body.items = options.items;
|
|
57
233
|
if (options.metadata)
|
|
58
234
|
body.metadata = options.metadata;
|
|
59
235
|
return this.request("/v1/charges", body);
|
|
60
236
|
}
|
|
237
|
+
/**
|
|
238
|
+
* Retrieve an existing charge by Sphoq ID or your external ID.
|
|
239
|
+
*
|
|
240
|
+
* At least one of `id` or `externalId` must be provided.
|
|
241
|
+
*
|
|
242
|
+
* @param options - Lookup criteria (Sphoq ID or external ID).
|
|
243
|
+
* @returns The charge object with current status.
|
|
244
|
+
* @throws {@link SphoqApiError} with status 404 if the charge is not found.
|
|
245
|
+
*
|
|
246
|
+
* @example By Sphoq ID
|
|
247
|
+
* ```ts
|
|
248
|
+
* const charge = await sphoq.charges.get({ id: "ch_abc123" });
|
|
249
|
+
* console.log(charge.status); // "pending" | "paid" | "expired" | "cancelled"
|
|
250
|
+
* ```
|
|
251
|
+
*
|
|
252
|
+
* @example By external ID
|
|
253
|
+
* ```ts
|
|
254
|
+
* const charge = await sphoq.charges.get({ externalId: "order_456" });
|
|
255
|
+
* ```
|
|
256
|
+
*/
|
|
61
257
|
async get(options) {
|
|
62
258
|
if (!options.id && !options.externalId) {
|
|
63
259
|
throw new Error("Either id or externalId is required");
|
|
@@ -69,6 +265,24 @@ class ChargesClient {
|
|
|
69
265
|
body.externalId = options.externalId;
|
|
70
266
|
return this.request("/v1/charges/get", body);
|
|
71
267
|
}
|
|
268
|
+
/**
|
|
269
|
+
* Cancel a pending PIX charge.
|
|
270
|
+
*
|
|
271
|
+
* Only charges with `status: "pending"` can be cancelled.
|
|
272
|
+
* After cancellation, the QR code is invalidated and the charge
|
|
273
|
+
* cannot be paid.
|
|
274
|
+
*
|
|
275
|
+
* @param options - The charge ID to cancel.
|
|
276
|
+
* @returns Confirmation with the cancelled charge ID and status.
|
|
277
|
+
* @throws {@link SphoqApiError} with status 400 if the charge is not pending.
|
|
278
|
+
* @throws {@link SphoqApiError} with status 404 if the charge is not found.
|
|
279
|
+
*
|
|
280
|
+
* @example
|
|
281
|
+
* ```ts
|
|
282
|
+
* const result = await sphoq.charges.cancel({ id: "ch_abc123" });
|
|
283
|
+
* console.log(result.status); // "cancelled"
|
|
284
|
+
* ```
|
|
285
|
+
*/
|
|
72
286
|
async cancel(options) {
|
|
73
287
|
return this.request("/v1/charges/cancel", {
|
|
74
288
|
id: options.id,
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export { SphoqPayments } from "./client.js";
|
|
2
2
|
export { SphoqApiError } from "./types.js";
|
|
3
|
-
export type { SphoqPaymentsOptions, CreateChargeOptions, Charge, GetChargeOptions, CancelChargeOptions, CancelChargeResult, WebhookPayload, VerifyWebhookOptions, } from "./types.js";
|
|
3
|
+
export type { SphoqPaymentsOptions, ChargeItem, CreateChargeOptions, Charge, GetChargeOptions, CancelChargeOptions, CancelChargeResult, WebhookPayload, VerifyWebhookOptions, } from "./types.js";
|
package/dist/types.d.ts
CHANGED
|
@@ -1,60 +1,377 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration options for the {@link SphoqPayments} client.
|
|
3
|
+
*
|
|
4
|
+
* @example
|
|
5
|
+
* ```ts
|
|
6
|
+
* import { SphoqPayments } from "@sphoq/payments";
|
|
7
|
+
*
|
|
8
|
+
* const sphoq = new SphoqPayments({
|
|
9
|
+
* apiKey: "spk_live_abc123...",
|
|
10
|
+
* baseUrl: "https://your-deployment.convex.site",
|
|
11
|
+
* });
|
|
12
|
+
* ```
|
|
13
|
+
*/
|
|
1
14
|
export interface SphoqPaymentsOptions {
|
|
15
|
+
/**
|
|
16
|
+
* Your Sphoq API key. Starts with `spk_`.
|
|
17
|
+
* Generate one from the Plugin page in your Sphoq store dashboard.
|
|
18
|
+
*/
|
|
2
19
|
apiKey: string;
|
|
20
|
+
/**
|
|
21
|
+
* The base URL of your Sphoq Convex deployment.
|
|
22
|
+
* This is the HTTP Actions URL from your Convex dashboard (ends in `.convex.site`).
|
|
23
|
+
*
|
|
24
|
+
* @example "https://your-deployment.convex.site"
|
|
25
|
+
*/
|
|
3
26
|
baseUrl?: string;
|
|
4
27
|
}
|
|
28
|
+
/**
|
|
29
|
+
* A line item included in a charge.
|
|
30
|
+
*
|
|
31
|
+
* When `items` are provided, the sum of `quantity * unitPrice` for all items
|
|
32
|
+
* **must** equal the charge `amount`. The API will reject the request otherwise.
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* ```ts
|
|
36
|
+
* const items: ChargeItem[] = [
|
|
37
|
+
* { name: "Premium Sword", quantity: 2, unitPrice: 29.90 },
|
|
38
|
+
* { name: "Health Potion", quantity: 1, unitPrice: 10.00, externalId: "sku_hp_01" },
|
|
39
|
+
* ];
|
|
40
|
+
* // Total: (2 * 29.90) + (1 * 10.00) = 69.80
|
|
41
|
+
* const charge = await sphoq.charges.create({
|
|
42
|
+
* amount: 69.80,
|
|
43
|
+
* description: "3 items from GameStore",
|
|
44
|
+
* items,
|
|
45
|
+
* });
|
|
46
|
+
* ```
|
|
47
|
+
*/
|
|
48
|
+
export interface ChargeItem {
|
|
49
|
+
/** Product or item name. */
|
|
50
|
+
name: string;
|
|
51
|
+
/** Quantity purchased. Must be a positive integer. */
|
|
52
|
+
quantity: number;
|
|
53
|
+
/** Price per unit in BRL. Must be greater than 0. */
|
|
54
|
+
unitPrice: number;
|
|
55
|
+
/** Optional item description or variant info. */
|
|
56
|
+
description?: string;
|
|
57
|
+
/**
|
|
58
|
+
* Your own identifier for this product (SKU, product ID, etc.).
|
|
59
|
+
* Useful for reconciling items with your catalog.
|
|
60
|
+
*
|
|
61
|
+
* @example "sku_sword_01"
|
|
62
|
+
*/
|
|
63
|
+
externalId?: string;
|
|
64
|
+
/** URL to an image of the product. */
|
|
65
|
+
imageUrl?: string;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Options for creating a new PIX charge.
|
|
69
|
+
*
|
|
70
|
+
* @example Simple charge
|
|
71
|
+
* ```ts
|
|
72
|
+
* const charge = await sphoq.charges.create({
|
|
73
|
+
* amount: 49.90,
|
|
74
|
+
* description: "Premium Plan - Monthly",
|
|
75
|
+
* externalId: "order_123",
|
|
76
|
+
* customer: {
|
|
77
|
+
* name: "João Silva",
|
|
78
|
+
* email: "joao@example.com",
|
|
79
|
+
* cpf: "12345678900",
|
|
80
|
+
* },
|
|
81
|
+
* expirationSeconds: 3600, // 1 hour
|
|
82
|
+
* metadata: { planId: "premium", userId: "usr_abc" },
|
|
83
|
+
* });
|
|
84
|
+
* ```
|
|
85
|
+
*
|
|
86
|
+
* @example Charge with itemized products
|
|
87
|
+
* ```ts
|
|
88
|
+
* const charge = await sphoq.charges.create({
|
|
89
|
+
* amount: 69.80, // Must equal sum of items: (2 * 29.90) + (1 * 10.00)
|
|
90
|
+
* description: "3 items from GameStore",
|
|
91
|
+
* items: [
|
|
92
|
+
* { name: "Premium Sword", quantity: 2, unitPrice: 29.90, externalId: "sku_sword" },
|
|
93
|
+
* { name: "Health Potion", quantity: 1, unitPrice: 10.00, externalId: "sku_hp" },
|
|
94
|
+
* ],
|
|
95
|
+
* });
|
|
96
|
+
* ```
|
|
97
|
+
*/
|
|
5
98
|
export interface CreateChargeOptions {
|
|
99
|
+
/**
|
|
100
|
+
* Charge amount in BRL (Brazilian Real).
|
|
101
|
+
* Must be greater than 0. Use decimal notation (e.g., `29.90` for R$ 29,90).
|
|
102
|
+
*/
|
|
6
103
|
amount: number;
|
|
104
|
+
/** Human-readable description shown to the customer. */
|
|
7
105
|
description: string;
|
|
106
|
+
/**
|
|
107
|
+
* Your own unique identifier for this charge.
|
|
108
|
+
* Useful for correlating Sphoq charges with records in your database.
|
|
109
|
+
* Must be unique per store.
|
|
110
|
+
*
|
|
111
|
+
* @example "order_abc123"
|
|
112
|
+
*/
|
|
8
113
|
externalId?: string;
|
|
114
|
+
/** Customer information attached to the charge. */
|
|
9
115
|
customer?: {
|
|
116
|
+
/** Customer's full name. */
|
|
10
117
|
name?: string;
|
|
118
|
+
/** Customer's email address. */
|
|
11
119
|
email?: string;
|
|
120
|
+
/** Customer's CPF (Brazilian tax ID, 11 digits, no formatting). */
|
|
12
121
|
cpf?: string;
|
|
13
122
|
};
|
|
123
|
+
/**
|
|
124
|
+
* Time in seconds until the PIX charge expires.
|
|
125
|
+
* After expiration, the QR code can no longer be paid.
|
|
126
|
+
*
|
|
127
|
+
* @default 3600 (1 hour)
|
|
128
|
+
*/
|
|
14
129
|
expirationSeconds?: number;
|
|
130
|
+
/**
|
|
131
|
+
* Line items included in this charge.
|
|
132
|
+
*
|
|
133
|
+
* When provided, the sum of `quantity * unitPrice` across all items **must**
|
|
134
|
+
* exactly equal the `amount` field (within R$ 0.01 tolerance). The API rejects
|
|
135
|
+
* the request if the totals don't match.
|
|
136
|
+
*
|
|
137
|
+
* Items are stored with the charge and included in webhook payloads,
|
|
138
|
+
* making them available for order fulfillment and reconciliation.
|
|
139
|
+
*
|
|
140
|
+
* Maximum 100 items per charge.
|
|
141
|
+
*
|
|
142
|
+
* @example
|
|
143
|
+
* ```ts
|
|
144
|
+
* items: [
|
|
145
|
+
* { name: "Gamepass: VIP", quantity: 1, unitPrice: 19.90, externalId: "gp_vip" },
|
|
146
|
+
* { name: "Robux 1000", quantity: 2, unitPrice: 15.00 },
|
|
147
|
+
* ]
|
|
148
|
+
* ```
|
|
149
|
+
*/
|
|
150
|
+
items?: ChargeItem[];
|
|
151
|
+
/**
|
|
152
|
+
* Arbitrary key-value data attached to the charge.
|
|
153
|
+
* Returned in webhook payloads and charge queries.
|
|
154
|
+
* Useful for storing your application context (plan IDs, user IDs, etc.).
|
|
155
|
+
*
|
|
156
|
+
* @example { planId: "premium", userId: "usr_abc" }
|
|
157
|
+
*/
|
|
15
158
|
metadata?: Record<string, unknown>;
|
|
16
159
|
}
|
|
160
|
+
/**
|
|
161
|
+
* A PIX charge object returned by the Sphoq API.
|
|
162
|
+
*
|
|
163
|
+
* @example
|
|
164
|
+
* ```ts
|
|
165
|
+
* const charge = await sphoq.charges.create({
|
|
166
|
+
* amount: 29.90,
|
|
167
|
+
* description: "Monthly subscription",
|
|
168
|
+
* });
|
|
169
|
+
*
|
|
170
|
+
* // Display QR code to user
|
|
171
|
+
* console.log(charge.qrCode); // Base64-encoded QR code image
|
|
172
|
+
* console.log(charge.pixCopiaECola); // PIX copy-paste string
|
|
173
|
+
* console.log(charge.status); // "pending"
|
|
174
|
+
*
|
|
175
|
+
* // After payment, query the charge
|
|
176
|
+
* const paid = await sphoq.charges.get({ id: charge.id });
|
|
177
|
+
* console.log(paid.status); // "paid"
|
|
178
|
+
* console.log(paid.paidAt); // Unix timestamp in ms
|
|
179
|
+
* console.log(paid.endToEndId); // Central Bank end-to-end ID
|
|
180
|
+
* ```
|
|
181
|
+
*/
|
|
17
182
|
export interface Charge {
|
|
183
|
+
/** Unique Sphoq charge ID. Use this to query or cancel the charge. */
|
|
18
184
|
id: string;
|
|
185
|
+
/** Your external ID, if provided when creating the charge. */
|
|
19
186
|
externalId?: string;
|
|
187
|
+
/** Charge amount in BRL. */
|
|
20
188
|
amount: number;
|
|
189
|
+
/**
|
|
190
|
+
* Currency code. Always `"BRL"` (Brazilian Real).
|
|
191
|
+
*/
|
|
21
192
|
currency: string;
|
|
193
|
+
/** Human-readable description of the charge. */
|
|
22
194
|
description: string;
|
|
195
|
+
/**
|
|
196
|
+
* Current status of the charge.
|
|
197
|
+
*
|
|
198
|
+
* - `"pending"` — Awaiting payment. QR code is active.
|
|
199
|
+
* - `"paid"` — Payment confirmed by the bank.
|
|
200
|
+
* - `"expired"` — QR code expired before payment.
|
|
201
|
+
* - `"cancelled"` — Charge was cancelled via the API.
|
|
202
|
+
*/
|
|
23
203
|
status: "pending" | "paid" | "expired" | "cancelled";
|
|
204
|
+
/** PIX transaction ID (txid). Unique identifier in the Brazilian PIX system. */
|
|
24
205
|
txid: string;
|
|
206
|
+
/**
|
|
207
|
+
* PIX "copia e cola" (copy-paste) string.
|
|
208
|
+
* The customer can paste this into their banking app to pay.
|
|
209
|
+
*/
|
|
25
210
|
pixCopiaECola?: string;
|
|
211
|
+
/**
|
|
212
|
+
* Base64-encoded PNG image of the PIX QR code.
|
|
213
|
+
* Display this to the customer for scanning.
|
|
214
|
+
*/
|
|
26
215
|
qrCode?: string;
|
|
216
|
+
/** Customer name, if provided. */
|
|
27
217
|
customerName?: string;
|
|
218
|
+
/** Customer email, if provided. */
|
|
28
219
|
customerEmail?: string;
|
|
220
|
+
/**
|
|
221
|
+
* Unix timestamp (milliseconds) when the payment was confirmed.
|
|
222
|
+
* Only present when `status` is `"paid"`.
|
|
223
|
+
*/
|
|
29
224
|
paidAt?: number;
|
|
225
|
+
/**
|
|
226
|
+
* Central Bank end-to-end ID for the PIX transaction.
|
|
227
|
+
* Only present when `status` is `"paid"`.
|
|
228
|
+
* Can be used for reconciliation with bank statements.
|
|
229
|
+
*/
|
|
30
230
|
endToEndId?: string;
|
|
231
|
+
/**
|
|
232
|
+
* Line items included in the charge, if provided at creation.
|
|
233
|
+
* Returned as-is from the API. Useful for order fulfillment.
|
|
234
|
+
*/
|
|
235
|
+
items?: ChargeItem[];
|
|
236
|
+
/**
|
|
237
|
+
* Arbitrary metadata attached when the charge was created.
|
|
238
|
+
* Returned as-is from the API.
|
|
239
|
+
*/
|
|
31
240
|
metadata?: Record<string, unknown>;
|
|
241
|
+
/** Unix timestamp (milliseconds) when the charge was created. */
|
|
32
242
|
createdAt: number;
|
|
243
|
+
/** Unix timestamp (milliseconds) when the PIX QR code expires. */
|
|
33
244
|
expiresAt: number;
|
|
34
245
|
}
|
|
246
|
+
/**
|
|
247
|
+
* Options for retrieving an existing charge.
|
|
248
|
+
* Provide either `id` (Sphoq charge ID) or `externalId` (your own ID) — at least one is required.
|
|
249
|
+
*
|
|
250
|
+
* @example
|
|
251
|
+
* ```ts
|
|
252
|
+
* // By Sphoq ID
|
|
253
|
+
* const charge = await sphoq.charges.get({ id: "ch_abc123" });
|
|
254
|
+
*
|
|
255
|
+
* // By your external ID
|
|
256
|
+
* const charge = await sphoq.charges.get({ externalId: "order_456" });
|
|
257
|
+
* ```
|
|
258
|
+
*/
|
|
35
259
|
export interface GetChargeOptions {
|
|
260
|
+
/** Sphoq charge ID. */
|
|
36
261
|
id?: string;
|
|
262
|
+
/** Your external ID provided when the charge was created. */
|
|
37
263
|
externalId?: string;
|
|
38
264
|
}
|
|
265
|
+
/**
|
|
266
|
+
* Options for cancelling a pending charge.
|
|
267
|
+
*
|
|
268
|
+
* @example
|
|
269
|
+
* ```ts
|
|
270
|
+
* const result = await sphoq.charges.cancel({ id: "ch_abc123" });
|
|
271
|
+
* console.log(result.status); // "cancelled"
|
|
272
|
+
* ```
|
|
273
|
+
*/
|
|
39
274
|
export interface CancelChargeOptions {
|
|
275
|
+
/** Sphoq charge ID to cancel. The charge must have `status: "pending"`. */
|
|
40
276
|
id: string;
|
|
41
277
|
}
|
|
278
|
+
/**
|
|
279
|
+
* Result of a successful charge cancellation.
|
|
280
|
+
*/
|
|
42
281
|
export interface CancelChargeResult {
|
|
282
|
+
/** The cancelled charge's ID. */
|
|
43
283
|
id: string;
|
|
284
|
+
/** Always `"cancelled"`. */
|
|
44
285
|
status: "cancelled";
|
|
45
286
|
}
|
|
287
|
+
/**
|
|
288
|
+
* Webhook event payload sent by Sphoq to your endpoint.
|
|
289
|
+
*
|
|
290
|
+
* Sphoq sends webhooks for payment lifecycle events. Each payload includes
|
|
291
|
+
* the event type, the full charge data, and a timestamp.
|
|
292
|
+
*
|
|
293
|
+
* Always verify the webhook signature before processing:
|
|
294
|
+
*
|
|
295
|
+
* @example
|
|
296
|
+
* ```ts
|
|
297
|
+
* // Convex HTTP endpoint (httpAction)
|
|
298
|
+
* const handler = httpAction(async (ctx, req) => {
|
|
299
|
+
* const body = await req.text();
|
|
300
|
+
* const signature = req.headers.get("x-sphoq-signature")!;
|
|
301
|
+
*
|
|
302
|
+
* const isValid = await SphoqPayments.verifyWebhookSignature({
|
|
303
|
+
* payload: body,
|
|
304
|
+
* signature,
|
|
305
|
+
* secret: process.env.SPHOQ_WEBHOOK_SECRET!,
|
|
306
|
+
* });
|
|
307
|
+
*
|
|
308
|
+
* if (!isValid) {
|
|
309
|
+
* return new Response("Invalid signature", { status: 401 });
|
|
310
|
+
* }
|
|
311
|
+
*
|
|
312
|
+
* const event: WebhookPayload = JSON.parse(body);
|
|
313
|
+
*
|
|
314
|
+
* if (event.type === "charge.paid") {
|
|
315
|
+
* // Update your database — the charge has been paid
|
|
316
|
+
* await ctx.runMutation(internal.orders.markAsPaid, {
|
|
317
|
+
* externalId: event.data.externalId,
|
|
318
|
+
* paidAt: event.data.paidAt,
|
|
319
|
+
* });
|
|
320
|
+
* }
|
|
321
|
+
*
|
|
322
|
+
* return new Response("OK", { status: 200 });
|
|
323
|
+
* });
|
|
324
|
+
* ```
|
|
325
|
+
*/
|
|
46
326
|
export interface WebhookPayload {
|
|
327
|
+
/**
|
|
328
|
+
* The event type.
|
|
329
|
+
*
|
|
330
|
+
* - `"charge.paid"` — Customer completed the PIX payment.
|
|
331
|
+
* - `"charge.expired"` — QR code expired before payment.
|
|
332
|
+
* - `"charge.cancelled"` — Charge was cancelled via the API.
|
|
333
|
+
*/
|
|
47
334
|
type: "charge.paid" | "charge.expired" | "charge.cancelled";
|
|
335
|
+
/** Full charge data at the time of the event. */
|
|
48
336
|
data: Charge;
|
|
337
|
+
/** Unix timestamp (milliseconds) when the event was created. */
|
|
49
338
|
createdAt: number;
|
|
50
339
|
}
|
|
340
|
+
/**
|
|
341
|
+
* Options for verifying a webhook signature.
|
|
342
|
+
*
|
|
343
|
+
* @see {@link SphoqPayments.verifyWebhookSignature}
|
|
344
|
+
*/
|
|
51
345
|
export interface VerifyWebhookOptions {
|
|
346
|
+
/** The raw request body as a string (do NOT parse it before verifying). */
|
|
52
347
|
payload: string;
|
|
348
|
+
/** The `x-sphoq-signature` header value from the webhook request. */
|
|
53
349
|
signature: string;
|
|
350
|
+
/** Your webhook endpoint secret from the Sphoq dashboard. */
|
|
54
351
|
secret: string;
|
|
55
352
|
}
|
|
353
|
+
/**
|
|
354
|
+
* Error thrown when the Sphoq API returns a non-2xx response.
|
|
355
|
+
*
|
|
356
|
+
* @example
|
|
357
|
+
* ```ts
|
|
358
|
+
* import { SphoqApiError } from "@sphoq/payments";
|
|
359
|
+
*
|
|
360
|
+
* try {
|
|
361
|
+
* await sphoq.charges.cancel({ id: "invalid_id" });
|
|
362
|
+
* } catch (error) {
|
|
363
|
+
* if (error instanceof SphoqApiError) {
|
|
364
|
+
* console.error(error.message); // "Charge not found"
|
|
365
|
+
* console.error(error.statusCode); // 404
|
|
366
|
+
* console.error(error.body); // Raw API response body
|
|
367
|
+
* }
|
|
368
|
+
* }
|
|
369
|
+
* ```
|
|
370
|
+
*/
|
|
56
371
|
export declare class SphoqApiError extends Error {
|
|
372
|
+
/** HTTP status code from the API response (e.g., 400, 401, 404, 500). */
|
|
57
373
|
readonly statusCode: number;
|
|
374
|
+
/** Raw response body from the API. May contain additional error details. */
|
|
58
375
|
readonly body: unknown;
|
|
59
376
|
constructor(message: string, statusCode: number, body?: unknown);
|
|
60
377
|
}
|
package/dist/types.js
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error thrown when the Sphoq API returns a non-2xx response.
|
|
3
|
+
*
|
|
4
|
+
* @example
|
|
5
|
+
* ```ts
|
|
6
|
+
* import { SphoqApiError } from "@sphoq/payments";
|
|
7
|
+
*
|
|
8
|
+
* try {
|
|
9
|
+
* await sphoq.charges.cancel({ id: "invalid_id" });
|
|
10
|
+
* } catch (error) {
|
|
11
|
+
* if (error instanceof SphoqApiError) {
|
|
12
|
+
* console.error(error.message); // "Charge not found"
|
|
13
|
+
* console.error(error.statusCode); // 404
|
|
14
|
+
* console.error(error.body); // Raw API response body
|
|
15
|
+
* }
|
|
16
|
+
* }
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
1
19
|
export class SphoqApiError extends Error {
|
|
20
|
+
/** HTTP status code from the API response (e.g., 400, 401, 404, 500). */
|
|
2
21
|
statusCode;
|
|
22
|
+
/** Raw response body from the API. May contain additional error details. */
|
|
3
23
|
body;
|
|
4
24
|
constructor(message, statusCode, body) {
|
|
5
25
|
super(message);
|