@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 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 it came from Sphoq.
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
- * Works in both Node.js (using crypto module) and edge runtimes (using Web Crypto API).
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 it came from Sphoq.
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
- * Works in both Node.js (using crypto module) and edge runtimes (using Web Crypto API).
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);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sphoq/payments",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Sphoq Payments SDK — integrate PIX payment processing into your app",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",