@agent-cards/checkout 0.7.0 → 0.9.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.
Files changed (46) hide show
  1. package/CHANGELOG.md +15 -1
  2. package/PREFLIGHT.md +294 -0
  3. package/README.md +16 -3
  4. package/dist/braintree.d.ts +2 -10
  5. package/dist/braintree.generated.d.ts +10 -0
  6. package/dist/braintree.generated.js +302 -0
  7. package/dist/braintree.js +2 -302
  8. package/dist/builtin-registry.generated.d.ts +2 -0
  9. package/dist/builtin-registry.generated.js +1 -0
  10. package/dist/cdp.d.ts +4 -2
  11. package/dist/cdp.js +38 -6
  12. package/dist/client.d.ts +13 -4
  13. package/dist/client.js +80 -8
  14. package/dist/index.d.ts +1 -1
  15. package/dist/lifecycle.d.ts +1 -0
  16. package/dist/lifecycle.js +27 -1
  17. package/dist/owned-shop.generated.d.ts +24 -0
  18. package/dist/owned-shop.generated.js +108 -0
  19. package/dist/playwright.d.ts +3 -0
  20. package/dist/playwright.js +3 -0
  21. package/dist/preflight-capabilities.generated.d.ts +1165 -0
  22. package/dist/preflight-capabilities.generated.js +1799 -0
  23. package/dist/preflight-catalog.json +3892 -0
  24. package/dist/preflight-playwright.d.ts +34 -0
  25. package/dist/preflight-playwright.js +262 -0
  26. package/dist/preflight-schemas.json +1063 -0
  27. package/dist/preflight.d.ts +1 -0
  28. package/dist/preflight.generated.d.ts +1867 -0
  29. package/dist/preflight.generated.js +482 -0
  30. package/dist/preflight.js +2 -0
  31. package/dist/registry.d.ts +2 -19
  32. package/dist/registry.js +3 -188
  33. package/dist/substitute.d.ts +5 -9
  34. package/dist/substitute.js +5 -69
  35. package/dist/substitutions.generated.d.ts +10 -0
  36. package/dist/substitutions.generated.js +66 -0
  37. package/examples/preflight/classify-direct.mjs +21 -0
  38. package/examples/preflight/classify-kernel.mjs +30 -0
  39. package/examples/preflight/inspect-browser.mjs +44 -0
  40. package/examples/preflight/kernel-profile.empty.json +11 -0
  41. package/examples/preflight/mollie-hosted.observations.json +23 -0
  42. package/examples/preflight/mollie-hosted.result.json +103 -0
  43. package/examples/preflight/stripe-script.direct.result.json +92 -0
  44. package/examples/preflight/stripe-script.observations.json +16 -0
  45. package/examples/preflight/stripe-script.result.json +87 -0
  46. package/package.json +16 -7
@@ -50,25 +50,8 @@ export interface Recognizer {
50
50
  /** How the card reaches the processor; absent means 'token'. Carried verbatim by syncRegistry. */
51
51
  mode?: CheckoutMode;
52
52
  }
53
- /**
54
- * Every PSP the service recognizes, shipped with the package.
55
- *
56
- * This list is the FALLBACK that decides interception whenever
57
- * `syncRegistry()` is skipped or its fetch fails — and that failure is silent
58
- * by design ("never break checkout over a registry fetch"). A PSP missing here
59
- * is not an error: its tokenization request is simply never paused, the vault
60
- * is never consulted, and the shopper pays with their OWN card.
61
- *
62
- * It shipped with only 5 of the service's 22 recognizers, which meant 17
63
- * processors degraded to a silent wrong-card charge for any integrator whose
64
- * sync had not landed. Generated from the backend registry
65
- * (packages/backend/src/services/checkout-recognizers.ts) so the two cannot
66
- * drift by transcription; `sdk-registry-drift.test.ts` fails CI if they do.
67
- *
68
- * Syncing still matters — it is how a NEW processor arrives without an npm
69
- * release. This list is the floor, not the ceiling.
70
- */
71
- export declare const BUILTIN_REGISTRY: Recognizer[];
53
+ import { BUILTIN_REGISTRY } from './builtin-registry.generated.js';
54
+ export { BUILTIN_REGISTRY };
72
55
  export declare function findRecognizer(url: string, registry?: Recognizer[]): Recognizer | null;
73
56
  /**
74
57
  * Glob url patterns covering every host in `registry` — what to hand
package/dist/registry.js CHANGED
@@ -6,194 +6,9 @@
6
6
  * site shares another. The list is served from the Agentcard API at runtime so
7
7
  * integrators pick up new processors without shipping a release.
8
8
  */
9
- /**
10
- * Every PSP the service recognizes, shipped with the package.
11
- *
12
- * This list is the FALLBACK that decides interception whenever
13
- * `syncRegistry()` is skipped or its fetch fails — and that failure is silent
14
- * by design ("never break checkout over a registry fetch"). A PSP missing here
15
- * is not an error: its tokenization request is simply never paused, the vault
16
- * is never consulted, and the shopper pays with their OWN card.
17
- *
18
- * It shipped with only 5 of the service's 22 recognizers, which meant 17
19
- * processors degraded to a silent wrong-card charge for any integrator whose
20
- * sync had not landed. Generated from the backend registry
21
- * (packages/backend/src/services/checkout-recognizers.ts) so the two cannot
22
- * drift by transcription; `sdk-registry-drift.test.ts` fails CI if they do.
23
- *
24
- * Syncing still matters — it is how a NEW processor arrives without an npm
25
- * release. This list is the floor, not the ceiling.
26
- */
27
- export const BUILTIN_REGISTRY = [
28
- {
29
- psp: 'shopify',
30
- match: /(checkout\.pci\.shopifyinc\.com|deposit\.[a-z0-9-]+\.shopifycs\.com)\/sessions/i,
31
- hosts: ["^checkout\\.pci\\.shopifyinc\\.com$", "^deposit\\.[a-z0-9-]+\\.shopifycs\\.com$"],
32
- encoding: 'json',
33
- passthroughHeaders: [/^shopify-identification-signature$/i],
34
- },
35
- {
36
- psp: 'stripe',
37
- match: /api\.stripe\.com\/v1\/(payment_methods|tokens|setup_intents\/seti_\w+\/confirm|payment_intents\/pi_\w+\/confirm)/i,
38
- hosts: ["^api\\.stripe\\.com$"],
39
- encoding: 'form',
40
- passthroughHeaders: [/^authorization$/i, /^stripe-version$/i, /^x-stripe-client-user-agent$/i],
41
- },
42
- {
43
- psp: 'braintree',
44
- match: /payments(\.sandbox)?\.braintree-api\.com\/graphql/i,
45
- hosts: ["^payments\\.braintree-api\\.com$", "^payments\\.sandbox\\.braintree-api\\.com$"],
46
- encoding: 'json',
47
- passthroughHeaders: [/^authorization$/i, /^braintree-version$/i],
48
- },
49
- {
50
- psp: 'checkout_com',
51
- // Flow's native card controller can use either tokenization host.
52
- match: /(api|card-acquisition-gateway)(\.sandbox)?\.checkout\.com\/tokens/i,
53
- hosts: [
54
- "^api\\.checkout\\.com$", "^api\\.sandbox\\.checkout\\.com$",
55
- "^card-acquisition-gateway\\.checkout\\.com$", "^card-acquisition-gateway\\.sandbox\\.checkout\\.com$",
56
- ],
57
- encoding: 'json',
58
- passthroughHeaders: [/^authorization$/i],
59
- },
60
- {
61
- psp: 'adyen',
62
- match: /(checkoutshopper-(test|live(-[a-z]+)?)\.adyen\.com|([a-z0-9-]+\.)*adyenpayments\.com)\/checkoutshopper\/v1\/sessions\/[A-Za-z0-9_-]+\/payments/i,
63
- hosts: ["^checkoutshopper-(test|live(-[a-z]+)?)\\.adyen\\.com$", "^([a-z0-9-]+\\.)*adyenpayments\\.com$"],
64
- encoding: 'json',
65
- passthroughHeaders: [],
66
- clientSideEncrypted: true,
67
- mode: 'cse',
68
- },
69
- {
70
- psp: 'tranzila',
71
- match: /(direct\.tranzila\.com\/process\/n\/|directng\.tranzila\.com\/process\/?)/i,
72
- hosts: ["^direct\\.tranzila\\.com$", "^directng\\.tranzila\\.com$"],
73
- encoding: 'form',
74
- passthroughHeaders: [],
75
- mode: 'hosted_form',
76
- },
77
- {
78
- psp: 'square',
79
- match: /pci-connect\.squareup(?:sandbox)?\.com\/v2\/card-nonce/i,
80
- hosts: ["^pci-connect\\.squareup\\.com$", "^pci-connect\\.squareupsandbox\\.com$"],
81
- encoding: 'json',
82
- passthroughHeaders: [],
83
- },
84
- {
85
- psp: 'authorize_net',
86
- match: /(api2|apitest)\.authorize\.net\/xml\/v1\/request\.api/i,
87
- hosts: ["^api2\\.authorize\\.net$", "^apitest\\.authorize\\.net$"],
88
- encoding: 'json',
89
- passthroughHeaders: [],
90
- },
91
- {
92
- psp: 'worldpay',
93
- match: /(try\.)?access\.worldpay\.com\/(sessions\/card|verifiedTokens\/sessions)/i,
94
- hosts: ["^access\\.worldpay\\.com$", "^try\\.access\\.worldpay\\.com$"],
95
- encoding: 'json',
96
- passthroughHeaders: [/^accept$/i],
97
- },
98
- {
99
- psp: 'nuvei',
100
- match: /(secure|ppp-test)\.safecharge\.com\/ppp\/api\/v1\/(?:cardTokenization|clientPayment|websdk\/initPaymentWithCardTokenization)\.do/i,
101
- hosts: ["^secure\\.safecharge\\.com$", "^ppp-test\\.safecharge\\.com$"],
102
- encoding: 'json',
103
- passthroughHeaders: [],
104
- },
105
- {
106
- psp: 'airwallex',
107
- match: /(checkout(\.sandbox)?\.airwallex\.com|pci-api(\.sandbox)?\.airwallex\.com)\/api\/v1\/pa\/(payment_intents\/[^\/?#]+\/confirm|payment_consents\/[^\/?#]+\/verify|payment_methods\/create)(\?|$)/i,
108
- hosts: ["^checkout\\.airwallex\\.com$", "^checkout\\.sandbox\\.airwallex\\.com$", "^pci-api\\.airwallex\\.com$", "^pci-api\\.sandbox\\.airwallex\\.com$"],
109
- encoding: 'json',
110
- passthroughHeaders: [/^client-secret$/i, /^authorization$/i, /^x-auth-token$/i, /^x-on-behalf-of$/i, /^x-api-version$/i],
111
- },
112
- {
113
- psp: 'rapyd',
114
- match: /(api|sandboxapi)\.rapyd\.net\/v1\/(?:hosted\/collect\/card\/[^\/?#]+\/payment_method|checkout\/checkout_[A-Za-z0-9]+)(?:[?#]|$)/i,
115
- hosts: ["^api\\.rapyd\\.net$", "^sandboxapi\\.rapyd\\.net$"],
116
- encoding: 'json',
117
- passthroughHeaders: [],
118
- },
119
- {
120
- psp: 'dlocal',
121
- match: /ppmcc(-sandbox)?\.dlocal\.com\/cvault\/credit-card\/temporal/i,
122
- hosts: ["^ppmcc\\.dlocal\\.com$", "^ppmcc-sandbox\\.dlocal\\.com$"],
123
- encoding: 'json',
124
- passthroughHeaders: [/^x-fields-api-key$/i, /^x-uow$/i, /^x-dlocal-infrav2$/i],
125
- },
126
- {
127
- psp: 'ebanx',
128
- match: /(?:(?:customer-frontier|customer|api-local-latam|api|sandbox-local-latam|sandbox)\.ebanx\.com|(?:api-diamond|api|sandbox)\.ebanxpay\.com)\/ws\/token/i,
129
- 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$"],
130
- encoding: 'json',
131
- passthroughHeaders: [],
132
- },
133
- {
134
- psp: 'mercado_pago',
135
- match: /api\.mercadopago\.com\/v1\/card_tokens(?![\w\/-])/i,
136
- hosts: ["^api\\.mercadopago\\.com$"],
137
- encoding: 'json',
138
- passthroughHeaders: [/^x-product-id$/i],
139
- },
140
- {
141
- psp: 'payu',
142
- match: /(secure\.payu\.com|(merch-prod|secure)\.snd\.payu\.com)\/api\/front\/tokens/i,
143
- hosts: ["^secure\\.payu\\.com$", "^(merch-prod|secure)\\.snd\\.payu\\.com$"],
144
- encoding: 'json',
145
- passthroughHeaders: [],
146
- },
147
- {
148
- psp: 'razorpay',
149
- match: /api\.razorpay\.com\/v1\/(?:standard_checkout\/)?payments\/create\/(?:ajax|checkout|fees)/i,
150
- hosts: ["^api\\.razorpay\\.com$"],
151
- encoding: 'form',
152
- passthroughHeaders: [/^x-razorpay-sessionid$/i, /^x-customer-access-token$/i],
153
- },
154
- {
155
- psp: 'mollie',
156
- match: /api\.cc\.mollie\.com\/v1\/card-tokens/i,
157
- hosts: ["^api\\.cc\\.mollie\\.com$"],
158
- encoding: 'json',
159
- passthroughHeaders: [],
160
- },
161
- {
162
- psp: 'paysafe',
163
- match: /(?:api(\.test)?\.paysafe\.com\/(?:paymenthub\/v1\/singleusepaymenthandles|js\/api\/v1\/tokenize)|hosted(\.test)?\.paysafe\.com\/checkout\/api\/v1\/tokenize)(?![\w\/-])/i,
164
- hosts: ["^api\\.paysafe\\.com$", "^api\\.test\\.paysafe\\.com$", "^hosted\\.paysafe\\.com$", "^hosted\\.test\\.paysafe\\.com$"],
165
- encoding: 'json',
166
- passthroughHeaders: [/^authorization$/i, /^x-paysafe-credentials$/i, /^correlationid$/i],
167
- },
168
- {
169
- psp: 'recurly',
170
- match: /api(\.eu)?\.recurly\.com\/js\/v1\/token(?![\w-])/i,
171
- hosts: ["^api\\.recurly\\.com$", "^api\\.eu\\.recurly\\.com$"],
172
- encoding: 'form',
173
- passthroughHeaders: [/^recurly-credential-checkout-hostname$/i],
174
- },
175
- {
176
- psp: 'moneris',
177
- match: /(www3|esqa|gateway|gatewayqa|gatewayt|gatewaydev)\.moneris\.com\/HPPtoken\/request\.php/i,
178
- hosts: ["^www3\\.moneris\\.com$", "^esqa\\.moneris\\.com$", "^gateway\\.moneris\\.com$", "^gatewayqa\\.moneris\\.com$", "^gatewayt\\.moneris\\.com$", "^gatewaydev\\.moneris\\.com$"],
179
- encoding: 'form',
180
- passthroughHeaders: [],
181
- },
182
- {
183
- psp: 'bambora',
184
- match: /(api\.na\.bambora\.com|api\.bam\.shift4api\.net)\/scripts\/tokenization\/tokens/i,
185
- hosts: ["^api\\.na\\.bambora\\.com$", "^api\\.bam\\.shift4api\\.net$"],
186
- encoding: 'json',
187
- passthroughHeaders: [],
188
- },
189
- {
190
- psp: 'global_payments',
191
- 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,
192
- hosts: ["^api\\.heartlandportico\\.com$", "^cert\\.api2\\.heartlandportico\\.com$", "^apis\\.globalpay\\.com$", "^apis\\.sandbox\\.globalpay\\.com$", "^apis\\.eu\\.globalpay\\.com$", "^apis\\.sandbox\\.eu\\.globalpay\\.com$"],
193
- encoding: 'json',
194
- passthroughHeaders: [/^authorization$/i, /^x-gp-version$/i],
195
- },
196
- ];
9
+ // Pinned fallback generated from the single shared processor catalog.
10
+ import { BUILTIN_REGISTRY } from './builtin-registry.generated.js';
11
+ export { BUILTIN_REGISTRY };
197
12
  export function findRecognizer(url, registry = BUILTIN_REGISTRY) {
198
13
  // Match the way the SERVER does, on the PARSED url — never as a substring of
199
14
  // the raw string. `match` is unanchored, so a raw-string test classifies any
@@ -29,14 +29,10 @@ export interface Substitutions {
29
29
  */
30
30
  remove?: string[];
31
31
  }
32
- /** The paused body could not take the substitutions. Not terminal: the next request may differ. */
33
- export declare class SubstitutionError extends Error {
32
+ declare class SubstitutionErrorShape extends Error {
34
33
  constructor(message: string);
35
34
  }
36
- /**
37
- * Return the paused JSON body with `sub.fields` written over the same-named
38
- * string members of the object at `sub.at`. Throws SubstitutionError when the
39
- * body is not JSON, the path does not lead to an object, or any field is not
40
- * already a string there.
41
- */
42
- export declare function substituteEncryptedFields(body: string, sub: Substitutions): string;
35
+ export type SubstitutionError = SubstitutionErrorShape;
36
+ export declare const SubstitutionError: typeof SubstitutionErrorShape;
37
+ export declare const substituteEncryptedFields: (body: string, sub: Substitutions) => string;
38
+ export {};
@@ -16,72 +16,8 @@
16
16
  * and Adyen refuses the mismatch), and a body that does not carry the fields
17
17
  * is refused rather than guessed at.
18
18
  */
19
- /** The paused body could not take the substitutions. Not terminal: the next request may differ. */
20
- export class SubstitutionError extends Error {
21
- constructor(message) {
22
- super(message);
23
- this.name = 'SubstitutionError';
24
- }
25
- }
26
- const FORBIDDEN_KEYS = new Set(['__proto__', 'constructor', 'prototype']);
27
- function isPlainObject(v) {
28
- return !!v && typeof v === 'object' && !Array.isArray(v);
29
- }
30
- /**
31
- * Return the paused JSON body with `sub.fields` written over the same-named
32
- * string members of the object at `sub.at`. Throws SubstitutionError when the
33
- * body is not JSON, the path does not lead to an object, or any field is not
34
- * already a string there.
35
- */
36
- export function substituteEncryptedFields(body, sub) {
37
- if (!sub || sub.encoding !== 'json') {
38
- throw new SubstitutionError(`unsupported substitution encoding: ${String(sub?.encoding)}`);
39
- }
40
- if (typeof sub.at !== 'string' || !sub.at)
41
- throw new SubstitutionError('substitutions name no path');
42
- if (!isPlainObject(sub.fields) || Object.keys(sub.fields).length === 0) {
43
- throw new SubstitutionError('substitutions carry no fields');
44
- }
45
- let parsed;
46
- try {
47
- parsed = JSON.parse(body);
48
- }
49
- catch {
50
- throw new SubstitutionError('the paused body is not JSON');
51
- }
52
- if (!isPlainObject(parsed))
53
- throw new SubstitutionError('the paused body is not a JSON object');
54
- let node = parsed;
55
- for (const step of sub.at.split('.')) {
56
- if (!step || FORBIDDEN_KEYS.has(step))
57
- throw new SubstitutionError(`refusing substitution path ${sub.at}`);
58
- const next = node[step];
59
- if (!isPlainObject(next))
60
- throw new SubstitutionError(`the paused body has no object at ${sub.at}`);
61
- node = next;
62
- }
63
- for (const [key, value] of Object.entries(sub.fields)) {
64
- if (FORBIDDEN_KEYS.has(key))
65
- throw new SubstitutionError(`refusing substitution field ${key}`);
66
- if (typeof value !== 'string' || value.length === 0)
67
- throw new SubstitutionError(`substitution for ${key} is not a string`);
68
- if (typeof node[key] !== 'string')
69
- throw new SubstitutionError(`the paused body has no string field ${sub.at}.${key}`);
70
- }
71
- const remove = sub.remove ?? [];
72
- if (!Array.isArray(remove))
73
- throw new SubstitutionError('substitutions.remove is not a list');
74
- for (const key of remove) {
75
- if (typeof key !== 'string' || !key || FORBIDDEN_KEYS.has(key))
76
- throw new SubstitutionError(`refusing removal of ${String(key)}`);
77
- if (key in sub.fields)
78
- throw new SubstitutionError(`substitution field ${key} is also listed for removal`);
79
- }
80
- // Only past every check, so a refused body is never half-written.
81
- for (const [key, value] of Object.entries(sub.fields))
82
- node[key] = value;
83
- for (const key of remove)
84
- if (Object.prototype.hasOwnProperty.call(node, key))
85
- delete node[key];
86
- return JSON.stringify(parsed);
87
- }
19
+ // The implementation is bundled from the internal core; the published SDK
20
+ // keeps its existing public types and does not require another package at runtime.
21
+ import { substituteEncryptedFields as sharedSubstitute, SubstitutionError as SharedSubstitutionError, } from './substitutions.generated.js';
22
+ export const SubstitutionError = SharedSubstitutionError;
23
+ export const substituteEncryptedFields = sharedSubstitute;
@@ -0,0 +1,10 @@
1
+ declare var SubstitutionError: {
2
+ new (message: any): {
3
+ name: string;
4
+ message: string;
5
+ stack?: string;
6
+ cause?: unknown;
7
+ };
8
+ };
9
+ declare function substituteEncryptedFields(body: any, sub: any): string;
10
+ export { SubstitutionError, substituteEncryptedFields };
@@ -0,0 +1,66 @@
1
+ // @ts-nocheck
2
+ // Generated from @agent-cards/payment-core. Do not edit.
3
+ // artifact-sha256: a74db713f1742d9bc5dc8efc78b364d2a69b255ee8da7f98345242f02114e6ce
4
+ // src/substitutions.js
5
+ var SubstitutionError = class extends Error {
6
+ constructor(message) {
7
+ super(message);
8
+ this.name = "SubstitutionError";
9
+ }
10
+ };
11
+ var FORBIDDEN_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
12
+ function isPlainObject(v) {
13
+ return !!v && typeof v === "object" && !Array.isArray(v);
14
+ }
15
+ function substituteEncryptedFields(body, sub) {
16
+ if (!sub || sub.encoding !== "json") {
17
+ throw new SubstitutionError(`unsupported substitution encoding: ${String(sub?.encoding)}`);
18
+ }
19
+ if (typeof sub.at !== "string" || !sub.at)
20
+ throw new SubstitutionError("substitutions name no path");
21
+ if (!isPlainObject(sub.fields) || Object.keys(sub.fields).length === 0) {
22
+ throw new SubstitutionError("substitutions carry no fields");
23
+ }
24
+ let parsed;
25
+ try {
26
+ parsed = JSON.parse(body);
27
+ }
28
+ catch {
29
+ throw new SubstitutionError("the paused body is not JSON");
30
+ }
31
+ if (!isPlainObject(parsed))
32
+ throw new SubstitutionError("the paused body is not a JSON object");
33
+ let node = parsed;
34
+ for (const step of sub.at.split(".")) {
35
+ if (!step || FORBIDDEN_KEYS.has(step))
36
+ throw new SubstitutionError(`refusing substitution path ${sub.at}`);
37
+ const next = node[step];
38
+ if (!isPlainObject(next))
39
+ throw new SubstitutionError(`the paused body has no object at ${sub.at}`);
40
+ node = next;
41
+ }
42
+ for (const [key, value] of Object.entries(sub.fields)) {
43
+ if (FORBIDDEN_KEYS.has(key))
44
+ throw new SubstitutionError(`refusing substitution field ${key}`);
45
+ if (typeof value !== "string" || value.length === 0)
46
+ throw new SubstitutionError(`substitution for ${key} is not a string`);
47
+ if (typeof node[key] !== "string")
48
+ throw new SubstitutionError(`the paused body has no string field ${sub.at}.${key}`);
49
+ }
50
+ const remove = sub.remove ?? [];
51
+ if (!Array.isArray(remove))
52
+ throw new SubstitutionError("substitutions.remove is not a list");
53
+ for (const key of remove) {
54
+ if (typeof key !== "string" || !key || FORBIDDEN_KEYS.has(key))
55
+ throw new SubstitutionError(`refusing removal of ${String(key)}`);
56
+ if (key in sub.fields)
57
+ throw new SubstitutionError(`substitution field ${key} is also listed for removal`);
58
+ }
59
+ for (const [key, value] of Object.entries(sub.fields))
60
+ node[key] = value;
61
+ for (const key of remove)
62
+ if (Object.prototype.hasOwnProperty.call(node, key))
63
+ delete node[key];
64
+ return JSON.stringify(parsed);
65
+ }
66
+ export { SubstitutionError, substituteEncryptedFields };
@@ -0,0 +1,21 @@
1
+ // Run from an installed SDK or from this package after npm run build.
2
+ // Optional argument: observations JSON path. Uses this SDK's bundled capabilities.
3
+ import { readFile } from 'node:fs/promises';
4
+ import {
5
+ normalizeCheckoutSignals,
6
+ assessCheckoutSupport,
7
+ } from '@agent-cards/checkout/preflight';
8
+
9
+ try {
10
+ const observations = JSON.parse(await readFile(
11
+ process.argv[2] ?? new URL('./stripe-script.observations.json', import.meta.url), 'utf8',
12
+ ));
13
+ const snapshot = normalizeCheckoutSignals(observations);
14
+ const result = assessCheckoutSupport(snapshot, { scenario: 'one_time' });
15
+
16
+ // Read support_scope and checkout_flow_status with status before continuing.
17
+ console.log(JSON.stringify({ snapshot, result }, null, 2));
18
+ } catch {
19
+ console.error('Could not assess the observations. Check the JSON file against PREFLIGHT.md.');
20
+ process.exitCode = 1;
21
+ }
@@ -0,0 +1,30 @@
1
+ // Run from an installed SDK or from this package after npm run build.
2
+ // Optional arguments: observations JSON path, profile JSON path, adapter version.
3
+ import { readFile } from 'node:fs/promises';
4
+ import {
5
+ normalizeCheckoutSignals,
6
+ assessCheckoutSupport,
7
+ } from '@agent-cards/checkout/preflight';
8
+
9
+ const [observationsPath, profilePath, adapterVersion = 'example-native-adapter'] = process.argv.slice(2);
10
+
11
+ try {
12
+ const observations = JSON.parse(await readFile(
13
+ observationsPath ?? new URL('./stripe-script.observations.json', import.meta.url), 'utf8',
14
+ ));
15
+ const profile = JSON.parse(await readFile(
16
+ profilePath ?? new URL('./kernel-profile.empty.json', import.meta.url), 'utf8',
17
+ ));
18
+ const snapshot = normalizeCheckoutSignals(observations);
19
+ const result = assessCheckoutSupport(snapshot, {
20
+ scenario: 'one_time',
21
+ integration: { id: 'kernel_native', version: adapterVersion },
22
+ profile,
23
+ });
24
+
25
+ // Normalized snapshots contain reviewed rule IDs rather than raw asset URLs.
26
+ console.log(JSON.stringify({ snapshot, result }, null, 2));
27
+ } catch {
28
+ console.error('Could not assess the observations. Check the JSON files and adapter version against PREFLIGHT.md.');
29
+ process.exitCode = 1;
30
+ }
@@ -0,0 +1,44 @@
1
+ // Read an existing checkout tab. Does not navigate, click, fill, or attach payment interception.
2
+ // Install playwright-core in the consuming project before running this example.
3
+ import { readFile } from 'node:fs/promises';
4
+ import { chromium } from 'playwright-core';
5
+ import { inspectCheckout } from '@agent-cards/checkout/playwright';
6
+
7
+ let browser;
8
+ try {
9
+ if (!process.env.CHECKOUT_CDP_URL) throw new Error('missing_connection');
10
+ const integration = process.env.CHECKOUT_INTEGRATION ?? 'kernel_native';
11
+ if (!['kernel_native', 'direct_sdk'].includes(integration)) throw new Error('invalid_integration');
12
+ const pageIndex = Number(process.env.CHECKOUT_PAGE_INDEX ?? 0);
13
+ if (!Number.isSafeInteger(pageIndex) || pageIndex < 0) throw new Error('invalid_page_index');
14
+
15
+ const profile = integration === 'kernel_native' && process.env.CHECKOUT_PROFILE
16
+ ? JSON.parse(await readFile(process.env.CHECKOUT_PROFILE, 'utf8'))
17
+ : undefined;
18
+ const options = integration === 'direct_sdk'
19
+ ? { scenario: 'one_time' }
20
+ : {
21
+ scenario: 'one_time',
22
+ integration: { id: 'kernel_native', version: process.env.CHECKOUT_ADAPTER_VERSION ?? null },
23
+ profile,
24
+ };
25
+
26
+ browser = await chromium.connectOverCDP(process.env.CHECKOUT_CDP_URL, { timeout: 15_000 });
27
+ const page = browser.contexts()[0]?.pages()[pageIndex];
28
+ if (!page) throw new Error('page_not_found');
29
+
30
+ const result = await inspectCheckout(page, options);
31
+ console.log(JSON.stringify(result, null, 2));
32
+ } catch {
33
+ // A browser connection URL can contain a credential. Do not print connection errors.
34
+ console.error('Could not inspect the checkout. Check the CDP connection, page index, integration, and optional profile.');
35
+ process.exitCode = 1;
36
+ } finally {
37
+ if (browser) {
38
+ // Disconnect this CDP client; the existing agent keeps its browser session.
39
+ await browser.close().catch(() => {
40
+ console.error('Could not disconnect the inspection client. Close this local process before inspecting again.');
41
+ process.exitCode = 1;
42
+ });
43
+ }
44
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "schema_version": 1,
3
+ "profile_version": "kernel-template-1",
4
+ "catalog_version": "2026-09-11.2",
5
+ "integration": {
6
+ "id": "kernel_native",
7
+ "version": "example-native-adapter"
8
+ },
9
+ "entries": [],
10
+ "processor_entries": []
11
+ }
@@ -0,0 +1,23 @@
1
+ {
2
+ "signals": [
3
+ {
4
+ "kind": "document",
5
+ "url": "https://www.mollie.com/checkout/credit-card/session/synthetic-example",
6
+ "frame_id": 0,
7
+ "parent_frame_id": null,
8
+ "visible": true
9
+ },
10
+ {
11
+ "kind": "script",
12
+ "url": "https://js.mollie.com/v2/mollie.js",
13
+ "frame_id": 0,
14
+ "parent_frame_id": null,
15
+ "visible": null
16
+ }
17
+ ],
18
+ "observation": {
19
+ "complete": true,
20
+ "truncated": false,
21
+ "reasons": []
22
+ }
23
+ }
@@ -0,0 +1,103 @@
1
+ {
2
+ "snapshot": {
3
+ "schema_version": 1,
4
+ "catalog_version": "2026-09-11.2",
5
+ "signals": [
6
+ {
7
+ "rule_id": "mollie-hosted-credit-card",
8
+ "kind": "document",
9
+ "frame_id": 0,
10
+ "parent_frame_id": null,
11
+ "visible": true
12
+ },
13
+ {
14
+ "rule_id": "mollie-components-script",
15
+ "kind": "script",
16
+ "frame_id": 0,
17
+ "parent_frame_id": null,
18
+ "visible": null
19
+ },
20
+ {
21
+ "rule_id": "mollie-components-v2",
22
+ "kind": "script",
23
+ "frame_id": 0,
24
+ "parent_frame_id": null,
25
+ "visible": null
26
+ }
27
+ ],
28
+ "observation": {
29
+ "complete": true,
30
+ "truncated": false,
31
+ "reasons": []
32
+ }
33
+ },
34
+ "result": {
35
+ "schema_version": 1,
36
+ "catalog_version": "2026-09-11.2",
37
+ "integration": {
38
+ "id": "kernel_native",
39
+ "version": "example-native-adapter"
40
+ },
41
+ "scenario": "one_time",
42
+ "scenario_defaulted": false,
43
+ "psp": "mollie",
44
+ "flow": "mollie.hosted_components",
45
+ "mode": "token",
46
+ "status": "unknown",
47
+ "support_scope": null,
48
+ "checkout_flow_status": "identified",
49
+ "candidates": [
50
+ {
51
+ "psp": "mollie",
52
+ "relevance": "active",
53
+ "evidence": [
54
+ {
55
+ "rule_id": "mollie-hosted-credit-card",
56
+ "kind": "document",
57
+ "frame_id": 0,
58
+ "parent_frame_id": null,
59
+ "visible": true,
60
+ "origin": "https://www.mollie.com",
61
+ "path_pattern": "/checkout/credit-card/session/:session"
62
+ },
63
+ {
64
+ "rule_id": "mollie-components-script",
65
+ "kind": "script",
66
+ "frame_id": 0,
67
+ "parent_frame_id": null,
68
+ "visible": null,
69
+ "origin": "https://js.mollie.com",
70
+ "path_pattern": "/v2/mollie.js"
71
+ },
72
+ {
73
+ "rule_id": "mollie-components-v2",
74
+ "kind": "script",
75
+ "frame_id": 0,
76
+ "parent_frame_id": null,
77
+ "visible": null,
78
+ "origin": "https://js.mollie.com",
79
+ "path_pattern": "^/v2/mollie\\.js$"
80
+ }
81
+ ]
82
+ }
83
+ ],
84
+ "reason_codes": [
85
+ "integration_flow_undeclared"
86
+ ],
87
+ "limitations": [
88
+ "The payment request must match the supported Mollie Components card-token format.",
89
+ "Authentication and merchant confirmation must finish in the merchant browser.",
90
+ "Merchant configuration, card eligibility and processor approval still determine whether the purchase succeeds."
91
+ ],
92
+ "missing_evidence": [
93
+ "integration_flow_capability"
94
+ ],
95
+ "observation": {
96
+ "complete": true,
97
+ "truncated": false,
98
+ "reasons": []
99
+ },
100
+ "scope": "preflight_hint",
101
+ "merchant_confirmation_required": true
102
+ }
103
+ }