@agent-cards/sdk 0.21.0 → 0.23.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/CHANGELOG.md +87 -2
- package/PREFLIGHT.md +32 -0
- package/README.md +1177 -23
- package/dist/aci.d.ts +173 -0
- package/dist/aci.generated.d.ts +87 -0
- package/dist/aci.generated.js +306 -0
- package/dist/aci.js +371 -0
- package/dist/adyen-merchant-hosted.generated.d.ts +3 -3
- package/dist/adyen-merchant-hosted.generated.js +302 -56
- package/dist/agentcard.d.ts +9 -2
- package/dist/agentcard.js +4 -2
- package/dist/amadeus-checkout.generated.d.ts +57 -0
- package/dist/amadeus-checkout.generated.js +549 -0
- package/dist/amadeus.d.ts +41 -0
- package/dist/amadeus.js +81 -0
- package/dist/builtin-registry.generated.js +1 -1
- package/dist/card-endpoint-facts.d.ts +96 -0
- package/dist/card-endpoint-facts.js +275 -0
- package/dist/card-endpoint-page.d.ts +54 -0
- package/dist/card-endpoint-page.js +69 -0
- package/dist/card-endpoint.d.ts +298 -0
- package/dist/card-endpoint.js +438 -0
- package/dist/card-fields.generated.d.ts +5 -3
- package/dist/card-fields.generated.js +284 -7
- package/dist/cdp.d.ts +5 -1
- package/dist/cdp.js +1155 -141
- package/dist/client.d.ts +456 -20
- package/dist/client.js +1072 -104
- package/dist/creditguard.generated.d.ts +79 -0
- package/dist/creditguard.generated.js +662 -0
- package/dist/cse-body.js +11 -1
- package/dist/cybersource-flex.d.ts +30 -0
- package/dist/cybersource-flex.generated.d.ts +27 -1
- package/dist/cybersource-flex.generated.js +42 -2
- package/dist/cybersource-flex.js +46 -0
- package/dist/datatrans.d.ts +96 -0
- package/dist/datatrans.generated.d.ts +64 -0
- package/dist/datatrans.generated.js +426 -0
- package/dist/datatrans.js +178 -0
- package/dist/ekashu.d.ts +35 -0
- package/dist/ekashu.js +55 -0
- package/dist/elavon-converge.d.ts +37 -0
- package/dist/elavon-converge.generated.d.ts +49 -0
- package/dist/elavon-converge.generated.js +439 -0
- package/dist/elavon-converge.js +84 -0
- package/dist/eprotect.d.ts +152 -0
- package/dist/eprotect.generated.d.ts +77 -0
- package/dist/eprotect.generated.js +498 -0
- package/dist/eprotect.js +222 -0
- package/dist/hosted-form.d.ts +3 -2
- package/dist/hosted-form.js +3 -2
- package/dist/index.d.ts +17 -3
- package/dist/index.js +6 -1
- package/dist/juspay-gate.d.ts +82 -0
- package/dist/juspay-gate.js +126 -0
- package/dist/juspay.generated.d.ts +89 -0
- package/dist/juspay.generated.js +705 -0
- package/dist/lifecycle.d.ts +60 -6
- package/dist/lifecycle.js +122 -8
- package/dist/merchant-card-endpoints.generated.d.ts +122 -0
- package/dist/merchant-card-endpoints.generated.js +4261 -0
- package/dist/merchant-hosted.d.ts +4 -1
- package/dist/merchant-hosted.js +5 -2
- package/dist/merchant-session-probe.d.ts +48 -0
- package/dist/merchant-session-probe.js +53 -0
- package/dist/merchant-session.generated.d.ts +69 -0
- package/dist/merchant-session.generated.js +919 -0
- package/dist/payrails.d.ts +133 -0
- package/dist/payrails.generated.d.ts +73 -0
- package/dist/payrails.generated.js +596 -0
- package/dist/payrails.js +305 -0
- package/dist/pelecard.d.ts +32 -0
- package/dist/pelecard.generated.d.ts +32 -0
- package/dist/pelecard.generated.js +283 -0
- package/dist/pelecard.js +42 -0
- package/dist/pre-claim.d.ts +81 -2
- package/dist/pre-claim.js +1023 -5
- package/dist/preflight-catalog.json +2267 -288
- package/dist/preflight-schemas.json +183 -6
- package/dist/preflight.generated.d.ts +1 -1
- package/dist/preflight.generated.js +243 -4
- package/dist/preparation.d.ts +12 -3
- package/dist/preparation.js +63 -12
- package/dist/prepared-processor.d.ts +84 -5
- package/dist/prepared-processor.js +147 -6
- package/dist/primer.d.ts +53 -0
- package/dist/primer.generated.d.ts +38 -0
- package/dist/primer.generated.js +624 -0
- package/dist/primer.js +82 -0
- package/dist/registry.d.ts +100 -8
- package/dist/registry.js +85 -11
- package/dist/shiji-sts.d.ts +144 -0
- package/dist/shiji-sts.generated.d.ts +72 -0
- package/dist/shiji-sts.generated.js +913 -0
- package/dist/shiji-sts.js +222 -0
- package/dist/stripe-checkout.generated.d.ts +1 -1
- package/dist/stripe-checkout.generated.js +764 -9
- package/dist/stripe-setup.d.ts +16 -0
- package/dist/stripe-setup.js +61 -0
- package/dist/substitutions.generated.js +187 -29
- package/dist/verifone.d.ts +68 -0
- package/dist/verifone.generated.d.ts +51 -0
- package/dist/verifone.generated.js +1413 -0
- package/dist/verifone.js +119 -0
- package/dist/voice-types.d.ts +27 -0
- package/dist/voice.d.ts +6 -1
- package/dist/voice.js +6 -1
- package/dist/wspay.d.ts +41 -0
- package/dist/wspay.generated.d.ts +41 -0
- package/dist/wspay.generated.js +370 -0
- package/dist/wspay.js +65 -0
- package/examples/preflight/kernel-native/inventory.json +1 -1
- package/package.json +3 -3
|
@@ -1 +1 @@
|
|
|
1
|
-
export const BUILTIN_REGISTRY = [{ "psp": "spreedly", "match": /core\.spreedly\.com\/v1\/payment_methods\/restricted\.json/i, "hosts": ["^core\\.spreedly\\.com$"], "encoding": "json", "passthroughHeaders": [/^spreedly-environment-key$/i] }, { "psp": "shopify", "match": /(checkout\.pci\.shopifyinc\.com|deposit\.[a-z0-9-]+\.shopifycs\.com)\/sessions/i, "hosts": ["^checkout\\.pci\\.shopifyinc\\.com$", "^deposit\\.[a-z0-9-]+\\.shopifycs\\.com$"], "encoding": "json", "passthroughHeaders": [/^shopify-identification-signature$/i] }, { "psp": "stripe", "match": /api\.stripe\.com\/v1\/(payment_methods|tokens|sources|confirmation_tokens|setup_intents\/seti_\w+\/confirm|payment_intents\/pi_\w+\/confirm|payment_pages\/cs_(?:test|live)_\w+\/confirm)/i, "hosts": ["^api\\.stripe\\.com$"], "encoding": "form", "passthroughHeaders": [/^authorization$/i, /^stripe-version$/i, /^x-stripe-client-user-agent$/i], "cardFields": ["card[number]", "payment_method_data[card][number]"], "passWithoutCard": "api\\.stripe\\.com/v1/(sources|confirmation_tokens|payment_pages/cs_(?:test|live)_\\w+/confirm)" }, { "psp": "braintree", "match": /payments(\.sandbox)?\.braintree-api\.com\/graphql/i, "hosts": ["^payments\\.braintree-api\\.com$", "^payments\\.sandbox\\.braintree-api\\.com$"], "sandboxHosts": ["^payments\\.sandbox\\.braintree-api\\.com$"], "encoding": "json", "passthroughHeaders": [/^authorization$/i, /^braintree-version$/i] }, { "psp": "checkout_com", "match": /(api|card-acquisition-gateway)(\.sandbox)?\.checkout\.com\/tokens/i, "hosts": ["^api\\.checkout\\.com$", "^api\\.sandbox\\.checkout\\.com$", "^card-acquisition-gateway\\.checkout\\.com$", "^card-acquisition-gateway\\.sandbox\\.checkout\\.com$"], "sandboxHosts": ["^api\\.sandbox\\.checkout\\.com$", "^card-acquisition-gateway\\.sandbox\\.checkout\\.com$"], "encoding": "json", "passthroughHeaders": [/^authorization$/i] }, { "psp": "adyen", "match": /(checkoutshopper-(test|live(-[a-z]+)?)\.adyen\.com|([a-z0-9-]+\.)*adyenpayments\.com)\/checkoutshopper\/v1\/sessions\/[A-Za-z0-9_-]+\/payments/i, "hosts": ["^checkoutshopper-(test|live(-[a-z]+)?)\\.adyen\\.com$", "^([a-z0-9-]+\\.)*adyenpayments\\.com$"], "sandboxHosts": ["^checkoutshopper-test\\.adyen\\.com$"], "encoding": "json", "passthroughHeaders": [], "clientSideEncrypted": true, "mode": "cse" }, { "psp": "tranzila", "match": /(direct\.tranzila\.com\/process\/n\/|directng\.tranzila\.com\/process\/?)/i, "hosts": ["^direct\\.tranzila\\.com$", "^directng\\.tranzila\\.com$"], "encoding": "form", "passthroughHeaders": [], "mode": "hosted_form" }, { "psp": "square", "match": /pci-connect\.squareup(?:sandbox)?\.com\/v2\/card-nonce/i, "hosts": ["^pci-connect\\.squareup\\.com$", "^pci-connect\\.squareupsandbox\\.com$"], "sandboxHosts": ["^pci-connect\\.squareupsandbox\\.com$"], "encoding": "json", "passthroughHeaders": [] }, { "psp": "authorize_net", "match": /(api2|apitest)\.authorize\.net\/xml\/v1\/request\.api/i, "hosts": ["^api2\\.authorize\\.net$", "^apitest\\.authorize\\.net$"], "sandboxHosts": ["^apitest\\.authorize\\.net$"], "encoding": "json", "passthroughHeaders": [] }, { "psp": "worldpay", "match": /(try\.)?access\.worldpay\.com\/(sessions\/card|verifiedTokens\/sessions)/i, "hosts": ["^access\\.worldpay\\.com$", "^try\\.access\\.worldpay\\.com$"], "sandboxHosts": ["^try\\.access\\.worldpay\\.com$"], "encoding": "json", "passthroughHeaders": [/^accept$/i] }, { "psp": "nuvei", "match": /(secure|ppp-test)\.safecharge\.com\/ppp\/api\/v1\/(?:cardTokenization|clientPayment|websdk\/initPaymentWithCardTokenization)\.do/i, "hosts": ["^secure\\.safecharge\\.com$", "^ppp-test\\.safecharge\\.com$"], "sandboxHosts": ["^ppp-test\\.safecharge\\.com$"], "encoding": "json", "passthroughHeaders": [] }, { "psp": "airwallex", "match": /(checkout(\.sandbox)?\.airwallex\.com|pci-api(\.sandbox)?\.airwallex\.com)\/api\/v1\/pa\/(payment_intents\/[^/?#]+\/confirm|payment_consents\/[^/?#]+\/verify|payment_methods\/create)(\?|$)/i, "hosts": ["^checkout\\.airwallex\\.com$", "^checkout\\.sandbox\\.airwallex\\.com$", "^pci-api\\.airwallex\\.com$", "^pci-api\\.sandbox\\.airwallex\\.com$"], "sandboxHosts": ["^checkout\\.sandbox\\.airwallex\\.com$", "^pci-api\\.sandbox\\.airwallex\\.com$"], "encoding": "json", "passthroughHeaders": [/^client-secret$/i, /^authorization$/i, /^x-auth-token$/i, /^x-on-behalf-of$/i, /^x-api-version$/i] }, { "psp": "rapyd", "match": /(api|sandboxapi)\.rapyd\.net\/v1\/(?:hosted\/collect\/card\/[^/?#]+\/payment_method|checkout\/checkout_[A-Za-z0-9]+)(?:[?#]|$)/i, "hosts": ["^api\\.rapyd\\.net$", "^sandboxapi\\.rapyd\\.net$"], "sandboxHosts": ["^sandboxapi\\.rapyd\\.net$"], "encoding": "json", "passthroughHeaders": [] }, { "psp": "dlocal", "match": /ppmcc(-sandbox)?\.dlocal\.com\/cvault\/credit-card\/temporal/i, "hosts": ["^ppmcc\\.dlocal\\.com$", "^ppmcc-sandbox\\.dlocal\\.com$"], "sandboxHosts": ["^ppmcc-sandbox\\.dlocal\\.com$"], "encoding": "json", "passthroughHeaders": [/^x-fields-api-key$/i, /^x-uow$/i, /^x-dlocal-infrav2$/i] }, { "psp": "ebanx", "match": /(?:(?:customer-frontier|customer|api-local-latam|api|sandbox-local-latam|sandbox)\.ebanx\.com|(?:api-diamond|api|sandbox)\.ebanxpay\.com)\/ws\/token/i, "hosts": ["^customer\\.ebanx\\.com$", "^customer-frontier\\.ebanx\\.com$", "^api\\.ebanx\\.com$", "^sandbox\\.ebanx\\.com$", "^api-local-latam\\.ebanx\\.com$", "^sandbox-local-latam\\.ebanx\\.com$", "^api-diamond\\.ebanxpay\\.com$", "^api\\.ebanxpay\\.com$", "^sandbox\\.ebanxpay\\.com$"], "sandboxHosts": ["^sandbox\\.ebanx\\.com$", "^sandbox-local-latam\\.ebanx\\.com$", "^sandbox\\.ebanxpay\\.com$"], "encoding": "json", "passthroughHeaders": [] }, { "psp": "mercado_pago", "match": /api\.mercadopago\.com\/v1\/card_tokens(?![\w/-])/i, "hosts": ["^api\\.mercadopago\\.com$"], "encoding": "json", "passthroughHeaders": [/^x-product-id$/i] }, { "psp": "payu", "match": /(secure\.payu\.com|(merch-prod|secure)\.snd\.payu\.com)\/api\/front\/tokens/i, "hosts": ["^secure\\.payu\\.com$", "^(merch-prod|secure)\\.snd\\.payu\\.com$"], "sandboxHosts": ["^(merch-prod|secure)\\.snd\\.payu\\.com$"], "encoding": "json", "passthroughHeaders": [/^authorization$/i] }, { "psp": "razorpay", "match": /api\.razorpay\.com\/v1\/(?:standard_checkout\/)?payments\/create\/(?:ajax|checkout|fees)/i, "hosts": ["^api\\.razorpay\\.com$"], "encoding": "form", "passthroughHeaders": [/^x-razorpay-sessionid$/i, /^x-customer-access-token$/i] }, { "psp": "mollie", "match": /api\.cc\.mollie\.com\/v1\/card-tokens/i, "hosts": ["^api\\.cc\\.mollie\\.com$"], "encoding": "json", "passthroughHeaders": [] }, { "psp": "paysafe", "match": /(?:api(\.test)?\.paysafe\.com\/(?:paymenthub\/v1\/singleusepaymenthandles|js\/api\/v1\/tokenize)|hosted(\.test)?\.paysafe\.com\/checkout\/api\/v1\/tokenize)(?![\w/-])/i, "hosts": ["^api\\.paysafe\\.com$", "^api\\.test\\.paysafe\\.com$", "^hosted\\.paysafe\\.com$", "^hosted\\.test\\.paysafe\\.com$"], "sandboxHosts": ["^api\\.test\\.paysafe\\.com$", "^hosted\\.test\\.paysafe\\.com$"], "encoding": "json", "passthroughHeaders": [/^authorization$/i, /^x-paysafe-credentials$/i, /^correlationid$/i] }, { "psp": "recurly", "match": /api(\.eu)?\.recurly\.com\/js\/v1\/token(?![\w-])/i, "hosts": ["^api\\.recurly\\.com$", "^api\\.eu\\.recurly\\.com$"], "encoding": "form", "passthroughHeaders": [/^recurly-credential-checkout-hostname$/i] }, { "psp": "moneris", "match": /(www3|esqa|gateway|gatewayqa|gatewayt|gatewaydev)\.moneris\.com\/HPPtoken\/request\.php/i, "hosts": ["^www3\\.moneris\\.com$", "^esqa\\.moneris\\.com$", "^gateway\\.moneris\\.com$", "^gatewayqa\\.moneris\\.com$", "^gatewayt\\.moneris\\.com$", "^gatewaydev\\.moneris\\.com$"], "sandboxHosts": ["^esqa\\.moneris\\.com$", "^gatewayqa\\.moneris\\.com$", "^gatewayt\\.moneris\\.com$", "^gatewaydev\\.moneris\\.com$"], "encoding": "form", "passthroughHeaders": [] }, { "psp": "bambora", "match": /(api\.na\.bambora\.com|api\.bam\.shift4api\.net)\/scripts\/tokenization\/tokens/i, "hosts": ["^api\\.na\\.bambora\\.com$", "^api\\.bam\\.shift4api\\.net$"], "encoding": "json", "passthroughHeaders": [] }, { "psp": "global_payments", "match": /(?:api\.heartlandportico\.com\/SecureSubmit\.v1\/api\/token|cert\.api2\.heartlandportico\.com\/Hps\.Exchange\.PosGateway\.Hpf\.v1\/api\/token|apis(?:\.sandbox)?(?:\.eu)?\.globalpay\.com\/ucp\/(?:merchants\/[^/]+\/)?payment-methods)/i, "hosts": ["^api\\.heartlandportico\\.com$", "^cert\\.api2\\.heartlandportico\\.com$", "^apis\\.globalpay\\.com$", "^apis\\.sandbox\\.globalpay\\.com$", "^apis\\.eu\\.globalpay\\.com$", "^apis\\.sandbox\\.eu\\.globalpay\\.com$"], "sandboxHosts": ["^cert\\.api2\\.heartlandportico\\.com$", "^apis\\.sandbox\\.globalpay\\.com$", "^apis\\.sandbox\\.eu\\.globalpay\\.com$"], "encoding": "json", "passthroughHeaders": [/^authorization$/i, /^x-gp-version$/i] }, { "psp": "gravy", "match": /api(\.sandbox)?\.[a-z0-9-]+\.gr4vy\.app\/checkout\/sessions\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\/fields/i, "hosts": ["^api\\.[a-z0-9-]+\\.gr4vy\\.app$", "^api\\.sandbox\\.[a-z0-9-]+\\.gr4vy\\.app$"], "sandboxHosts": ["^api\\.sandbox\\.[a-z0-9-]+\\.gr4vy\\.app$"], "encoding": "json", "passthroughHeaders": [] }];
|
|
1
|
+
export const BUILTIN_REGISTRY = [{ "psp": "spreedly", "match": /core\.spreedly\.com\/v1\/payment_methods\/restricted\.json/i, "hosts": ["^core\\.spreedly\\.com$"], "encoding": "json", "passthroughHeaders": [/^spreedly-environment-key$/i] }, { "psp": "shopify", "match": /(checkout\.pci\.shopifyinc\.com|deposit\.[a-z0-9-]+\.shopifycs\.com)\/sessions/i, "hosts": ["^checkout\\.pci\\.shopifyinc\\.com$", "^deposit\\.[a-z0-9-]+\\.shopifycs\\.com$"], "encoding": "json", "passthroughHeaders": [/^shopify-identification-signature$/i] }, { "psp": "stripe", "match": /api\.stripe\.com\/v1\/(payment_methods|tokens|sources|confirmation_tokens|setup_intents\/seti_\w+\/confirm|payment_intents\/pi_\w+\/confirm|payment_pages\/cs_(?:test|live)_\w+\/confirm)/i, "hosts": ["^api\\.stripe\\.com$"], "encoding": "form", "passthroughHeaders": [/^authorization$/i, /^stripe-version$/i, /^x-stripe-client-user-agent$/i], "cardFields": ["card[number]", "payment_method_data[card][number]"], "passWithoutCard": "api\\.stripe\\.com/v1/(sources|confirmation_tokens|payment_pages/cs_(?:test|live)_\\w+/confirm)" }, { "psp": "braintree", "match": /payments(\.sandbox)?\.braintree-api\.com\/graphql/i, "hosts": ["^payments\\.braintree-api\\.com$", "^payments\\.sandbox\\.braintree-api\\.com$"], "sandboxHosts": ["^payments\\.sandbox\\.braintree-api\\.com$"], "encoding": "json", "passthroughHeaders": [/^authorization$/i, /^braintree-version$/i] }, { "psp": "checkout_com", "match": /(api|card-acquisition-gateway)(\.sandbox)?\.checkout\.com\/tokens/i, "hosts": ["^api\\.checkout\\.com$", "^api\\.sandbox\\.checkout\\.com$", "^card-acquisition-gateway\\.checkout\\.com$", "^card-acquisition-gateway\\.sandbox\\.checkout\\.com$"], "sandboxHosts": ["^api\\.sandbox\\.checkout\\.com$", "^card-acquisition-gateway\\.sandbox\\.checkout\\.com$"], "encoding": "json", "passthroughHeaders": [/^authorization$/i] }, { "psp": "adyen", "match": /(checkoutshopper-(test|live(-[a-z]+)?)\.adyen\.com|([a-z0-9-]+\.)*adyenpayments\.com)\/checkoutshopper\/v1\/sessions\/[A-Za-z0-9_-]+\/payments/i, "hosts": ["^checkoutshopper-(test|live(-[a-z]+)?)\\.adyen\\.com$", "^([a-z0-9-]+\\.)*adyenpayments\\.com$"], "sandboxHosts": ["^checkoutshopper-test\\.adyen\\.com$"], "encoding": "json", "passthroughHeaders": [], "clientSideEncrypted": true, "mode": "cse" }, { "psp": "tranzila", "match": /(direct\.tranzila\.com\/process\/n\/|directng\.tranzila\.com\/process\/?)/i, "hosts": ["^direct\\.tranzila\\.com$", "^directng\\.tranzila\\.com$"], "encoding": "form", "passthroughHeaders": [], "mode": "hosted_form" }, { "psp": "square", "match": /pci-connect\.squareup(?:sandbox)?\.com\/v2\/card-nonce/i, "hosts": ["^pci-connect\\.squareup\\.com$", "^pci-connect\\.squareupsandbox\\.com$"], "sandboxHosts": ["^pci-connect\\.squareupsandbox\\.com$"], "encoding": "json", "passthroughHeaders": [] }, { "psp": "authorize_net", "match": /(api2|apitest)\.authorize\.net\/xml\/v1\/request\.api/i, "hosts": ["^api2\\.authorize\\.net$", "^apitest\\.authorize\\.net$"], "sandboxHosts": ["^apitest\\.authorize\\.net$"], "encoding": "json", "passthroughHeaders": [] }, { "psp": "worldpay", "match": /(try\.)?access\.worldpay\.com\/(sessions\/card|verifiedTokens\/sessions)/i, "hosts": ["^access\\.worldpay\\.com$", "^try\\.access\\.worldpay\\.com$"], "sandboxHosts": ["^try\\.access\\.worldpay\\.com$"], "encoding": "json", "passthroughHeaders": [/^accept$/i] }, { "psp": "nuvei", "match": /(secure|ppp-test)\.safecharge\.com\/ppp\/api\/v1\/(?:cardTokenization|clientPayment|websdk\/initPaymentWithCardTokenization)\.do/i, "hosts": ["^secure\\.safecharge\\.com$", "^ppp-test\\.safecharge\\.com$"], "sandboxHosts": ["^ppp-test\\.safecharge\\.com$"], "encoding": "json", "passthroughHeaders": [], "jsonCardFields": [["paymentOption", "card", "cardNumber"], ["cardData", "cardNumber"]], "passWithoutCard": "(?:secure|ppp-test)\\.safecharge\\.com/ppp/api/v1/(?:clientPayment|websdk/initPaymentWithCardTokenization)\\.do" }, { "psp": "airwallex", "match": /(checkout(\.sandbox)?\.airwallex\.com|pci-api(\.sandbox)?\.airwallex\.com)\/api\/v1\/pa\/(payment_intents\/[^/?#]+\/confirm|payment_consents\/[^/?#]+\/verify|payment_methods\/create)(\?|$)/i, "hosts": ["^checkout\\.airwallex\\.com$", "^checkout\\.sandbox\\.airwallex\\.com$", "^pci-api\\.airwallex\\.com$", "^pci-api\\.sandbox\\.airwallex\\.com$"], "sandboxHosts": ["^checkout\\.sandbox\\.airwallex\\.com$", "^pci-api\\.sandbox\\.airwallex\\.com$"], "encoding": "json", "passthroughHeaders": [/^client-secret$/i, /^authorization$/i, /^x-auth-token$/i, /^x-on-behalf-of$/i, /^x-api-version$/i] }, { "psp": "rapyd", "match": /(api|sandboxapi)\.rapyd\.net\/v1\/(?:hosted\/collect\/card\/[^/?#]+\/payment_method|checkout\/checkout_[A-Za-z0-9]+)(?:[?#]|$)/i, "hosts": ["^api\\.rapyd\\.net$", "^sandboxapi\\.rapyd\\.net$"], "sandboxHosts": ["^sandboxapi\\.rapyd\\.net$"], "encoding": "json", "passthroughHeaders": [] }, { "psp": "dlocal", "match": /ppmcc(-sandbox)?\.dlocal\.com\/cvault\/credit-card\/temporal/i, "hosts": ["^ppmcc\\.dlocal\\.com$", "^ppmcc-sandbox\\.dlocal\\.com$"], "sandboxHosts": ["^ppmcc-sandbox\\.dlocal\\.com$"], "encoding": "json", "passthroughHeaders": [/^x-fields-api-key$/i, /^x-uow$/i, /^x-dlocal-infrav2$/i] }, { "psp": "ebanx", "match": /(?:(?:customer-frontier|customer|api-local-latam|api|sandbox-local-latam|sandbox)\.ebanx\.com|(?:api-diamond|api|sandbox)\.ebanxpay\.com)\/ws\/token/i, "hosts": ["^customer\\.ebanx\\.com$", "^customer-frontier\\.ebanx\\.com$", "^api\\.ebanx\\.com$", "^sandbox\\.ebanx\\.com$", "^api-local-latam\\.ebanx\\.com$", "^sandbox-local-latam\\.ebanx\\.com$", "^api-diamond\\.ebanxpay\\.com$", "^api\\.ebanxpay\\.com$", "^sandbox\\.ebanxpay\\.com$"], "sandboxHosts": ["^sandbox\\.ebanx\\.com$", "^sandbox-local-latam\\.ebanx\\.com$", "^sandbox\\.ebanxpay\\.com$"], "encoding": "json", "passthroughHeaders": [] }, { "psp": "mercado_pago", "match": /api\.mercadopago\.com\/v1\/card_tokens(?![\w/-])/i, "hosts": ["^api\\.mercadopago\\.com$"], "encoding": "json", "passthroughHeaders": [/^x-product-id$/i] }, { "psp": "payu", "match": /(secure\.payu\.com|(merch-prod|secure)\.snd\.payu\.com)\/api\/front\/tokens/i, "hosts": ["^secure\\.payu\\.com$", "^(merch-prod|secure)\\.snd\\.payu\\.com$"], "sandboxHosts": ["^(merch-prod|secure)\\.snd\\.payu\\.com$"], "encoding": "json", "passthroughHeaders": [/^authorization$/i] }, { "psp": "razorpay", "match": /api\.razorpay\.com\/v1\/(?:standard_checkout\/)?payments\/create\/(?:ajax|checkout|fees)/i, "hosts": ["^api\\.razorpay\\.com$"], "encoding": "form", "passthroughHeaders": [/^x-razorpay-sessionid$/i, /^x-customer-access-token$/i] }, { "psp": "mollie", "match": /api\.cc\.mollie\.com\/v1\/card-tokens/i, "hosts": ["^api\\.cc\\.mollie\\.com$"], "encoding": "json", "passthroughHeaders": [] }, { "psp": "paysafe", "match": /(?:api(\.test)?\.paysafe\.com\/(?:paymenthub\/v1\/singleusepaymenthandles|js\/api\/v1\/tokenize)|hosted(\.test)?\.paysafe\.com\/checkout\/api\/v1\/tokenize)(?![\w/-])/i, "hosts": ["^api\\.paysafe\\.com$", "^api\\.test\\.paysafe\\.com$", "^hosted\\.paysafe\\.com$", "^hosted\\.test\\.paysafe\\.com$"], "sandboxHosts": ["^api\\.test\\.paysafe\\.com$", "^hosted\\.test\\.paysafe\\.com$"], "encoding": "json", "passthroughHeaders": [/^authorization$/i, /^x-paysafe-credentials$/i, /^correlationid$/i] }, { "psp": "recurly", "match": /api(\.eu)?\.recurly\.com\/js\/v1\/token(?![\w-])/i, "hosts": ["^api\\.recurly\\.com$", "^api\\.eu\\.recurly\\.com$"], "encoding": "form", "passthroughHeaders": [/^recurly-credential-checkout-hostname$/i] }, { "psp": "moneris", "match": /(www3|esqa|gateway|gatewayqa|gatewayt|gatewaydev)\.moneris\.com\/HPPtoken\/request\.php/i, "hosts": ["^www3\\.moneris\\.com$", "^esqa\\.moneris\\.com$", "^gateway\\.moneris\\.com$", "^gatewayqa\\.moneris\\.com$", "^gatewayt\\.moneris\\.com$", "^gatewaydev\\.moneris\\.com$"], "sandboxHosts": ["^esqa\\.moneris\\.com$", "^gatewayqa\\.moneris\\.com$", "^gatewayt\\.moneris\\.com$", "^gatewaydev\\.moneris\\.com$"], "encoding": "form", "passthroughHeaders": [] }, { "psp": "bambora", "match": /(api\.na\.bambora\.com|api\.bam\.shift4api\.net)\/scripts\/tokenization\/tokens/i, "hosts": ["^api\\.na\\.bambora\\.com$", "^api\\.bam\\.shift4api\\.net$"], "encoding": "json", "passthroughHeaders": [] }, { "psp": "global_payments", "match": /(?:api\.heartlandportico\.com\/SecureSubmit\.v1\/api\/token|cert\.api2\.heartlandportico\.com\/Hps\.Exchange\.PosGateway\.Hpf\.v1\/api\/token|apis(?:\.sandbox)?(?:\.eu)?\.globalpay\.com\/ucp\/(?:merchants\/[^/]+\/)?payment-methods)/i, "hosts": ["^api\\.heartlandportico\\.com$", "^cert\\.api2\\.heartlandportico\\.com$", "^apis\\.globalpay\\.com$", "^apis\\.sandbox\\.globalpay\\.com$", "^apis\\.eu\\.globalpay\\.com$", "^apis\\.sandbox\\.eu\\.globalpay\\.com$"], "sandboxHosts": ["^cert\\.api2\\.heartlandportico\\.com$", "^apis\\.sandbox\\.globalpay\\.com$", "^apis\\.sandbox\\.eu\\.globalpay\\.com$"], "encoding": "json", "passthroughHeaders": [/^authorization$/i, /^x-gp-version$/i] }, { "psp": "gravy", "match": /api(\.sandbox)?\.[a-z0-9-]+\.gr4vy\.app\/checkout\/sessions\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\/fields/i, "hosts": ["^api\\.[a-z0-9-]+\\.gr4vy\\.app$", "^api\\.sandbox\\.[a-z0-9-]+\\.gr4vy\\.app$"], "sandboxHosts": ["^api\\.sandbox\\.[a-z0-9-]+\\.gr4vy\\.app$"], "encoding": "json", "passthroughHeaders": [] }];
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { type CardEndpointProfile } from './card-endpoint.js';
|
|
2
|
+
/** How long a paused card request waits for a request on its checkout session that left before it and has not answered. */
|
|
3
|
+
export declare const CARD_ENDPOINT_FACTS_WAIT_MS = 3000;
|
|
4
|
+
/**
|
|
5
|
+
* How many recorded requests the watch keeps. Past it, the oldest request of the checkout
|
|
6
|
+
* session that holds the most goes first, so another checkout's requests never push this
|
|
7
|
+
* one's record out while this one holds the fewer. What a checkout lost can still refuse
|
|
8
|
+
* its capture (Lost).
|
|
9
|
+
*/
|
|
10
|
+
export declare const CARD_ENDPOINT_FACTS_KEPT = 32;
|
|
11
|
+
/**
|
|
12
|
+
* How many checkout sessions the watch remembers a loss for. Past it, every capture refuses:
|
|
13
|
+
* a loss the watch forgot could let an older record through.
|
|
14
|
+
*/
|
|
15
|
+
export declare const CARD_ENDPOINT_FACTS_LOST_SESSIONS_KEPT = 256;
|
|
16
|
+
/** The cut order record a create carries (merchant_card_endpoint.facts). */
|
|
17
|
+
export interface CardEndpointFactsRecord {
|
|
18
|
+
method: string;
|
|
19
|
+
url: string;
|
|
20
|
+
state: unknown;
|
|
21
|
+
}
|
|
22
|
+
/** What capture() gives: the cut record, or why there is none to send (names no value). */
|
|
23
|
+
export type CardEndpointFactsCapture = {
|
|
24
|
+
record: CardEndpointFactsRecord;
|
|
25
|
+
refusal?: undefined;
|
|
26
|
+
} | {
|
|
27
|
+
refusal: string;
|
|
28
|
+
record?: undefined;
|
|
29
|
+
};
|
|
30
|
+
export declare class CardEndpointFactsWatch {
|
|
31
|
+
private readonly now;
|
|
32
|
+
private readonly origins;
|
|
33
|
+
private readonly sessionOf;
|
|
34
|
+
private readonly exchanges;
|
|
35
|
+
private readonly lost;
|
|
36
|
+
private lostTooMany;
|
|
37
|
+
private readonly waiters;
|
|
38
|
+
private ticks;
|
|
39
|
+
constructor(now?: () => number, origins?: readonly string[], sessionOf?: (url: string) => string | null);
|
|
40
|
+
/** Whether the watch records the request under this key. */
|
|
41
|
+
tracks(key: string): boolean;
|
|
42
|
+
/**
|
|
43
|
+
* A request left. Recorded only when it is a document, an XHR or a fetch (or of a type
|
|
44
|
+
* the adapter could not name) on the endpoint origin of a profile with facts.
|
|
45
|
+
*/
|
|
46
|
+
sent(key: string, request: {
|
|
47
|
+
method: string;
|
|
48
|
+
url: string;
|
|
49
|
+
resourceType?: string | null;
|
|
50
|
+
networkId?: string | null;
|
|
51
|
+
}): void;
|
|
52
|
+
/** Drops the oldest request of the checkout session that holds the most (of two that hold as many, the one seen first). */
|
|
53
|
+
private evictOne;
|
|
54
|
+
/** Notes what dropping `exchange` takes from its checkout session (Lost). */
|
|
55
|
+
private remember;
|
|
56
|
+
/**
|
|
57
|
+
* The key a load-finished report belongs to: its own when recorded, else the recorded
|
|
58
|
+
* document with the same network id. Chrome reports an out-of-process frame's document
|
|
59
|
+
* request on the parent frame's session and its answer on the frame's own session.
|
|
60
|
+
*/
|
|
61
|
+
keyFor(key: string, networkId: string | null | undefined): string | null;
|
|
62
|
+
/** The request's answer began to arrive, with its HTTP status: the first report stamps when. */
|
|
63
|
+
answered(key: string, status: unknown): void;
|
|
64
|
+
/**
|
|
65
|
+
* The request finished: the adapter reports it the moment the browser does, before it reads
|
|
66
|
+
* the text, so two answers keep the order they finished in however their texts come back.
|
|
67
|
+
*/
|
|
68
|
+
finished(key: string): void;
|
|
69
|
+
/** The request finished and its text was read (null when it could not be, or was past the limit). */
|
|
70
|
+
read(key: string, text: string | null, completedAt?: number): void;
|
|
71
|
+
/** The request failed or was aborted: the page never read an answer from it. */
|
|
72
|
+
failed(key: string): void;
|
|
73
|
+
private settle;
|
|
74
|
+
/**
|
|
75
|
+
* The order record for a paused card request of `profile` at `cardUrl`, as of the pause.
|
|
76
|
+
* It reads every recorded request on the same checkout session sent no later than
|
|
77
|
+
* `pausedAt` that is a record or an unread call (payment-core
|
|
78
|
+
* merchantCardEndpointFactsRole); the card request and every earlier one to its URL
|
|
79
|
+
* answer with the payment's outcome, and an inert call changes nothing the record holds,
|
|
80
|
+
* so neither is waited for or read. One still loading is waited for, up to
|
|
81
|
+
* CARD_ENDPOINT_FACTS_WAIT_MS, and refuses once that runs out: the page was still
|
|
82
|
+
* changing the order when it sent the card request. Then the record whose answer
|
|
83
|
+
* finished last, in the order the watch saw them finish (the one the page shows), is cut
|
|
84
|
+
* by payment-core; one that holds no record (the /payment-context
|
|
85
|
+
* answer) is passed over for the one before it, and one that holds a record in another
|
|
86
|
+
* layout, or did not succeed, refuses. Last, the record refuses when the page sent an
|
|
87
|
+
* unread call, or a record call other than a GET that gave no record, after it arrived.
|
|
88
|
+
*
|
|
89
|
+
* A checkout that sent more requests than the watch keeps refuses whenever what it lost
|
|
90
|
+
* could change that answer: a request still loading when it went, or one that finished
|
|
91
|
+
* after the record or was sent after the record arrived. Every request payment-core gives
|
|
92
|
+
* a role for this card request is on the card request's own session
|
|
93
|
+
* (cardEndpointFactsSession), so its losses are the only ones read.
|
|
94
|
+
*/
|
|
95
|
+
capture(profile: CardEndpointProfile, cardUrl: string, pausedAt: number, signal?: AbortSignal): Promise<CardEndpointFactsCapture>;
|
|
96
|
+
}
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
// The merchant's own record of the order, for a reviewed card endpoint whose merchant
|
|
2
|
+
// keeps one (profile.facts; payment-core merchant-card-endpoint-facts.js says what it
|
|
3
|
+
// proves and what it does not).
|
|
4
|
+
//
|
|
5
|
+
// FastSpring's popup posts the card with no amount. Its page keeps the order it shows in
|
|
6
|
+
// the popup document (script#viewdata) and replaces it with every JSON answer it reads
|
|
7
|
+
// from the session URL, tax included. From attach on, the adapters tell this watch about
|
|
8
|
+
// every request that leaves, every answer and every failure; it keeps, read-only, the
|
|
9
|
+
// documents, XHRs and fetches on the endpoint origin of a profile with facts, and nothing
|
|
10
|
+
// else, a bounded number of them, the busiest checkout session's oldest going first; what a
|
|
11
|
+
// checkout lost to that bound can still refuse its capture. When that profile's card request
|
|
12
|
+
// pauses, capture() waits briefly for any request on the same checkout session that left
|
|
13
|
+
// before the pause, then takes the order record whose answer finished last, the one the page
|
|
14
|
+
// shows, cut by payment-core to the values its reader reads (the total with tax, the
|
|
15
|
+
// currency, test mode and the renewal marks). The create carries that cut record; the client
|
|
16
|
+
// and the API read it with the same rules.
|
|
17
|
+
//
|
|
18
|
+
// A record holds the order only if the page changed nothing after it. payment-core's
|
|
19
|
+
// merchantCardEndpointFactsRole names what each request is to the card request: a call
|
|
20
|
+
// the reader never reads ('unread'), or one of the record's own calls that wrote and gave
|
|
21
|
+
// no record (it failed, or answered without one), sent once the newest record's answer
|
|
22
|
+
// began to arrive or still unanswered when the wait ends, leaves the order unknown, and
|
|
23
|
+
// capture() refuses. "Once" is the order in which the watch saw the events, never a
|
|
24
|
+
// clock, which can give two of them one millisecond. A GET of the record's shapes that
|
|
25
|
+
// answers without one changes nothing, and an inert call (customer recognition) is never read.
|
|
26
|
+
//
|
|
27
|
+
// Nothing here writes to the page, sends a request of its own or keeps a response it was
|
|
28
|
+
// not asked about. A response body is cardholder data by association (it carries the
|
|
29
|
+
// merchant's session id in its URL and the buyer's name and address in its text): it
|
|
30
|
+
// stays in this process, is cut before it leaves, and is dropped with the oldest entries.
|
|
31
|
+
import { MERCHANT_CARD_ENDPOINT_FACTS_TEXT_MAX as SHARED_TEXT_MAX, merchantCardEndpointFactsRole as sharedRole, projectMerchantCardEndpointFacts as sharedProject, } from './merchant-card-endpoints.generated.js';
|
|
32
|
+
import { cardEndpointFactsOrigins, cardEndpointFactsSession } from './card-endpoint.js';
|
|
33
|
+
/** How long a paused card request waits for a request on its checkout session that left before it and has not answered. */
|
|
34
|
+
export const CARD_ENDPOINT_FACTS_WAIT_MS = 3_000;
|
|
35
|
+
/**
|
|
36
|
+
* How many recorded requests the watch keeps. Past it, the oldest request of the checkout
|
|
37
|
+
* session that holds the most goes first, so another checkout's requests never push this
|
|
38
|
+
* one's record out while this one holds the fewer. What a checkout lost can still refuse
|
|
39
|
+
* its capture (Lost).
|
|
40
|
+
*/
|
|
41
|
+
export const CARD_ENDPOINT_FACTS_KEPT = 32;
|
|
42
|
+
/**
|
|
43
|
+
* How many checkout sessions the watch remembers a loss for. Past it, every capture refuses:
|
|
44
|
+
* a loss the watch forgot could let an older record through.
|
|
45
|
+
*/
|
|
46
|
+
export const CARD_ENDPOINT_FACTS_LOST_SESSIONS_KEPT = 256;
|
|
47
|
+
const TEXT_MAX = SHARED_TEXT_MAX;
|
|
48
|
+
const RECORDED_TYPES = new Set(['document', 'xhr', 'fetch']);
|
|
49
|
+
export class CardEndpointFactsWatch {
|
|
50
|
+
now;
|
|
51
|
+
origins;
|
|
52
|
+
sessionOf;
|
|
53
|
+
exchanges = new Map();
|
|
54
|
+
lost = new Map();
|
|
55
|
+
lostTooMany = false;
|
|
56
|
+
waiters = new Set();
|
|
57
|
+
ticks = 0;
|
|
58
|
+
constructor(now = Date.now, origins = cardEndpointFactsOrigins(), sessionOf = cardEndpointFactsSession) {
|
|
59
|
+
this.now = now;
|
|
60
|
+
this.origins = origins;
|
|
61
|
+
this.sessionOf = sessionOf;
|
|
62
|
+
}
|
|
63
|
+
/** Whether the watch records the request under this key. */
|
|
64
|
+
tracks(key) { return this.exchanges.has(key); }
|
|
65
|
+
/**
|
|
66
|
+
* A request left. Recorded only when it is a document, an XHR or a fetch (or of a type
|
|
67
|
+
* the adapter could not name) on the endpoint origin of a profile with facts.
|
|
68
|
+
*/
|
|
69
|
+
sent(key, request) {
|
|
70
|
+
if (this.origins.length === 0 || typeof request.url !== 'string' || typeof request.method !== 'string')
|
|
71
|
+
return;
|
|
72
|
+
let origin;
|
|
73
|
+
try {
|
|
74
|
+
origin = new URL(request.url).origin;
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
if (!this.origins.includes(origin))
|
|
80
|
+
return;
|
|
81
|
+
const type = typeof request.resourceType === 'string' ? request.resourceType.toLowerCase() : '';
|
|
82
|
+
if (type !== '' && !RECORDED_TYPES.has(type))
|
|
83
|
+
return;
|
|
84
|
+
this.ticks += 1;
|
|
85
|
+
this.exchanges.set(key, { method: request.method.toUpperCase(), url: request.url, session: this.sessionOf(request.url) ?? origin,
|
|
86
|
+
networkId: request.networkId ?? null, document: type === 'document', sentTick: this.ticks, answeredTick: null, finishedTick: null,
|
|
87
|
+
sentAt: this.now(), status: null, body: null, completedAt: null, failed: false });
|
|
88
|
+
while (this.exchanges.size > CARD_ENDPOINT_FACTS_KEPT)
|
|
89
|
+
this.evictOne();
|
|
90
|
+
}
|
|
91
|
+
/** Drops the oldest request of the checkout session that holds the most (of two that hold as many, the one seen first). */
|
|
92
|
+
evictOne() {
|
|
93
|
+
const counts = new Map();
|
|
94
|
+
for (const exchange of this.exchanges.values())
|
|
95
|
+
counts.set(exchange.session, (counts.get(exchange.session) ?? 0) + 1);
|
|
96
|
+
let fullest = null;
|
|
97
|
+
for (const [session, count] of counts)
|
|
98
|
+
if (fullest === null || count > counts.get(fullest))
|
|
99
|
+
fullest = session;
|
|
100
|
+
for (const [key, exchange] of this.exchanges) {
|
|
101
|
+
if (exchange.session === fullest) {
|
|
102
|
+
this.exchanges.delete(key);
|
|
103
|
+
this.remember(exchange);
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
/** Notes what dropping `exchange` takes from its checkout session (Lost). */
|
|
109
|
+
remember(exchange) {
|
|
110
|
+
let lost = this.lost.get(exchange.session);
|
|
111
|
+
if (!lost) {
|
|
112
|
+
if (this.lost.size >= CARD_ENDPOINT_FACTS_LOST_SESSIONS_KEPT) {
|
|
113
|
+
this.lostTooMany = true;
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
lost = { loading: false, sentTick: 0, finishedTick: 0 };
|
|
117
|
+
this.lost.set(exchange.session, lost);
|
|
118
|
+
}
|
|
119
|
+
lost.loading ||= exchange.completedAt === null;
|
|
120
|
+
lost.sentTick = Math.max(lost.sentTick, exchange.sentTick);
|
|
121
|
+
lost.finishedTick = Math.max(lost.finishedTick, exchange.finishedTick ?? 0);
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* The key a load-finished report belongs to: its own when recorded, else the recorded
|
|
125
|
+
* document with the same network id. Chrome reports an out-of-process frame's document
|
|
126
|
+
* request on the parent frame's session and its answer on the frame's own session.
|
|
127
|
+
*/
|
|
128
|
+
keyFor(key, networkId) {
|
|
129
|
+
if (this.exchanges.has(key))
|
|
130
|
+
return key;
|
|
131
|
+
if (!networkId)
|
|
132
|
+
return null;
|
|
133
|
+
for (const [recorded, exchange] of this.exchanges) {
|
|
134
|
+
if (exchange.document && exchange.networkId === networkId && exchange.completedAt === null)
|
|
135
|
+
return recorded;
|
|
136
|
+
}
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
/** The request's answer began to arrive, with its HTTP status: the first report stamps when. */
|
|
140
|
+
answered(key, status) {
|
|
141
|
+
const exchange = this.exchanges.get(key);
|
|
142
|
+
if (!exchange)
|
|
143
|
+
return;
|
|
144
|
+
if (Number.isInteger(status))
|
|
145
|
+
exchange.status = status;
|
|
146
|
+
exchange.answeredTick ??= ++this.ticks;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* The request finished: the adapter reports it the moment the browser does, before it reads
|
|
150
|
+
* the text, so two answers keep the order they finished in however their texts come back.
|
|
151
|
+
*/
|
|
152
|
+
finished(key) {
|
|
153
|
+
const exchange = this.exchanges.get(key);
|
|
154
|
+
if (exchange)
|
|
155
|
+
exchange.finishedTick ??= ++this.ticks;
|
|
156
|
+
}
|
|
157
|
+
/** The request finished and its text was read (null when it could not be, or was past the limit). */
|
|
158
|
+
read(key, text, completedAt = this.now()) {
|
|
159
|
+
const exchange = this.exchanges.get(key);
|
|
160
|
+
if (!exchange)
|
|
161
|
+
return;
|
|
162
|
+
exchange.finishedTick ??= ++this.ticks;
|
|
163
|
+
exchange.body = typeof text === 'string' && text.length <= TEXT_MAX ? text : null;
|
|
164
|
+
exchange.completedAt = completedAt;
|
|
165
|
+
this.settle();
|
|
166
|
+
}
|
|
167
|
+
/** The request failed or was aborted: the page never read an answer from it. */
|
|
168
|
+
failed(key) {
|
|
169
|
+
const exchange = this.exchanges.get(key);
|
|
170
|
+
if (!exchange)
|
|
171
|
+
return;
|
|
172
|
+
exchange.failed = true;
|
|
173
|
+
exchange.completedAt ??= this.now();
|
|
174
|
+
this.settle();
|
|
175
|
+
}
|
|
176
|
+
settle() {
|
|
177
|
+
for (const wake of [...this.waiters])
|
|
178
|
+
wake();
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* The order record for a paused card request of `profile` at `cardUrl`, as of the pause.
|
|
182
|
+
* It reads every recorded request on the same checkout session sent no later than
|
|
183
|
+
* `pausedAt` that is a record or an unread call (payment-core
|
|
184
|
+
* merchantCardEndpointFactsRole); the card request and every earlier one to its URL
|
|
185
|
+
* answer with the payment's outcome, and an inert call changes nothing the record holds,
|
|
186
|
+
* so neither is waited for or read. One still loading is waited for, up to
|
|
187
|
+
* CARD_ENDPOINT_FACTS_WAIT_MS, and refuses once that runs out: the page was still
|
|
188
|
+
* changing the order when it sent the card request. Then the record whose answer
|
|
189
|
+
* finished last, in the order the watch saw them finish (the one the page shows), is cut
|
|
190
|
+
* by payment-core; one that holds no record (the /payment-context
|
|
191
|
+
* answer) is passed over for the one before it, and one that holds a record in another
|
|
192
|
+
* layout, or did not succeed, refuses. Last, the record refuses when the page sent an
|
|
193
|
+
* unread call, or a record call other than a GET that gave no record, after it arrived.
|
|
194
|
+
*
|
|
195
|
+
* A checkout that sent more requests than the watch keeps refuses whenever what it lost
|
|
196
|
+
* could change that answer: a request still loading when it went, or one that finished
|
|
197
|
+
* after the record or was sent after the record arrived. Every request payment-core gives
|
|
198
|
+
* a role for this card request is on the card request's own session
|
|
199
|
+
* (cardEndpointFactsSession), so its losses are the only ones read.
|
|
200
|
+
*/
|
|
201
|
+
async capture(profile, cardUrl, pausedAt, signal) {
|
|
202
|
+
if (!profile.facts)
|
|
203
|
+
return { refusal: `Agentcard reads no order record for ${profile.merchant}` };
|
|
204
|
+
const session = this.sessionOf(cardUrl);
|
|
205
|
+
const lostNow = () => (this.lostTooMany ? 'untracked' : (session === null ? null : this.lost.get(session) ?? null));
|
|
206
|
+
const lostRefusal = (lost) => (lost === 'untracked'
|
|
207
|
+
? { refusal: `this runtime lost track of the checkouts on ${profile.merchant}'s page, so the order the card would pay is not known` }
|
|
208
|
+
: { refusal: `${profile.merchant}'s page sent more requests on this checkout than this runtime keeps, so the order the card would pay is not known` });
|
|
209
|
+
const before = lostNow();
|
|
210
|
+
if (before === 'untracked' || before?.loading)
|
|
211
|
+
return lostRefusal(before);
|
|
212
|
+
const roleOf = (exchange) => {
|
|
213
|
+
try {
|
|
214
|
+
return sharedRole(profile.id, cardUrl, { method: exchange.method, url: exchange.url });
|
|
215
|
+
}
|
|
216
|
+
catch {
|
|
217
|
+
return null;
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
const read = () => [...this.exchanges.values()].filter((exchange) => exchange.sentAt <= pausedAt)
|
|
221
|
+
.map((exchange) => ({ exchange, role: roleOf(exchange) })).filter(({ role }) => role === 'record' || role === 'unread');
|
|
222
|
+
// The wait runs on the real clock, whatever clock stamps the requests.
|
|
223
|
+
const deadline = Date.now() + CARD_ENDPOINT_FACTS_WAIT_MS;
|
|
224
|
+
while (read().some(({ exchange }) => exchange.completedAt === null)) {
|
|
225
|
+
const left = deadline - Date.now();
|
|
226
|
+
if (left <= 0 || signal?.aborted) {
|
|
227
|
+
return read().some(({ exchange, role }) => role === 'record' && exchange.completedAt === null)
|
|
228
|
+
? { refusal: `${profile.merchant}'s record of this checkout was still loading when the card request left` }
|
|
229
|
+
: { refusal: `${profile.merchant}'s page was still waiting on a request on this checkout when the card request left, so the order the card would pay is not known` };
|
|
230
|
+
}
|
|
231
|
+
await new Promise((resolve) => {
|
|
232
|
+
const wake = () => { clearTimeout(timer); this.waiters.delete(wake); signal?.removeEventListener('abort', wake); resolve(); };
|
|
233
|
+
const timer = setTimeout(wake, left);
|
|
234
|
+
this.waiters.add(wake);
|
|
235
|
+
signal?.addEventListener('abort', wake, { once: true });
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
const seen = read();
|
|
239
|
+
// The page may have sent more while the wait ran.
|
|
240
|
+
const lost = lostNow();
|
|
241
|
+
if (lost === 'untracked' || lost?.loading)
|
|
242
|
+
return lostRefusal(lost);
|
|
243
|
+
// Newest by when it finished, in the order the watch saw them finish: two in one millisecond
|
|
244
|
+
// keep that order, whichever of them was sent first.
|
|
245
|
+
const done = seen.filter(({ exchange, role }) => role === 'record' && !exchange.failed)
|
|
246
|
+
.sort((a, b) => (b.exchange.finishedTick ?? 0) - (a.exchange.finishedTick ?? 0));
|
|
247
|
+
for (const { exchange } of done) {
|
|
248
|
+
let cut;
|
|
249
|
+
try {
|
|
250
|
+
cut = sharedProject(profile.id, cardUrl, { method: exchange.method, url: exchange.url, status: exchange.status ?? 0, body: exchange.body });
|
|
251
|
+
}
|
|
252
|
+
catch {
|
|
253
|
+
return { refusal: `${profile.merchant}'s record of this checkout could not be read` };
|
|
254
|
+
}
|
|
255
|
+
if (cut.none)
|
|
256
|
+
continue;
|
|
257
|
+
if (cut.refusal)
|
|
258
|
+
return { refusal: cut.refusal };
|
|
259
|
+
// Anything the page sent once this record's answer began to arrive could have changed the
|
|
260
|
+
// order unseen: an unread call, or a record call other than a GET, which gave no record of
|
|
261
|
+
// its own (one that did would be newer than this one). A record the reader read had its
|
|
262
|
+
// status reported, and with it the moment its answer began.
|
|
263
|
+
const arrived = exchange.answeredTick ?? exchange.sentTick;
|
|
264
|
+
if (seen.some(({ exchange: later, role }) => later.sentTick > arrived && (role === 'unread' || later.method !== 'GET'))) {
|
|
265
|
+
return { refusal: `${profile.merchant}'s page sent a request on this checkout after its newest record of the order, so the order the card would pay is not known` };
|
|
266
|
+
}
|
|
267
|
+
// A request the bound took that finished after this record could have been a newer one,
|
|
268
|
+
// and one sent after this record arrived could have changed the order after it.
|
|
269
|
+
if (lost && (lost.finishedTick > (exchange.finishedTick ?? 0) || lost.sentTick > arrived))
|
|
270
|
+
return lostRefusal(lost);
|
|
271
|
+
return { record: cut.record };
|
|
272
|
+
}
|
|
273
|
+
return { refusal: `this runtime saw no record of this checkout from ${profile.merchant}'s page` };
|
|
274
|
+
}
|
|
275
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The page a paused card form navigation is answered with when Agentcard stops it
|
|
3
|
+
* (card-endpoint.ts, a profile whose `navigation` is 'top' or 'frame').
|
|
4
|
+
*
|
|
5
|
+
* A merchant's form that the page submits as a page load leaves the agent's browser
|
|
6
|
+
* waiting for a page, so an aborted request is the wrong answer: it renders an error
|
|
7
|
+
* screen or nothing, and invites another click on Pay (the hosted_form lesson,
|
|
8
|
+
* hosted-form.ts). The page says what happened in one of three outcomes, and nothing else:
|
|
9
|
+
*
|
|
10
|
+
* - `stopped_before_send`: the card never left the cardholder's device, the merchant
|
|
11
|
+
* received nothing, and no other payment for this checkout is waiting or may have
|
|
12
|
+
* reached the merchant, so nothing was charged;
|
|
13
|
+
* - `sent_answer_not_shown`: the card reached the merchant and its answer cannot be
|
|
14
|
+
* shown here (withheld because it could carry card data, or a redirect that would
|
|
15
|
+
* make the browser send the form again), so the payment has to be checked with the
|
|
16
|
+
* merchant before paying again;
|
|
17
|
+
* - `payment_in_progress`: a payment for this checkout is waiting for the cardholder's
|
|
18
|
+
* approval or may have reached the merchant: an approval still outstanding (a card
|
|
19
|
+
* endpoint's device sends the card before it approves), an earlier payment this
|
|
20
|
+
* attachment handed over or cannot account for, one whose outcome the API could not
|
|
21
|
+
* confirm, or one the API holds for the same checkout (merchant_checkout_in_use). The
|
|
22
|
+
* form must not be sent again until that payment is checked with the merchant.
|
|
23
|
+
*
|
|
24
|
+
* Self-contained HTML: no external resource, no script that runs (the one `<script>` is
|
|
25
|
+
* `application/json`), readable by a person in one paragraph and by an agent through the
|
|
26
|
+
* meta tag, the header and the JSON block. It never carries the endpoint URL, the paused
|
|
27
|
+
* body or the merchant's answer: only the profile's own merchant name, escaped.
|
|
28
|
+
*/
|
|
29
|
+
import type { SyntheticPage } from './hosted-form.js';
|
|
30
|
+
import type { CheckoutState } from './lifecycle.js';
|
|
31
|
+
export type CardEndpointStoppedOutcome = 'stopped_before_send' | 'sent_answer_not_shown' | 'payment_in_progress';
|
|
32
|
+
/** Why the navigation was stopped, in the words an agent reads from the JSON block. */
|
|
33
|
+
export type CardEndpointStoppedReason = 'not_enabled' | 'request_unsupported' | 'not_approved' | 'blocked' | 'checkout_in_use' | 'answer_withheld' | 'redirect_repeats_post' | 'redirect_location_invalid' | 'outcome_unknown';
|
|
34
|
+
/**
|
|
35
|
+
* The outcome for a card form navigation refused before its own card could leave. While
|
|
36
|
+
* an approval is outstanding (a card endpoint's device sends the card before it approves)
|
|
37
|
+
* or once the controller says a payment may exist, the page never says that nothing was
|
|
38
|
+
* sent, which would invite a second payment: it reads `payment_in_progress`. Otherwise
|
|
39
|
+
* nothing has left, and it reads `stopped_before_send`.
|
|
40
|
+
*/
|
|
41
|
+
export declare function refusedNavigationOutcome(state: {
|
|
42
|
+
awaitingApproval: boolean;
|
|
43
|
+
status: CheckoutState['status'];
|
|
44
|
+
}): CardEndpointStoppedOutcome;
|
|
45
|
+
export interface CardEndpointStoppedPageInput {
|
|
46
|
+
outcome: CardEndpointStoppedOutcome;
|
|
47
|
+
reason: CardEndpointStoppedReason;
|
|
48
|
+
/** The profile's own merchant name (never the integrator's or the page's). */
|
|
49
|
+
merchant: string;
|
|
50
|
+
/** The authorization, once one exists. */
|
|
51
|
+
authorizationId?: string | null;
|
|
52
|
+
}
|
|
53
|
+
/** The stopped page for a paused card form navigation: status 200, HTML, never cached. */
|
|
54
|
+
export declare function cardEndpointStoppedPage(input: CardEndpointStoppedPageInput): SyntheticPage;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The controller statuses (lifecycle.ts) in which a payment for the attachment's checkout
|
|
3
|
+
* may exist: its card was handed over, a step-up holds it, or its outcome is not known.
|
|
4
|
+
*/
|
|
5
|
+
const PAYMENT_MAY_EXIST = new Set(['awaiting_merchant', 'completed', 'requires_user_action', 'outcome_unknown']);
|
|
6
|
+
/**
|
|
7
|
+
* The outcome for a card form navigation refused before its own card could leave. While
|
|
8
|
+
* an approval is outstanding (a card endpoint's device sends the card before it approves)
|
|
9
|
+
* or once the controller says a payment may exist, the page never says that nothing was
|
|
10
|
+
* sent, which would invite a second payment: it reads `payment_in_progress`. Otherwise
|
|
11
|
+
* nothing has left, and it reads `stopped_before_send`.
|
|
12
|
+
*/
|
|
13
|
+
export function refusedNavigationOutcome(state) {
|
|
14
|
+
return state.awaitingApproval || PAYMENT_MAY_EXIST.has(state.status) ? 'payment_in_progress' : 'stopped_before_send';
|
|
15
|
+
}
|
|
16
|
+
function escapeHtml(s) {
|
|
17
|
+
return String(s).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/'/g, ''');
|
|
18
|
+
}
|
|
19
|
+
const TEXT = {
|
|
20
|
+
stopped_before_send: (merchant) => ({
|
|
21
|
+
title: 'Payment stopped',
|
|
22
|
+
body: `Agentcard stopped this payment before your card reached ${merchant}. Nothing was sent and nothing was charged.`,
|
|
23
|
+
next: 'nothing reached the merchant; start the checkout again or pay another way',
|
|
24
|
+
}),
|
|
25
|
+
sent_answer_not_shown: (merchant) => ({
|
|
26
|
+
title: 'Check this payment',
|
|
27
|
+
body: `Your card was sent to ${merchant}, and its answer cannot be shown here. Do not submit this form again. Check the payment with ${merchant} before paying again.`,
|
|
28
|
+
next: "check the payment with the merchant; never resubmit this form",
|
|
29
|
+
}),
|
|
30
|
+
payment_in_progress: (merchant) => ({
|
|
31
|
+
title: 'Check this payment',
|
|
32
|
+
body: `A payment for this checkout is waiting for approval or may have reached ${merchant}. Do not submit this form again. Check that payment with ${merchant} before paying again.`,
|
|
33
|
+
next: 'a payment for this checkout is waiting or may have reached the merchant; check it with the merchant; never resubmit this form',
|
|
34
|
+
}),
|
|
35
|
+
};
|
|
36
|
+
/** The stopped page for a paused card form navigation: status 200, HTML, never cached. */
|
|
37
|
+
export function cardEndpointStoppedPage(input) {
|
|
38
|
+
const merchant = String(input.merchant ?? '');
|
|
39
|
+
const text = TEXT[input.outcome](escapeHtml(merchant));
|
|
40
|
+
const record = {
|
|
41
|
+
outcome: input.outcome,
|
|
42
|
+
reason: input.reason,
|
|
43
|
+
merchant,
|
|
44
|
+
...(input.authorizationId ? { authorization_id: String(input.authorizationId) } : {}),
|
|
45
|
+
next: text.next,
|
|
46
|
+
};
|
|
47
|
+
const json = JSON.stringify(record).replace(/</g, '\\u003c').replace(/>/g, '\\u003e').replace(/&/g, '\\u0026');
|
|
48
|
+
const body = [
|
|
49
|
+
'<!doctype html>',
|
|
50
|
+
'<html lang="en">',
|
|
51
|
+
'<head>',
|
|
52
|
+
'<meta charset="utf-8">',
|
|
53
|
+
`<meta name="agentcard-checkout" content="${input.outcome}">`,
|
|
54
|
+
'<meta name="robots" content="noindex">',
|
|
55
|
+
`<title>${text.title}</title>`,
|
|
56
|
+
'</head>',
|
|
57
|
+
'<body>',
|
|
58
|
+
`<p>${text.body}</p>`,
|
|
59
|
+
`<script type="application/json" id="agentcard-checkout">${json}</script>`,
|
|
60
|
+
'</body>',
|
|
61
|
+
'</html>',
|
|
62
|
+
'',
|
|
63
|
+
].join('\n');
|
|
64
|
+
return {
|
|
65
|
+
status: 200,
|
|
66
|
+
headers: { 'content-type': 'text/html; charset=utf-8', 'cache-control': 'no-store', 'x-agentcard-checkout': input.outcome },
|
|
67
|
+
body,
|
|
68
|
+
};
|
|
69
|
+
}
|