@bloque/payments 0.0.2 → 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +112 -2
- package/dist/client.d.ts +3 -0
- package/dist/index.cjs +33 -2
- package/dist/index.d.ts +1 -0
- package/dist/index.js +33 -2
- package/dist/resources/webhook.d.ts +26 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -214,6 +214,59 @@ const checkout = await bloque.checkout.cancel('checkout_id_here');
|
|
|
214
214
|
|
|
215
215
|
**Returns**: A `Checkout` object with updated status reflecting the cancellation.
|
|
216
216
|
|
|
217
|
+
### Webhooks
|
|
218
|
+
|
|
219
|
+
The webhooks resource allows you to verify the authenticity of webhook events sent from Bloque.
|
|
220
|
+
|
|
221
|
+
#### Verify Webhook Signature
|
|
222
|
+
|
|
223
|
+
Verify that a webhook event was actually sent by Bloque using HMAC-SHA256 signature verification.
|
|
224
|
+
|
|
225
|
+
```typescript
|
|
226
|
+
const isValid = bloque.webhooks.verify(
|
|
227
|
+
requestBody,
|
|
228
|
+
signatureHeader,
|
|
229
|
+
{ secret: process.env.WEBHOOK_SECRET }
|
|
230
|
+
);
|
|
231
|
+
|
|
232
|
+
if (isValid) {
|
|
233
|
+
// Process the webhook event
|
|
234
|
+
} else {
|
|
235
|
+
// Reject the webhook
|
|
236
|
+
}
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
**Parameters**:
|
|
240
|
+
- `body` (string | object): The raw webhook request body
|
|
241
|
+
- `signature` (string): The signature from the `x-bloque-signature` header
|
|
242
|
+
- `options` (optional): Verification options
|
|
243
|
+
- `secret` (string): Webhook secret key. Can be set during SDK initialization or passed here
|
|
244
|
+
|
|
245
|
+
**Returns**: `boolean` - `true` if the webhook signature is valid, `false` otherwise
|
|
246
|
+
|
|
247
|
+
**Configuration**:
|
|
248
|
+
|
|
249
|
+
You can set the webhook secret during SDK initialization:
|
|
250
|
+
|
|
251
|
+
```typescript
|
|
252
|
+
const bloque = new Bloque({
|
|
253
|
+
apiKey: process.env.BLOQUE_API_KEY!,
|
|
254
|
+
server: 'production',
|
|
255
|
+
webhookSecret: process.env.BLOQUE_WEBHOOK_SECRET,
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
// Then you can verify without passing the secret each time
|
|
259
|
+
const isValid = bloque.webhooks.verify(body, signature);
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
Or pass it directly during verification:
|
|
263
|
+
|
|
264
|
+
```typescript
|
|
265
|
+
const isValid = bloque.webhooks.verify(body, signature, {
|
|
266
|
+
secret: process.env.BLOQUE_WEBHOOK_SECRET,
|
|
267
|
+
});
|
|
268
|
+
```
|
|
269
|
+
|
|
217
270
|
## Examples
|
|
218
271
|
|
|
219
272
|
### Processing Payments
|
|
@@ -345,6 +398,61 @@ async function handlePayment(payload: PaymentSubmitPayload) {
|
|
|
345
398
|
}
|
|
346
399
|
```
|
|
347
400
|
|
|
401
|
+
### Webhook Handler
|
|
402
|
+
|
|
403
|
+
Handle incoming webhook events from Bloque and verify their authenticity:
|
|
404
|
+
|
|
405
|
+
```typescript
|
|
406
|
+
import { Bloque } from '@bloque/payments';
|
|
407
|
+
|
|
408
|
+
// Initialize SDK with webhook secret
|
|
409
|
+
const bloque = new Bloque({
|
|
410
|
+
apiKey: process.env.BLOQUE_API_KEY!,
|
|
411
|
+
server: 'production',
|
|
412
|
+
webhookSecret: process.env.BLOQUE_WEBHOOK_SECRET,
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
// Webhook endpoint
|
|
416
|
+
app.post(
|
|
417
|
+
'/webhooks/bloque',
|
|
418
|
+
(req, res) => {
|
|
419
|
+
const signature = req.headers['x-bloque-signature'] as string;
|
|
420
|
+
|
|
421
|
+
try {
|
|
422
|
+
// Verify the webhook signature
|
|
423
|
+
const isValid = bloque.webhooks.verify(req.body.toString(), signature);
|
|
424
|
+
|
|
425
|
+
if (!isValid) {
|
|
426
|
+
return res.status(400).send('Invalid signature');
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
// Parse and process the event
|
|
430
|
+
const event = JSON.parse(req.body.toString());
|
|
431
|
+
|
|
432
|
+
switch (event.type) {
|
|
433
|
+
case 'checkout.completed':
|
|
434
|
+
console.log('Checkout completed:', event.data);
|
|
435
|
+
// Update database, send confirmation, etc.
|
|
436
|
+
break;
|
|
437
|
+
|
|
438
|
+
case 'payment.succeeded':
|
|
439
|
+
console.log('Payment succeeded:', event.data);
|
|
440
|
+
break;
|
|
441
|
+
|
|
442
|
+
case 'payment.failed':
|
|
443
|
+
console.log('Payment failed:', event.data);
|
|
444
|
+
break;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
res.json({ received: true });
|
|
448
|
+
} catch (error) {
|
|
449
|
+
console.error('Webhook error:', error);
|
|
450
|
+
res.status(400).send('Webhook error');
|
|
451
|
+
}
|
|
452
|
+
},
|
|
453
|
+
);
|
|
454
|
+
```
|
|
455
|
+
|
|
348
456
|
### Basic Checkout with Single Item
|
|
349
457
|
|
|
350
458
|
```typescript
|
|
@@ -444,6 +552,8 @@ import type {
|
|
|
444
552
|
CheckoutStatus,
|
|
445
553
|
CheckoutItem,
|
|
446
554
|
CheckoutParams,
|
|
555
|
+
// Webhook types
|
|
556
|
+
WebhookVerifyOptions,
|
|
447
557
|
} from '@bloque/payments';
|
|
448
558
|
|
|
449
559
|
const item: CheckoutItem = {
|
|
@@ -526,8 +636,8 @@ bun run check
|
|
|
526
636
|
## Links
|
|
527
637
|
|
|
528
638
|
- [Homepage](https://www.bloque.app)
|
|
529
|
-
- [GitHub Repository](git+https://github.com/bloque-app/
|
|
530
|
-
- [Issue Tracker](git+https://github.com/bloque-app/
|
|
639
|
+
- [GitHub Repository](git+https://github.com/bloque-app/payments.git)
|
|
640
|
+
- [Issue Tracker](git+https://github.com/bloque-app/payments.git/issues)
|
|
531
641
|
|
|
532
642
|
## License
|
|
533
643
|
|
package/dist/client.d.ts
CHANGED
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
import { CheckoutResource } from './resources/checkout';
|
|
2
2
|
import { PaymentResource } from './resources/payment';
|
|
3
|
+
import { WebhookResource } from './resources/webhook';
|
|
3
4
|
export type BloqueConfig = {
|
|
4
5
|
server: 'sandbox' | 'production';
|
|
5
6
|
apiKey: string;
|
|
6
7
|
timeout?: number;
|
|
7
8
|
maxRetries?: number;
|
|
9
|
+
webhookSecret?: string;
|
|
8
10
|
};
|
|
9
11
|
export declare class Bloque {
|
|
10
12
|
#private;
|
|
11
13
|
checkout: CheckoutResource;
|
|
12
14
|
payments: PaymentResource;
|
|
15
|
+
webhooks: WebhookResource;
|
|
13
16
|
constructor(config: BloqueConfig);
|
|
14
17
|
private initializeResources;
|
|
15
18
|
}
|
package/dist/index.cjs
CHANGED
|
@@ -26,7 +26,7 @@ __webpack_require__.r(__webpack_exports__);
|
|
|
26
26
|
__webpack_require__.d(__webpack_exports__, {
|
|
27
27
|
Bloque: ()=>Bloque
|
|
28
28
|
});
|
|
29
|
-
var package_namespaceObject = JSON.parse('{"UU":"@bloque/payments","rE":"0.0.
|
|
29
|
+
var package_namespaceObject = JSON.parse('{"UU":"@bloque/payments","rE":"0.0.3"}');
|
|
30
30
|
class BloqueError extends Error {
|
|
31
31
|
constructor(message){
|
|
32
32
|
super(message);
|
|
@@ -282,16 +282,46 @@ class PaymentResource extends BaseResource {
|
|
|
282
282
|
};
|
|
283
283
|
}
|
|
284
284
|
}
|
|
285
|
+
const external_node_crypto_namespaceObject = require("node:crypto");
|
|
286
|
+
class WebhookResource {
|
|
287
|
+
#secret;
|
|
288
|
+
constructor(secret){
|
|
289
|
+
this.#secret = secret || '';
|
|
290
|
+
}
|
|
291
|
+
verify(body, signature, options) {
|
|
292
|
+
const secret = options?.secret || this.#secret;
|
|
293
|
+
if (!secret) throw new Error('Webhook secret is required. Pass it in options or set it during Bloque initialization.');
|
|
294
|
+
if (!signature) return false;
|
|
295
|
+
const payload = 'string' == typeof body ? body : JSON.stringify(body);
|
|
296
|
+
const computedSignature = this.#computeSignature(payload, secret);
|
|
297
|
+
return this.#secureCompare(signature, computedSignature);
|
|
298
|
+
}
|
|
299
|
+
setSecret(secret) {
|
|
300
|
+
this.#secret = secret;
|
|
301
|
+
}
|
|
302
|
+
#computeSignature(payload, secret) {
|
|
303
|
+
const hmac = (0, external_node_crypto_namespaceObject.createHmac)('sha256', secret);
|
|
304
|
+
hmac.update(payload);
|
|
305
|
+
return hmac.digest('hex');
|
|
306
|
+
}
|
|
307
|
+
#secureCompare(a, b) {
|
|
308
|
+
if (a.length !== b.length) return false;
|
|
309
|
+
let result = 0;
|
|
310
|
+
for(let i = 0; i < a.length; i++)result |= a.charCodeAt(i) ^ b.charCodeAt(i);
|
|
311
|
+
return 0 === result;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
285
314
|
class Bloque {
|
|
286
315
|
#httpClient;
|
|
287
316
|
#config;
|
|
288
317
|
checkout;
|
|
289
318
|
payments;
|
|
319
|
+
webhooks;
|
|
290
320
|
constructor(config){
|
|
291
321
|
if (!config.apiKey) throw new Error('API key is required');
|
|
292
322
|
this.#config = config;
|
|
293
323
|
this.#httpClient = new HttpClient({
|
|
294
|
-
baseURL: 'https:
|
|
324
|
+
baseURL: 'sandbox' === this.#config.server ? 'https://dev.bloque.app/api/payments' : 'https://api.bloque.app/api/payments',
|
|
295
325
|
apiKey: this.#config.apiKey,
|
|
296
326
|
timeout: this.#config.timeout,
|
|
297
327
|
maxRetries: this.#config.maxRetries
|
|
@@ -301,6 +331,7 @@ class Bloque {
|
|
|
301
331
|
initializeResources() {
|
|
302
332
|
this.checkout = new CheckoutResource(this.#httpClient);
|
|
303
333
|
this.payments = new PaymentResource(this.#httpClient);
|
|
334
|
+
this.webhooks = new WebhookResource(this.#config.webhookSecret);
|
|
304
335
|
}
|
|
305
336
|
}
|
|
306
337
|
exports.Bloque = __webpack_exports__.Bloque;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
export type { CreatePaymentParams, PaymentResponse, PaymentSubmitPayload, } from '@bloque/payments-core';
|
|
2
2
|
export { Bloque, type BloqueConfig } from './client';
|
|
3
|
+
export type { WebhookVerifyOptions } from './resources/webhook';
|
|
3
4
|
export type { Checkout, CheckoutItem, CheckoutParams, CheckoutStatus, } from './types/checkout';
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
|
|
1
|
+
import { createHmac } from "node:crypto";
|
|
2
|
+
var package_namespaceObject = JSON.parse('{"UU":"@bloque/payments","rE":"0.0.3"}');
|
|
2
3
|
class BloqueError extends Error {
|
|
3
4
|
constructor(message){
|
|
4
5
|
super(message);
|
|
@@ -254,16 +255,45 @@ class PaymentResource extends BaseResource {
|
|
|
254
255
|
};
|
|
255
256
|
}
|
|
256
257
|
}
|
|
258
|
+
class WebhookResource {
|
|
259
|
+
#secret;
|
|
260
|
+
constructor(secret){
|
|
261
|
+
this.#secret = secret || '';
|
|
262
|
+
}
|
|
263
|
+
verify(body, signature, options) {
|
|
264
|
+
const secret = options?.secret || this.#secret;
|
|
265
|
+
if (!secret) throw new Error('Webhook secret is required. Pass it in options or set it during Bloque initialization.');
|
|
266
|
+
if (!signature) return false;
|
|
267
|
+
const payload = 'string' == typeof body ? body : JSON.stringify(body);
|
|
268
|
+
const computedSignature = this.#computeSignature(payload, secret);
|
|
269
|
+
return this.#secureCompare(signature, computedSignature);
|
|
270
|
+
}
|
|
271
|
+
setSecret(secret) {
|
|
272
|
+
this.#secret = secret;
|
|
273
|
+
}
|
|
274
|
+
#computeSignature(payload, secret) {
|
|
275
|
+
const hmac = createHmac('sha256', secret);
|
|
276
|
+
hmac.update(payload);
|
|
277
|
+
return hmac.digest('hex');
|
|
278
|
+
}
|
|
279
|
+
#secureCompare(a, b) {
|
|
280
|
+
if (a.length !== b.length) return false;
|
|
281
|
+
let result = 0;
|
|
282
|
+
for(let i = 0; i < a.length; i++)result |= a.charCodeAt(i) ^ b.charCodeAt(i);
|
|
283
|
+
return 0 === result;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
257
286
|
class Bloque {
|
|
258
287
|
#httpClient;
|
|
259
288
|
#config;
|
|
260
289
|
checkout;
|
|
261
290
|
payments;
|
|
291
|
+
webhooks;
|
|
262
292
|
constructor(config){
|
|
263
293
|
if (!config.apiKey) throw new Error('API key is required');
|
|
264
294
|
this.#config = config;
|
|
265
295
|
this.#httpClient = new HttpClient({
|
|
266
|
-
baseURL: 'https:
|
|
296
|
+
baseURL: 'sandbox' === this.#config.server ? 'https://dev.bloque.app/api/payments' : 'https://api.bloque.app/api/payments',
|
|
267
297
|
apiKey: this.#config.apiKey,
|
|
268
298
|
timeout: this.#config.timeout,
|
|
269
299
|
maxRetries: this.#config.maxRetries
|
|
@@ -273,6 +303,7 @@ class Bloque {
|
|
|
273
303
|
initializeResources() {
|
|
274
304
|
this.checkout = new CheckoutResource(this.#httpClient);
|
|
275
305
|
this.payments = new PaymentResource(this.#httpClient);
|
|
306
|
+
this.webhooks = new WebhookResource(this.#config.webhookSecret);
|
|
276
307
|
}
|
|
277
308
|
}
|
|
278
309
|
export { Bloque };
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export interface WebhookVerifyOptions {
|
|
2
|
+
secret: string;
|
|
3
|
+
}
|
|
4
|
+
export declare class WebhookResource {
|
|
5
|
+
#private;
|
|
6
|
+
constructor(secret?: string);
|
|
7
|
+
/**
|
|
8
|
+
* Verify the authenticity of a webhook payload using HMAC-SHA256
|
|
9
|
+
*
|
|
10
|
+
* @param body - The raw webhook body (string or object)
|
|
11
|
+
* @param signature - The signature from the webhook header
|
|
12
|
+
* @param options - Optional verification options
|
|
13
|
+
* @returns True if the webhook is valid
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```typescript
|
|
17
|
+
* const isValid = bloque.webhooks.verify(
|
|
18
|
+
* req.body,
|
|
19
|
+
* req.headers['x-bloque-signature'],
|
|
20
|
+
* { secret: process.env.WEBHOOK_SECRET }
|
|
21
|
+
* );
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
verify(body: string | Record<string, unknown>, signature: string, options?: WebhookVerifyOptions): boolean;
|
|
25
|
+
setSecret(secret: string): void;
|
|
26
|
+
}
|