@openfacilitator/sdk 0.3.0 → 0.6.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.js CHANGED
@@ -28,14 +28,24 @@ __export(index_exports, {
28
28
  SettlementError: () => SettlementError,
29
29
  VerificationError: () => VerificationError,
30
30
  createDefaultFacilitator: () => createDefaultFacilitator,
31
+ createPaymentContext: () => createPaymentContext,
32
+ createPaymentMiddleware: () => createPaymentMiddleware,
33
+ createRefundMiddleware: () => createRefundMiddleware,
34
+ executeClaim: () => executeClaim,
35
+ getClaimHistory: () => getClaimHistory,
36
+ getClaimable: () => getClaimable,
31
37
  getMainnets: () => getMainnets,
32
38
  getNetwork: () => getNetwork,
33
39
  getNetworkType: () => getNetworkType,
34
40
  getTestnets: () => getTestnets,
41
+ honoPaymentMiddleware: () => honoPaymentMiddleware,
42
+ honoRefundMiddleware: () => honoRefundMiddleware,
35
43
  isPaymentPayload: () => isPaymentPayload,
36
44
  isValidNetwork: () => isValidNetwork,
45
+ reportFailure: () => reportFailure,
37
46
  toV1NetworkId: () => toV1NetworkId,
38
- toV2NetworkId: () => toV2NetworkId
47
+ toV2NetworkId: () => toV2NetworkId,
48
+ withRefundProtection: () => withRefundProtection
39
49
  });
40
50
  module.exports = __toCommonJS(index_exports);
41
51
 
@@ -334,6 +344,434 @@ function getMainnets() {
334
344
  function getTestnets() {
335
345
  return NETWORKS.filter((n) => n.testnet);
336
346
  }
347
+
348
+ // src/claims.ts
349
+ async function reportFailure(params) {
350
+ const {
351
+ facilitatorUrl,
352
+ apiKey,
353
+ originalTxHash,
354
+ userWallet,
355
+ amount,
356
+ asset,
357
+ network,
358
+ reason
359
+ } = params;
360
+ const baseUrl = facilitatorUrl.replace(/\/$/, "");
361
+ try {
362
+ const response = await fetch(`${baseUrl}/claims/report-failure`, {
363
+ method: "POST",
364
+ headers: {
365
+ "Content-Type": "application/json",
366
+ "X-Server-Api-Key": apiKey
367
+ },
368
+ body: JSON.stringify({
369
+ originalTxHash,
370
+ userWallet,
371
+ amount,
372
+ asset,
373
+ network,
374
+ reason
375
+ })
376
+ });
377
+ const data = await response.json();
378
+ if (!response.ok) {
379
+ return {
380
+ success: false,
381
+ error: data.error || `HTTP ${response.status}`
382
+ };
383
+ }
384
+ return data;
385
+ } catch (error) {
386
+ return {
387
+ success: false,
388
+ error: error instanceof Error ? error.message : "Unknown error"
389
+ };
390
+ }
391
+ }
392
+ async function getClaimable(params) {
393
+ const { facilitatorUrl, wallet, facilitator } = params;
394
+ const baseUrl = facilitatorUrl.replace(/\/$/, "");
395
+ const queryParams = new URLSearchParams({ wallet });
396
+ if (facilitator) {
397
+ queryParams.set("facilitator", facilitator);
398
+ }
399
+ const response = await fetch(`${baseUrl}/api/claims?${queryParams.toString()}`);
400
+ const data = await response.json();
401
+ if (!response.ok) {
402
+ throw new Error(data.error || `HTTP ${response.status}`);
403
+ }
404
+ return data;
405
+ }
406
+ async function getClaimHistory(params) {
407
+ const { facilitatorUrl, wallet, facilitator } = params;
408
+ const baseUrl = facilitatorUrl.replace(/\/$/, "");
409
+ const queryParams = new URLSearchParams({ wallet });
410
+ if (facilitator) {
411
+ queryParams.set("facilitator", facilitator);
412
+ }
413
+ const response = await fetch(`${baseUrl}/api/claims/history?${queryParams.toString()}`);
414
+ const data = await response.json();
415
+ if (!response.ok) {
416
+ throw new Error(data.error || `HTTP ${response.status}`);
417
+ }
418
+ return data;
419
+ }
420
+ async function executeClaim(params) {
421
+ const { facilitatorUrl, claimId, signature } = params;
422
+ const baseUrl = facilitatorUrl.replace(/\/$/, "");
423
+ try {
424
+ const response = await fetch(`${baseUrl}/api/claims/${claimId}/execute`, {
425
+ method: "POST",
426
+ headers: {
427
+ "Content-Type": "application/json"
428
+ },
429
+ body: JSON.stringify({ signature })
430
+ });
431
+ const data = await response.json();
432
+ if (!response.ok) {
433
+ return {
434
+ success: false,
435
+ error: data.error || `HTTP ${response.status}`
436
+ };
437
+ }
438
+ return data;
439
+ } catch (error) {
440
+ return {
441
+ success: false,
442
+ error: error instanceof Error ? error.message : "Unknown error"
443
+ };
444
+ }
445
+ }
446
+
447
+ // src/middleware.ts
448
+ function withRefundProtection(config, handler) {
449
+ return async (context) => {
450
+ try {
451
+ return await handler(context);
452
+ } catch (error) {
453
+ const err = error instanceof Error ? error : new Error(String(error));
454
+ if (config.shouldReport && !config.shouldReport(err)) {
455
+ throw error;
456
+ }
457
+ try {
458
+ const result = await reportFailure({
459
+ facilitatorUrl: config.facilitatorUrl,
460
+ apiKey: config.apiKey,
461
+ originalTxHash: context.transactionHash,
462
+ userWallet: context.userWallet,
463
+ amount: context.amount,
464
+ asset: context.asset,
465
+ network: context.network,
466
+ reason: err.message
467
+ });
468
+ if (config.onReport) {
469
+ config.onReport(result.claimId, err);
470
+ }
471
+ } catch (reportError) {
472
+ if (config.onReportError) {
473
+ config.onReportError(
474
+ reportError instanceof Error ? reportError : new Error(String(reportError)),
475
+ err
476
+ );
477
+ }
478
+ }
479
+ throw error;
480
+ }
481
+ };
482
+ }
483
+ function createRefundMiddleware(config) {
484
+ return async (req, res, next) => {
485
+ const originalNext = next;
486
+ const paymentContext = res.locals?.paymentContext || req.paymentContext;
487
+ if (!paymentContext) {
488
+ return originalNext();
489
+ }
490
+ const wrappedNext = async (error) => {
491
+ if (error) {
492
+ const err = error instanceof Error ? error : new Error(String(error));
493
+ if (!config.shouldReport || config.shouldReport(err)) {
494
+ try {
495
+ const result = await reportFailure({
496
+ facilitatorUrl: config.facilitatorUrl,
497
+ apiKey: config.apiKey,
498
+ originalTxHash: paymentContext.transactionHash,
499
+ userWallet: paymentContext.userWallet,
500
+ amount: paymentContext.amount,
501
+ asset: paymentContext.asset,
502
+ network: paymentContext.network,
503
+ reason: err.message
504
+ });
505
+ if (config.onReport) {
506
+ config.onReport(result.claimId, err);
507
+ }
508
+ } catch (reportError) {
509
+ if (config.onReportError) {
510
+ config.onReportError(
511
+ reportError instanceof Error ? reportError : new Error(String(reportError)),
512
+ err
513
+ );
514
+ }
515
+ }
516
+ }
517
+ }
518
+ originalNext(error);
519
+ };
520
+ next = wrappedNext;
521
+ originalNext();
522
+ };
523
+ }
524
+ function honoRefundMiddleware(config) {
525
+ return async (c, next) => {
526
+ const paymentContext = config.getPaymentContext(c);
527
+ if (!paymentContext) {
528
+ return next();
529
+ }
530
+ try {
531
+ await next();
532
+ } catch (error) {
533
+ const err = error instanceof Error ? error : new Error(String(error));
534
+ if (!config.shouldReport || config.shouldReport(err)) {
535
+ try {
536
+ const result = await reportFailure({
537
+ facilitatorUrl: config.facilitatorUrl,
538
+ apiKey: config.apiKey,
539
+ originalTxHash: paymentContext.transactionHash,
540
+ userWallet: paymentContext.userWallet,
541
+ amount: paymentContext.amount,
542
+ asset: paymentContext.asset,
543
+ network: paymentContext.network,
544
+ reason: err.message
545
+ });
546
+ if (config.onReport) {
547
+ config.onReport(result.claimId, err);
548
+ }
549
+ } catch (reportError) {
550
+ if (config.onReportError) {
551
+ config.onReportError(
552
+ reportError instanceof Error ? reportError : new Error(String(reportError)),
553
+ err
554
+ );
555
+ }
556
+ }
557
+ }
558
+ throw error;
559
+ }
560
+ };
561
+ }
562
+ function createPaymentContext(settleResponse, paymentPayload) {
563
+ return {
564
+ transactionHash: settleResponse.transaction,
565
+ userWallet: settleResponse.payer,
566
+ amount: paymentPayload.payload.authorization.amount,
567
+ asset: paymentPayload.payload.authorization.asset,
568
+ network: settleResponse.network
569
+ };
570
+ }
571
+ function createPaymentMiddleware(config) {
572
+ const facilitator = typeof config.facilitator === "string" ? new OpenFacilitator({ url: config.facilitator }) : config.facilitator;
573
+ return async (req, res, next) => {
574
+ try {
575
+ const rawRequirements = await config.getRequirements(req);
576
+ const requirementsArray = Array.isArray(rawRequirements) ? rawRequirements : [rawRequirements];
577
+ const paymentHeader = req.headers["x-payment"];
578
+ const paymentString = Array.isArray(paymentHeader) ? paymentHeader[0] : paymentHeader;
579
+ if (!paymentString) {
580
+ if (config.on402) {
581
+ await config.on402(req, res, requirementsArray);
582
+ } else {
583
+ const accepts = requirementsArray.map((requirements2) => {
584
+ const extra = {
585
+ ...requirements2.extra
586
+ };
587
+ if (config.refundProtection) {
588
+ extra.supportsRefunds = true;
589
+ }
590
+ return {
591
+ scheme: requirements2.scheme,
592
+ network: requirements2.network,
593
+ maxAmountRequired: requirements2.maxAmountRequired,
594
+ asset: requirements2.asset,
595
+ payTo: requirements2.payTo,
596
+ resource: requirements2.resource || req.url,
597
+ description: requirements2.description,
598
+ ...Object.keys(extra).length > 0 ? { extra } : {}
599
+ };
600
+ });
601
+ res.status(402).json({
602
+ error: "Payment Required",
603
+ accepts
604
+ });
605
+ }
606
+ return;
607
+ }
608
+ let paymentPayload;
609
+ try {
610
+ paymentPayload = JSON.parse(paymentString);
611
+ if (!isPaymentPayload(paymentPayload)) {
612
+ throw new Error("Invalid payment payload structure");
613
+ }
614
+ } catch {
615
+ res.status(400).json({ error: "Invalid X-PAYMENT header" });
616
+ return;
617
+ }
618
+ const paymentNetwork = paymentPayload.network;
619
+ const requirements = requirementsArray.find((r) => r.network === paymentNetwork) || requirementsArray[0];
620
+ const verifyResult = await facilitator.verify(paymentPayload, requirements);
621
+ if (!verifyResult.isValid) {
622
+ res.status(402).json({
623
+ error: "Payment verification failed",
624
+ reason: verifyResult.invalidReason
625
+ });
626
+ return;
627
+ }
628
+ const settleResult = await facilitator.settle(paymentPayload, requirements);
629
+ if (!settleResult.success) {
630
+ res.status(402).json({
631
+ error: "Payment settlement failed",
632
+ reason: settleResult.errorReason
633
+ });
634
+ return;
635
+ }
636
+ const paymentContext = createPaymentContext(settleResult, paymentPayload);
637
+ req.paymentContext = paymentContext;
638
+ if (res.locals) {
639
+ res.locals.paymentContext = paymentContext;
640
+ }
641
+ if (config.refundProtection) {
642
+ const originalNext = next;
643
+ const refundConfig = config.refundProtection;
644
+ next = async (error) => {
645
+ if (error) {
646
+ const err = error instanceof Error ? error : new Error(String(error));
647
+ if (!refundConfig.shouldReport || refundConfig.shouldReport(err)) {
648
+ try {
649
+ const result = await reportFailure({
650
+ facilitatorUrl: refundConfig.facilitatorUrl,
651
+ apiKey: refundConfig.apiKey,
652
+ originalTxHash: paymentContext.transactionHash,
653
+ userWallet: paymentContext.userWallet,
654
+ amount: paymentContext.amount,
655
+ asset: paymentContext.asset,
656
+ network: paymentContext.network,
657
+ reason: err.message
658
+ });
659
+ if (refundConfig.onReport) {
660
+ refundConfig.onReport(result.claimId, err);
661
+ }
662
+ } catch (reportError) {
663
+ if (refundConfig.onReportError) {
664
+ refundConfig.onReportError(
665
+ reportError instanceof Error ? reportError : new Error(String(reportError)),
666
+ err
667
+ );
668
+ }
669
+ }
670
+ }
671
+ }
672
+ originalNext(error);
673
+ };
674
+ }
675
+ next();
676
+ } catch (error) {
677
+ next(error);
678
+ }
679
+ };
680
+ }
681
+ function honoPaymentMiddleware(config) {
682
+ const facilitator = typeof config.facilitator === "string" ? new OpenFacilitator({ url: config.facilitator }) : config.facilitator;
683
+ return async (c, next) => {
684
+ const rawRequirements = await config.getRequirements(c);
685
+ const requirementsArray = Array.isArray(rawRequirements) ? rawRequirements : [rawRequirements];
686
+ const paymentString = c.req.header("x-payment");
687
+ if (!paymentString) {
688
+ const accepts = requirementsArray.map((requirements2) => {
689
+ const extra = {
690
+ ...requirements2.extra
691
+ };
692
+ if (config.refundProtection) {
693
+ extra.supportsRefunds = true;
694
+ }
695
+ return {
696
+ scheme: requirements2.scheme,
697
+ network: requirements2.network,
698
+ maxAmountRequired: requirements2.maxAmountRequired,
699
+ asset: requirements2.asset,
700
+ payTo: requirements2.payTo,
701
+ resource: requirements2.resource || c.req.url,
702
+ description: requirements2.description,
703
+ ...Object.keys(extra).length > 0 ? { extra } : {}
704
+ };
705
+ });
706
+ return c.json({
707
+ error: "Payment Required",
708
+ accepts
709
+ }, 402);
710
+ }
711
+ let paymentPayload;
712
+ try {
713
+ paymentPayload = JSON.parse(paymentString);
714
+ if (!isPaymentPayload(paymentPayload)) {
715
+ throw new Error("Invalid payment payload structure");
716
+ }
717
+ } catch {
718
+ return c.json({ error: "Invalid X-PAYMENT header" }, 400);
719
+ }
720
+ const paymentNetwork = paymentPayload.network;
721
+ const requirements = requirementsArray.find((r) => r.network === paymentNetwork) || requirementsArray[0];
722
+ const verifyResult = await facilitator.verify(paymentPayload, requirements);
723
+ if (!verifyResult.isValid) {
724
+ return c.json({
725
+ error: "Payment verification failed",
726
+ reason: verifyResult.invalidReason
727
+ }, 402);
728
+ }
729
+ const settleResult = await facilitator.settle(paymentPayload, requirements);
730
+ if (!settleResult.success) {
731
+ return c.json({
732
+ error: "Payment settlement failed",
733
+ reason: settleResult.errorReason
734
+ }, 402);
735
+ }
736
+ const paymentContext = createPaymentContext(settleResult, paymentPayload);
737
+ c.set("paymentContext", paymentContext);
738
+ if (config.refundProtection) {
739
+ const refundConfig = config.refundProtection;
740
+ try {
741
+ await next();
742
+ } catch (error) {
743
+ const err = error instanceof Error ? error : new Error(String(error));
744
+ if (!refundConfig.shouldReport || refundConfig.shouldReport(err)) {
745
+ try {
746
+ const result = await reportFailure({
747
+ facilitatorUrl: refundConfig.facilitatorUrl,
748
+ apiKey: refundConfig.apiKey,
749
+ originalTxHash: paymentContext.transactionHash,
750
+ userWallet: paymentContext.userWallet,
751
+ amount: paymentContext.amount,
752
+ asset: paymentContext.asset,
753
+ network: paymentContext.network,
754
+ reason: err.message
755
+ });
756
+ if (refundConfig.onReport) {
757
+ refundConfig.onReport(result.claimId, err);
758
+ }
759
+ } catch (reportError) {
760
+ if (refundConfig.onReportError) {
761
+ refundConfig.onReportError(
762
+ reportError instanceof Error ? reportError : new Error(String(reportError)),
763
+ err
764
+ );
765
+ }
766
+ }
767
+ }
768
+ throw error;
769
+ }
770
+ } else {
771
+ await next();
772
+ }
773
+ };
774
+ }
337
775
  // Annotate the CommonJS export names for ESM import in node:
338
776
  0 && (module.exports = {
339
777
  ConfigurationError,
@@ -344,12 +782,22 @@ function getTestnets() {
344
782
  SettlementError,
345
783
  VerificationError,
346
784
  createDefaultFacilitator,
785
+ createPaymentContext,
786
+ createPaymentMiddleware,
787
+ createRefundMiddleware,
788
+ executeClaim,
789
+ getClaimHistory,
790
+ getClaimable,
347
791
  getMainnets,
348
792
  getNetwork,
349
793
  getNetworkType,
350
794
  getTestnets,
795
+ honoPaymentMiddleware,
796
+ honoRefundMiddleware,
351
797
  isPaymentPayload,
352
798
  isValidNetwork,
799
+ reportFailure,
353
800
  toV1NetworkId,
354
- toV2NetworkId
801
+ toV2NetworkId,
802
+ withRefundProtection
355
803
  });