@sphoq/payments 0.1.0 → 0.1.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/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,
@@ -58,6 +232,26 @@ class ChargesClient {
58
232
  body.metadata = options.metadata;
59
233
  return this.request("/v1/charges", body);
60
234
  }
235
+ /**
236
+ * Retrieve an existing charge by Sphoq ID or your external ID.
237
+ *
238
+ * At least one of `id` or `externalId` must be provided.
239
+ *
240
+ * @param options - Lookup criteria (Sphoq ID or external ID).
241
+ * @returns The charge object with current status.
242
+ * @throws {@link SphoqApiError} with status 404 if the charge is not found.
243
+ *
244
+ * @example By Sphoq ID
245
+ * ```ts
246
+ * const charge = await sphoq.charges.get({ id: "ch_abc123" });
247
+ * console.log(charge.status); // "pending" | "paid" | "expired" | "cancelled"
248
+ * ```
249
+ *
250
+ * @example By external ID
251
+ * ```ts
252
+ * const charge = await sphoq.charges.get({ externalId: "order_456" });
253
+ * ```
254
+ */
61
255
  async get(options) {
62
256
  if (!options.id && !options.externalId) {
63
257
  throw new Error("Either id or externalId is required");
@@ -69,6 +263,24 @@ class ChargesClient {
69
263
  body.externalId = options.externalId;
70
264
  return this.request("/v1/charges/get", body);
71
265
  }
266
+ /**
267
+ * Cancel a pending PIX charge.
268
+ *
269
+ * Only charges with `status: "pending"` can be cancelled.
270
+ * After cancellation, the QR code is invalidated and the charge
271
+ * cannot be paid.
272
+ *
273
+ * @param options - The charge ID to cancel.
274
+ * @returns Confirmation with the cancelled charge ID and status.
275
+ * @throws {@link SphoqApiError} with status 400 if the charge is not pending.
276
+ * @throws {@link SphoqApiError} with status 404 if the charge is not found.
277
+ *
278
+ * @example
279
+ * ```ts
280
+ * const result = await sphoq.charges.cancel({ id: "ch_abc123" });
281
+ * console.log(result.status); // "cancelled"
282
+ * ```
283
+ */
72
284
  async cancel(options) {
73
285
  return this.request("/v1/charges/cancel", {
74
286
  id: options.id,
package/dist/types.d.ts CHANGED
@@ -1,60 +1,300 @@
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
+ * Options for creating a new PIX charge.
30
+ *
31
+ * @example
32
+ * ```ts
33
+ * const charge = await sphoq.charges.create({
34
+ * amount: 49.90,
35
+ * description: "Premium Plan - Monthly",
36
+ * externalId: "order_123",
37
+ * customer: {
38
+ * name: "João Silva",
39
+ * email: "joao@example.com",
40
+ * cpf: "12345678900",
41
+ * },
42
+ * expirationSeconds: 3600, // 1 hour
43
+ * metadata: { planId: "premium", userId: "usr_abc" },
44
+ * });
45
+ * ```
46
+ */
5
47
  export interface CreateChargeOptions {
48
+ /**
49
+ * Charge amount in BRL (Brazilian Real).
50
+ * Must be greater than 0. Use decimal notation (e.g., `29.90` for R$ 29,90).
51
+ */
6
52
  amount: number;
53
+ /** Human-readable description shown to the customer. */
7
54
  description: string;
55
+ /**
56
+ * Your own unique identifier for this charge.
57
+ * Useful for correlating Sphoq charges with records in your database.
58
+ * Must be unique per store.
59
+ *
60
+ * @example "order_abc123"
61
+ */
8
62
  externalId?: string;
63
+ /** Customer information attached to the charge. */
9
64
  customer?: {
65
+ /** Customer's full name. */
10
66
  name?: string;
67
+ /** Customer's email address. */
11
68
  email?: string;
69
+ /** Customer's CPF (Brazilian tax ID, 11 digits, no formatting). */
12
70
  cpf?: string;
13
71
  };
72
+ /**
73
+ * Time in seconds until the PIX charge expires.
74
+ * After expiration, the QR code can no longer be paid.
75
+ *
76
+ * @default 3600 (1 hour)
77
+ */
14
78
  expirationSeconds?: number;
79
+ /**
80
+ * Arbitrary key-value data attached to the charge.
81
+ * Returned in webhook payloads and charge queries.
82
+ * Useful for storing your application context (plan IDs, user IDs, etc.).
83
+ *
84
+ * @example { planId: "premium", userId: "usr_abc" }
85
+ */
15
86
  metadata?: Record<string, unknown>;
16
87
  }
88
+ /**
89
+ * A PIX charge object returned by the Sphoq API.
90
+ *
91
+ * @example
92
+ * ```ts
93
+ * const charge = await sphoq.charges.create({
94
+ * amount: 29.90,
95
+ * description: "Monthly subscription",
96
+ * });
97
+ *
98
+ * // Display QR code to user
99
+ * console.log(charge.qrCode); // Base64-encoded QR code image
100
+ * console.log(charge.pixCopiaECola); // PIX copy-paste string
101
+ * console.log(charge.status); // "pending"
102
+ *
103
+ * // After payment, query the charge
104
+ * const paid = await sphoq.charges.get({ id: charge.id });
105
+ * console.log(paid.status); // "paid"
106
+ * console.log(paid.paidAt); // Unix timestamp in ms
107
+ * console.log(paid.endToEndId); // Central Bank end-to-end ID
108
+ * ```
109
+ */
17
110
  export interface Charge {
111
+ /** Unique Sphoq charge ID. Use this to query or cancel the charge. */
18
112
  id: string;
113
+ /** Your external ID, if provided when creating the charge. */
19
114
  externalId?: string;
115
+ /** Charge amount in BRL. */
20
116
  amount: number;
117
+ /**
118
+ * Currency code. Always `"BRL"` (Brazilian Real).
119
+ */
21
120
  currency: string;
121
+ /** Human-readable description of the charge. */
22
122
  description: string;
123
+ /**
124
+ * Current status of the charge.
125
+ *
126
+ * - `"pending"` — Awaiting payment. QR code is active.
127
+ * - `"paid"` — Payment confirmed by the bank.
128
+ * - `"expired"` — QR code expired before payment.
129
+ * - `"cancelled"` — Charge was cancelled via the API.
130
+ */
23
131
  status: "pending" | "paid" | "expired" | "cancelled";
132
+ /** PIX transaction ID (txid). Unique identifier in the Brazilian PIX system. */
24
133
  txid: string;
134
+ /**
135
+ * PIX "copia e cola" (copy-paste) string.
136
+ * The customer can paste this into their banking app to pay.
137
+ */
25
138
  pixCopiaECola?: string;
139
+ /**
140
+ * Base64-encoded PNG image of the PIX QR code.
141
+ * Display this to the customer for scanning.
142
+ */
26
143
  qrCode?: string;
144
+ /** Customer name, if provided. */
27
145
  customerName?: string;
146
+ /** Customer email, if provided. */
28
147
  customerEmail?: string;
148
+ /**
149
+ * Unix timestamp (milliseconds) when the payment was confirmed.
150
+ * Only present when `status` is `"paid"`.
151
+ */
29
152
  paidAt?: number;
153
+ /**
154
+ * Central Bank end-to-end ID for the PIX transaction.
155
+ * Only present when `status` is `"paid"`.
156
+ * Can be used for reconciliation with bank statements.
157
+ */
30
158
  endToEndId?: string;
159
+ /**
160
+ * Arbitrary metadata attached when the charge was created.
161
+ * Returned as-is from the API.
162
+ */
31
163
  metadata?: Record<string, unknown>;
164
+ /** Unix timestamp (milliseconds) when the charge was created. */
32
165
  createdAt: number;
166
+ /** Unix timestamp (milliseconds) when the PIX QR code expires. */
33
167
  expiresAt: number;
34
168
  }
169
+ /**
170
+ * Options for retrieving an existing charge.
171
+ * Provide either `id` (Sphoq charge ID) or `externalId` (your own ID) — at least one is required.
172
+ *
173
+ * @example
174
+ * ```ts
175
+ * // By Sphoq ID
176
+ * const charge = await sphoq.charges.get({ id: "ch_abc123" });
177
+ *
178
+ * // By your external ID
179
+ * const charge = await sphoq.charges.get({ externalId: "order_456" });
180
+ * ```
181
+ */
35
182
  export interface GetChargeOptions {
183
+ /** Sphoq charge ID. */
36
184
  id?: string;
185
+ /** Your external ID provided when the charge was created. */
37
186
  externalId?: string;
38
187
  }
188
+ /**
189
+ * Options for cancelling a pending charge.
190
+ *
191
+ * @example
192
+ * ```ts
193
+ * const result = await sphoq.charges.cancel({ id: "ch_abc123" });
194
+ * console.log(result.status); // "cancelled"
195
+ * ```
196
+ */
39
197
  export interface CancelChargeOptions {
198
+ /** Sphoq charge ID to cancel. The charge must have `status: "pending"`. */
40
199
  id: string;
41
200
  }
201
+ /**
202
+ * Result of a successful charge cancellation.
203
+ */
42
204
  export interface CancelChargeResult {
205
+ /** The cancelled charge's ID. */
43
206
  id: string;
207
+ /** Always `"cancelled"`. */
44
208
  status: "cancelled";
45
209
  }
210
+ /**
211
+ * Webhook event payload sent by Sphoq to your endpoint.
212
+ *
213
+ * Sphoq sends webhooks for payment lifecycle events. Each payload includes
214
+ * the event type, the full charge data, and a timestamp.
215
+ *
216
+ * Always verify the webhook signature before processing:
217
+ *
218
+ * @example
219
+ * ```ts
220
+ * // Convex HTTP endpoint (httpAction)
221
+ * const handler = httpAction(async (ctx, req) => {
222
+ * const body = await req.text();
223
+ * const signature = req.headers.get("x-sphoq-signature")!;
224
+ *
225
+ * const isValid = await SphoqPayments.verifyWebhookSignature({
226
+ * payload: body,
227
+ * signature,
228
+ * secret: process.env.SPHOQ_WEBHOOK_SECRET!,
229
+ * });
230
+ *
231
+ * if (!isValid) {
232
+ * return new Response("Invalid signature", { status: 401 });
233
+ * }
234
+ *
235
+ * const event: WebhookPayload = JSON.parse(body);
236
+ *
237
+ * if (event.type === "charge.paid") {
238
+ * // Update your database — the charge has been paid
239
+ * await ctx.runMutation(internal.orders.markAsPaid, {
240
+ * externalId: event.data.externalId,
241
+ * paidAt: event.data.paidAt,
242
+ * });
243
+ * }
244
+ *
245
+ * return new Response("OK", { status: 200 });
246
+ * });
247
+ * ```
248
+ */
46
249
  export interface WebhookPayload {
250
+ /**
251
+ * The event type.
252
+ *
253
+ * - `"charge.paid"` — Customer completed the PIX payment.
254
+ * - `"charge.expired"` — QR code expired before payment.
255
+ * - `"charge.cancelled"` — Charge was cancelled via the API.
256
+ */
47
257
  type: "charge.paid" | "charge.expired" | "charge.cancelled";
258
+ /** Full charge data at the time of the event. */
48
259
  data: Charge;
260
+ /** Unix timestamp (milliseconds) when the event was created. */
49
261
  createdAt: number;
50
262
  }
263
+ /**
264
+ * Options for verifying a webhook signature.
265
+ *
266
+ * @see {@link SphoqPayments.verifyWebhookSignature}
267
+ */
51
268
  export interface VerifyWebhookOptions {
269
+ /** The raw request body as a string (do NOT parse it before verifying). */
52
270
  payload: string;
271
+ /** The `x-sphoq-signature` header value from the webhook request. */
53
272
  signature: string;
273
+ /** Your webhook endpoint secret from the Sphoq dashboard. */
54
274
  secret: string;
55
275
  }
276
+ /**
277
+ * Error thrown when the Sphoq API returns a non-2xx response.
278
+ *
279
+ * @example
280
+ * ```ts
281
+ * import { SphoqApiError } from "@sphoq/payments";
282
+ *
283
+ * try {
284
+ * await sphoq.charges.cancel({ id: "invalid_id" });
285
+ * } catch (error) {
286
+ * if (error instanceof SphoqApiError) {
287
+ * console.error(error.message); // "Charge not found"
288
+ * console.error(error.statusCode); // 404
289
+ * console.error(error.body); // Raw API response body
290
+ * }
291
+ * }
292
+ * ```
293
+ */
56
294
  export declare class SphoqApiError extends Error {
295
+ /** HTTP status code from the API response (e.g., 400, 401, 404, 500). */
57
296
  readonly statusCode: number;
297
+ /** Raw response body from the API. May contain additional error details. */
58
298
  readonly body: unknown;
59
299
  constructor(message: string, statusCode: number, body?: unknown);
60
300
  }
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.1.1",
4
4
  "description": "Sphoq Payments SDK — integrate PIX payment processing into your app",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",