@openfacilitator/sdk 0.2.0 → 0.5.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/index.mjs CHANGED
@@ -293,6 +293,422 @@ function getMainnets() {
293
293
  function getTestnets() {
294
294
  return NETWORKS.filter((n) => n.testnet);
295
295
  }
296
+
297
+ // src/claims.ts
298
+ async function reportFailure(params) {
299
+ const {
300
+ facilitatorUrl,
301
+ apiKey,
302
+ originalTxHash,
303
+ userWallet,
304
+ amount,
305
+ asset,
306
+ network,
307
+ reason
308
+ } = params;
309
+ const baseUrl = facilitatorUrl.replace(/\/$/, "");
310
+ try {
311
+ const response = await fetch(`${baseUrl}/claims/report-failure`, {
312
+ method: "POST",
313
+ headers: {
314
+ "Content-Type": "application/json",
315
+ "X-Server-Api-Key": apiKey
316
+ },
317
+ body: JSON.stringify({
318
+ originalTxHash,
319
+ userWallet,
320
+ amount,
321
+ asset,
322
+ network,
323
+ reason
324
+ })
325
+ });
326
+ const data = await response.json();
327
+ if (!response.ok) {
328
+ return {
329
+ success: false,
330
+ error: data.error || `HTTP ${response.status}`
331
+ };
332
+ }
333
+ return data;
334
+ } catch (error) {
335
+ return {
336
+ success: false,
337
+ error: error instanceof Error ? error.message : "Unknown error"
338
+ };
339
+ }
340
+ }
341
+ async function getClaimable(params) {
342
+ const { facilitatorUrl, wallet, facilitator } = params;
343
+ const baseUrl = facilitatorUrl.replace(/\/$/, "");
344
+ const queryParams = new URLSearchParams({ wallet });
345
+ if (facilitator) {
346
+ queryParams.set("facilitator", facilitator);
347
+ }
348
+ const response = await fetch(`${baseUrl}/api/claims?${queryParams.toString()}`);
349
+ const data = await response.json();
350
+ if (!response.ok) {
351
+ throw new Error(data.error || `HTTP ${response.status}`);
352
+ }
353
+ return data;
354
+ }
355
+ async function getClaimHistory(params) {
356
+ const { facilitatorUrl, wallet, facilitator } = params;
357
+ const baseUrl = facilitatorUrl.replace(/\/$/, "");
358
+ const queryParams = new URLSearchParams({ wallet });
359
+ if (facilitator) {
360
+ queryParams.set("facilitator", facilitator);
361
+ }
362
+ const response = await fetch(`${baseUrl}/api/claims/history?${queryParams.toString()}`);
363
+ const data = await response.json();
364
+ if (!response.ok) {
365
+ throw new Error(data.error || `HTTP ${response.status}`);
366
+ }
367
+ return data;
368
+ }
369
+ async function executeClaim(params) {
370
+ const { facilitatorUrl, claimId, signature } = params;
371
+ const baseUrl = facilitatorUrl.replace(/\/$/, "");
372
+ try {
373
+ const response = await fetch(`${baseUrl}/api/claims/${claimId}/execute`, {
374
+ method: "POST",
375
+ headers: {
376
+ "Content-Type": "application/json"
377
+ },
378
+ body: JSON.stringify({ signature })
379
+ });
380
+ const data = await response.json();
381
+ if (!response.ok) {
382
+ return {
383
+ success: false,
384
+ error: data.error || `HTTP ${response.status}`
385
+ };
386
+ }
387
+ return data;
388
+ } catch (error) {
389
+ return {
390
+ success: false,
391
+ error: error instanceof Error ? error.message : "Unknown error"
392
+ };
393
+ }
394
+ }
395
+
396
+ // src/middleware.ts
397
+ function withRefundProtection(config, handler) {
398
+ return async (context) => {
399
+ try {
400
+ return await handler(context);
401
+ } catch (error) {
402
+ const err = error instanceof Error ? error : new Error(String(error));
403
+ if (config.shouldReport && !config.shouldReport(err)) {
404
+ throw error;
405
+ }
406
+ try {
407
+ const result = await reportFailure({
408
+ facilitatorUrl: config.facilitatorUrl,
409
+ apiKey: config.apiKey,
410
+ originalTxHash: context.transactionHash,
411
+ userWallet: context.userWallet,
412
+ amount: context.amount,
413
+ asset: context.asset,
414
+ network: context.network,
415
+ reason: err.message
416
+ });
417
+ if (config.onReport) {
418
+ config.onReport(result.claimId, err);
419
+ }
420
+ } catch (reportError) {
421
+ if (config.onReportError) {
422
+ config.onReportError(
423
+ reportError instanceof Error ? reportError : new Error(String(reportError)),
424
+ err
425
+ );
426
+ }
427
+ }
428
+ throw error;
429
+ }
430
+ };
431
+ }
432
+ function createRefundMiddleware(config) {
433
+ return async (req, res, next) => {
434
+ const originalNext = next;
435
+ const paymentContext = res.locals?.paymentContext || req.paymentContext;
436
+ if (!paymentContext) {
437
+ return originalNext();
438
+ }
439
+ const wrappedNext = async (error) => {
440
+ if (error) {
441
+ const err = error instanceof Error ? error : new Error(String(error));
442
+ if (!config.shouldReport || config.shouldReport(err)) {
443
+ try {
444
+ const result = await reportFailure({
445
+ facilitatorUrl: config.facilitatorUrl,
446
+ apiKey: config.apiKey,
447
+ originalTxHash: paymentContext.transactionHash,
448
+ userWallet: paymentContext.userWallet,
449
+ amount: paymentContext.amount,
450
+ asset: paymentContext.asset,
451
+ network: paymentContext.network,
452
+ reason: err.message
453
+ });
454
+ if (config.onReport) {
455
+ config.onReport(result.claimId, err);
456
+ }
457
+ } catch (reportError) {
458
+ if (config.onReportError) {
459
+ config.onReportError(
460
+ reportError instanceof Error ? reportError : new Error(String(reportError)),
461
+ err
462
+ );
463
+ }
464
+ }
465
+ }
466
+ }
467
+ originalNext(error);
468
+ };
469
+ next = wrappedNext;
470
+ originalNext();
471
+ };
472
+ }
473
+ function honoRefundMiddleware(config) {
474
+ return async (c, next) => {
475
+ const paymentContext = config.getPaymentContext(c);
476
+ if (!paymentContext) {
477
+ return next();
478
+ }
479
+ try {
480
+ await next();
481
+ } catch (error) {
482
+ const err = error instanceof Error ? error : new Error(String(error));
483
+ if (!config.shouldReport || config.shouldReport(err)) {
484
+ try {
485
+ const result = await reportFailure({
486
+ facilitatorUrl: config.facilitatorUrl,
487
+ apiKey: config.apiKey,
488
+ originalTxHash: paymentContext.transactionHash,
489
+ userWallet: paymentContext.userWallet,
490
+ amount: paymentContext.amount,
491
+ asset: paymentContext.asset,
492
+ network: paymentContext.network,
493
+ reason: err.message
494
+ });
495
+ if (config.onReport) {
496
+ config.onReport(result.claimId, err);
497
+ }
498
+ } catch (reportError) {
499
+ if (config.onReportError) {
500
+ config.onReportError(
501
+ reportError instanceof Error ? reportError : new Error(String(reportError)),
502
+ err
503
+ );
504
+ }
505
+ }
506
+ }
507
+ throw error;
508
+ }
509
+ };
510
+ }
511
+ function createPaymentContext(settleResponse, paymentPayload) {
512
+ return {
513
+ transactionHash: settleResponse.transaction,
514
+ userWallet: settleResponse.payer,
515
+ amount: paymentPayload.payload.authorization.amount,
516
+ asset: paymentPayload.payload.authorization.asset,
517
+ network: settleResponse.network
518
+ };
519
+ }
520
+ function createPaymentMiddleware(config) {
521
+ const facilitator = typeof config.facilitator === "string" ? new OpenFacilitator({ url: config.facilitator }) : config.facilitator;
522
+ return async (req, res, next) => {
523
+ try {
524
+ const requirements = await config.getRequirements(req);
525
+ const paymentHeader = req.headers["x-payment"];
526
+ const paymentString = Array.isArray(paymentHeader) ? paymentHeader[0] : paymentHeader;
527
+ if (!paymentString) {
528
+ if (config.on402) {
529
+ await config.on402(req, res, requirements);
530
+ } else {
531
+ const extra = {
532
+ ...requirements.extra
533
+ };
534
+ if (config.refundProtection) {
535
+ extra.supportsRefunds = true;
536
+ }
537
+ res.status(402).json({
538
+ error: "Payment Required",
539
+ accepts: [{
540
+ scheme: requirements.scheme,
541
+ network: requirements.network,
542
+ maxAmountRequired: requirements.maxAmountRequired,
543
+ asset: requirements.asset,
544
+ payTo: requirements.payTo,
545
+ resource: requirements.resource || req.url,
546
+ description: requirements.description,
547
+ ...Object.keys(extra).length > 0 ? { extra } : {}
548
+ }]
549
+ });
550
+ }
551
+ return;
552
+ }
553
+ let paymentPayload;
554
+ try {
555
+ paymentPayload = JSON.parse(paymentString);
556
+ if (!isPaymentPayload(paymentPayload)) {
557
+ throw new Error("Invalid payment payload structure");
558
+ }
559
+ } catch {
560
+ res.status(400).json({ error: "Invalid X-PAYMENT header" });
561
+ return;
562
+ }
563
+ const verifyResult = await facilitator.verify(paymentPayload, requirements);
564
+ if (!verifyResult.isValid) {
565
+ res.status(402).json({
566
+ error: "Payment verification failed",
567
+ reason: verifyResult.invalidReason
568
+ });
569
+ return;
570
+ }
571
+ const settleResult = await facilitator.settle(paymentPayload, requirements);
572
+ if (!settleResult.success) {
573
+ res.status(402).json({
574
+ error: "Payment settlement failed",
575
+ reason: settleResult.errorReason
576
+ });
577
+ return;
578
+ }
579
+ const paymentContext = createPaymentContext(settleResult, paymentPayload);
580
+ req.paymentContext = paymentContext;
581
+ if (res.locals) {
582
+ res.locals.paymentContext = paymentContext;
583
+ }
584
+ if (config.refundProtection) {
585
+ const originalNext = next;
586
+ const refundConfig = config.refundProtection;
587
+ next = async (error) => {
588
+ if (error) {
589
+ const err = error instanceof Error ? error : new Error(String(error));
590
+ if (!refundConfig.shouldReport || refundConfig.shouldReport(err)) {
591
+ try {
592
+ const result = await reportFailure({
593
+ facilitatorUrl: refundConfig.facilitatorUrl,
594
+ apiKey: refundConfig.apiKey,
595
+ originalTxHash: paymentContext.transactionHash,
596
+ userWallet: paymentContext.userWallet,
597
+ amount: paymentContext.amount,
598
+ asset: paymentContext.asset,
599
+ network: paymentContext.network,
600
+ reason: err.message
601
+ });
602
+ if (refundConfig.onReport) {
603
+ refundConfig.onReport(result.claimId, err);
604
+ }
605
+ } catch (reportError) {
606
+ if (refundConfig.onReportError) {
607
+ refundConfig.onReportError(
608
+ reportError instanceof Error ? reportError : new Error(String(reportError)),
609
+ err
610
+ );
611
+ }
612
+ }
613
+ }
614
+ }
615
+ originalNext(error);
616
+ };
617
+ }
618
+ next();
619
+ } catch (error) {
620
+ next(error);
621
+ }
622
+ };
623
+ }
624
+ function honoPaymentMiddleware(config) {
625
+ const facilitator = typeof config.facilitator === "string" ? new OpenFacilitator({ url: config.facilitator }) : config.facilitator;
626
+ return async (c, next) => {
627
+ const requirements = await config.getRequirements(c);
628
+ const paymentString = c.req.header("x-payment");
629
+ if (!paymentString) {
630
+ const extra = {
631
+ ...requirements.extra
632
+ };
633
+ if (config.refundProtection) {
634
+ extra.supportsRefunds = true;
635
+ }
636
+ return c.json({
637
+ error: "Payment Required",
638
+ accepts: [{
639
+ scheme: requirements.scheme,
640
+ network: requirements.network,
641
+ maxAmountRequired: requirements.maxAmountRequired,
642
+ asset: requirements.asset,
643
+ payTo: requirements.payTo,
644
+ resource: requirements.resource || c.req.url,
645
+ description: requirements.description,
646
+ ...Object.keys(extra).length > 0 ? { extra } : {}
647
+ }]
648
+ }, 402);
649
+ }
650
+ let paymentPayload;
651
+ try {
652
+ paymentPayload = JSON.parse(paymentString);
653
+ if (!isPaymentPayload(paymentPayload)) {
654
+ throw new Error("Invalid payment payload structure");
655
+ }
656
+ } catch {
657
+ return c.json({ error: "Invalid X-PAYMENT header" }, 400);
658
+ }
659
+ const verifyResult = await facilitator.verify(paymentPayload, requirements);
660
+ if (!verifyResult.isValid) {
661
+ return c.json({
662
+ error: "Payment verification failed",
663
+ reason: verifyResult.invalidReason
664
+ }, 402);
665
+ }
666
+ const settleResult = await facilitator.settle(paymentPayload, requirements);
667
+ if (!settleResult.success) {
668
+ return c.json({
669
+ error: "Payment settlement failed",
670
+ reason: settleResult.errorReason
671
+ }, 402);
672
+ }
673
+ const paymentContext = createPaymentContext(settleResult, paymentPayload);
674
+ c.set("paymentContext", paymentContext);
675
+ if (config.refundProtection) {
676
+ const refundConfig = config.refundProtection;
677
+ try {
678
+ await next();
679
+ } catch (error) {
680
+ const err = error instanceof Error ? error : new Error(String(error));
681
+ if (!refundConfig.shouldReport || refundConfig.shouldReport(err)) {
682
+ try {
683
+ const result = await reportFailure({
684
+ facilitatorUrl: refundConfig.facilitatorUrl,
685
+ apiKey: refundConfig.apiKey,
686
+ originalTxHash: paymentContext.transactionHash,
687
+ userWallet: paymentContext.userWallet,
688
+ amount: paymentContext.amount,
689
+ asset: paymentContext.asset,
690
+ network: paymentContext.network,
691
+ reason: err.message
692
+ });
693
+ if (refundConfig.onReport) {
694
+ refundConfig.onReport(result.claimId, err);
695
+ }
696
+ } catch (reportError) {
697
+ if (refundConfig.onReportError) {
698
+ refundConfig.onReportError(
699
+ reportError instanceof Error ? reportError : new Error(String(reportError)),
700
+ err
701
+ );
702
+ }
703
+ }
704
+ }
705
+ throw error;
706
+ }
707
+ } else {
708
+ await next();
709
+ }
710
+ };
711
+ }
296
712
  export {
297
713
  ConfigurationError,
298
714
  FacilitatorError,
@@ -302,12 +718,22 @@ export {
302
718
  SettlementError,
303
719
  VerificationError,
304
720
  createDefaultFacilitator,
721
+ createPaymentContext,
722
+ createPaymentMiddleware,
723
+ createRefundMiddleware,
724
+ executeClaim,
725
+ getClaimHistory,
726
+ getClaimable,
305
727
  getMainnets,
306
728
  getNetwork,
307
729
  getNetworkType,
308
730
  getTestnets,
731
+ honoPaymentMiddleware,
732
+ honoRefundMiddleware,
309
733
  isPaymentPayload,
310
734
  isValidNetwork,
735
+ reportFailure,
311
736
  toV1NetworkId,
312
- toV2NetworkId
737
+ toV2NetworkId,
738
+ withRefundProtection
313
739
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openfacilitator/sdk",
3
- "version": "0.2.0",
3
+ "version": "0.5.0",
4
4
  "description": "TypeScript SDK for x402 payment facilitation",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",