@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.mjs CHANGED
@@ -293,6 +293,434 @@ 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 rawRequirements = await config.getRequirements(req);
525
+ const requirementsArray = Array.isArray(rawRequirements) ? rawRequirements : [rawRequirements];
526
+ const paymentHeader = req.headers["x-payment"];
527
+ const paymentString = Array.isArray(paymentHeader) ? paymentHeader[0] : paymentHeader;
528
+ if (!paymentString) {
529
+ if (config.on402) {
530
+ await config.on402(req, res, requirementsArray);
531
+ } else {
532
+ const accepts = requirementsArray.map((requirements2) => {
533
+ const extra = {
534
+ ...requirements2.extra
535
+ };
536
+ if (config.refundProtection) {
537
+ extra.supportsRefunds = true;
538
+ }
539
+ return {
540
+ scheme: requirements2.scheme,
541
+ network: requirements2.network,
542
+ maxAmountRequired: requirements2.maxAmountRequired,
543
+ asset: requirements2.asset,
544
+ payTo: requirements2.payTo,
545
+ resource: requirements2.resource || req.url,
546
+ description: requirements2.description,
547
+ ...Object.keys(extra).length > 0 ? { extra } : {}
548
+ };
549
+ });
550
+ res.status(402).json({
551
+ error: "Payment Required",
552
+ accepts
553
+ });
554
+ }
555
+ return;
556
+ }
557
+ let paymentPayload;
558
+ try {
559
+ paymentPayload = JSON.parse(paymentString);
560
+ if (!isPaymentPayload(paymentPayload)) {
561
+ throw new Error("Invalid payment payload structure");
562
+ }
563
+ } catch {
564
+ res.status(400).json({ error: "Invalid X-PAYMENT header" });
565
+ return;
566
+ }
567
+ const paymentNetwork = paymentPayload.network;
568
+ const requirements = requirementsArray.find((r) => r.network === paymentNetwork) || requirementsArray[0];
569
+ const verifyResult = await facilitator.verify(paymentPayload, requirements);
570
+ if (!verifyResult.isValid) {
571
+ res.status(402).json({
572
+ error: "Payment verification failed",
573
+ reason: verifyResult.invalidReason
574
+ });
575
+ return;
576
+ }
577
+ const settleResult = await facilitator.settle(paymentPayload, requirements);
578
+ if (!settleResult.success) {
579
+ res.status(402).json({
580
+ error: "Payment settlement failed",
581
+ reason: settleResult.errorReason
582
+ });
583
+ return;
584
+ }
585
+ const paymentContext = createPaymentContext(settleResult, paymentPayload);
586
+ req.paymentContext = paymentContext;
587
+ if (res.locals) {
588
+ res.locals.paymentContext = paymentContext;
589
+ }
590
+ if (config.refundProtection) {
591
+ const originalNext = next;
592
+ const refundConfig = config.refundProtection;
593
+ next = async (error) => {
594
+ if (error) {
595
+ const err = error instanceof Error ? error : new Error(String(error));
596
+ if (!refundConfig.shouldReport || refundConfig.shouldReport(err)) {
597
+ try {
598
+ const result = await reportFailure({
599
+ facilitatorUrl: refundConfig.facilitatorUrl,
600
+ apiKey: refundConfig.apiKey,
601
+ originalTxHash: paymentContext.transactionHash,
602
+ userWallet: paymentContext.userWallet,
603
+ amount: paymentContext.amount,
604
+ asset: paymentContext.asset,
605
+ network: paymentContext.network,
606
+ reason: err.message
607
+ });
608
+ if (refundConfig.onReport) {
609
+ refundConfig.onReport(result.claimId, err);
610
+ }
611
+ } catch (reportError) {
612
+ if (refundConfig.onReportError) {
613
+ refundConfig.onReportError(
614
+ reportError instanceof Error ? reportError : new Error(String(reportError)),
615
+ err
616
+ );
617
+ }
618
+ }
619
+ }
620
+ }
621
+ originalNext(error);
622
+ };
623
+ }
624
+ next();
625
+ } catch (error) {
626
+ next(error);
627
+ }
628
+ };
629
+ }
630
+ function honoPaymentMiddleware(config) {
631
+ const facilitator = typeof config.facilitator === "string" ? new OpenFacilitator({ url: config.facilitator }) : config.facilitator;
632
+ return async (c, next) => {
633
+ const rawRequirements = await config.getRequirements(c);
634
+ const requirementsArray = Array.isArray(rawRequirements) ? rawRequirements : [rawRequirements];
635
+ const paymentString = c.req.header("x-payment");
636
+ if (!paymentString) {
637
+ const accepts = requirementsArray.map((requirements2) => {
638
+ const extra = {
639
+ ...requirements2.extra
640
+ };
641
+ if (config.refundProtection) {
642
+ extra.supportsRefunds = true;
643
+ }
644
+ return {
645
+ scheme: requirements2.scheme,
646
+ network: requirements2.network,
647
+ maxAmountRequired: requirements2.maxAmountRequired,
648
+ asset: requirements2.asset,
649
+ payTo: requirements2.payTo,
650
+ resource: requirements2.resource || c.req.url,
651
+ description: requirements2.description,
652
+ ...Object.keys(extra).length > 0 ? { extra } : {}
653
+ };
654
+ });
655
+ return c.json({
656
+ error: "Payment Required",
657
+ accepts
658
+ }, 402);
659
+ }
660
+ let paymentPayload;
661
+ try {
662
+ paymentPayload = JSON.parse(paymentString);
663
+ if (!isPaymentPayload(paymentPayload)) {
664
+ throw new Error("Invalid payment payload structure");
665
+ }
666
+ } catch {
667
+ return c.json({ error: "Invalid X-PAYMENT header" }, 400);
668
+ }
669
+ const paymentNetwork = paymentPayload.network;
670
+ const requirements = requirementsArray.find((r) => r.network === paymentNetwork) || requirementsArray[0];
671
+ const verifyResult = await facilitator.verify(paymentPayload, requirements);
672
+ if (!verifyResult.isValid) {
673
+ return c.json({
674
+ error: "Payment verification failed",
675
+ reason: verifyResult.invalidReason
676
+ }, 402);
677
+ }
678
+ const settleResult = await facilitator.settle(paymentPayload, requirements);
679
+ if (!settleResult.success) {
680
+ return c.json({
681
+ error: "Payment settlement failed",
682
+ reason: settleResult.errorReason
683
+ }, 402);
684
+ }
685
+ const paymentContext = createPaymentContext(settleResult, paymentPayload);
686
+ c.set("paymentContext", paymentContext);
687
+ if (config.refundProtection) {
688
+ const refundConfig = config.refundProtection;
689
+ try {
690
+ await next();
691
+ } catch (error) {
692
+ const err = error instanceof Error ? error : new Error(String(error));
693
+ if (!refundConfig.shouldReport || refundConfig.shouldReport(err)) {
694
+ try {
695
+ const result = await reportFailure({
696
+ facilitatorUrl: refundConfig.facilitatorUrl,
697
+ apiKey: refundConfig.apiKey,
698
+ originalTxHash: paymentContext.transactionHash,
699
+ userWallet: paymentContext.userWallet,
700
+ amount: paymentContext.amount,
701
+ asset: paymentContext.asset,
702
+ network: paymentContext.network,
703
+ reason: err.message
704
+ });
705
+ if (refundConfig.onReport) {
706
+ refundConfig.onReport(result.claimId, err);
707
+ }
708
+ } catch (reportError) {
709
+ if (refundConfig.onReportError) {
710
+ refundConfig.onReportError(
711
+ reportError instanceof Error ? reportError : new Error(String(reportError)),
712
+ err
713
+ );
714
+ }
715
+ }
716
+ }
717
+ throw error;
718
+ }
719
+ } else {
720
+ await next();
721
+ }
722
+ };
723
+ }
296
724
  export {
297
725
  ConfigurationError,
298
726
  FacilitatorError,
@@ -302,12 +730,22 @@ export {
302
730
  SettlementError,
303
731
  VerificationError,
304
732
  createDefaultFacilitator,
733
+ createPaymentContext,
734
+ createPaymentMiddleware,
735
+ createRefundMiddleware,
736
+ executeClaim,
737
+ getClaimHistory,
738
+ getClaimable,
305
739
  getMainnets,
306
740
  getNetwork,
307
741
  getNetworkType,
308
742
  getTestnets,
743
+ honoPaymentMiddleware,
744
+ honoRefundMiddleware,
309
745
  isPaymentPayload,
310
746
  isValidNetwork,
747
+ reportFailure,
311
748
  toV1NetworkId,
312
- toV2NetworkId
749
+ toV2NetworkId,
750
+ withRefundProtection
313
751
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openfacilitator/sdk",
3
- "version": "0.3.0",
3
+ "version": "0.6.0",
4
4
  "description": "TypeScript SDK for x402 payment facilitation",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",