@agent-cards/checkout 0.18.0 → 0.21.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/README.md +6 -669
- package/cdp.d.ts +1 -0
- package/cdp.js +2 -0
- package/index.d.ts +1 -0
- package/index.js +2 -0
- package/package.json +33 -33
- package/playwright.d.ts +1 -0
- package/playwright.js +2 -0
- package/preflight.d.ts +1 -0
- package/preflight.js +2 -0
- package/CHANGELOG.md +0 -124
- package/PREFLIGHT.md +0 -308
- package/dist/adyen.generated.d.ts +0 -24
- package/dist/adyen.generated.js +0 -64
- package/dist/attachment.d.ts +0 -11
- package/dist/attachment.js +0 -50
- package/dist/braintree.d.ts +0 -2
- package/dist/braintree.generated.d.ts +0 -10
- package/dist/braintree.generated.js +0 -302
- package/dist/braintree.js +0 -2
- package/dist/builtin-registry.generated.d.ts +0 -2
- package/dist/builtin-registry.generated.js +0 -1
- package/dist/card-fields.generated.d.ts +0 -3
- package/dist/card-fields.generated.js +0 -46
- package/dist/cdp.d.ts +0 -189
- package/dist/cdp.js +0 -2194
- package/dist/checkout-com.generated.d.ts +0 -4
- package/dist/checkout-com.generated.js +0 -183
- package/dist/client.d.ts +0 -618
- package/dist/client.js +0 -1251
- package/dist/hosted-form.d.ts +0 -44
- package/dist/hosted-form.js +0 -78
- package/dist/index.d.ts +0 -13
- package/dist/index.js +0 -6
- package/dist/lifecycle.d.ts +0 -165
- package/dist/lifecycle.js +0 -370
- package/dist/mercado-checkout.d.ts +0 -20
- package/dist/mercado-checkout.generated.d.ts +0 -52
- package/dist/mercado-checkout.generated.js +0 -198
- package/dist/mercado-checkout.js +0 -108
- package/dist/owned-shop.generated.d.ts +0 -24
- package/dist/owned-shop.generated.js +0 -108
- package/dist/paysafe.generated.d.ts +0 -12
- package/dist/paysafe.generated.js +0 -87
- package/dist/playwright.d.ts +0 -3
- package/dist/playwright.js +0 -3
- package/dist/preflight-capabilities.generated.d.ts +0 -1253
- package/dist/preflight-capabilities.generated.js +0 -1929
- package/dist/preflight-catalog.json +0 -4595
- package/dist/preflight-playwright.d.ts +0 -34
- package/dist/preflight-playwright.js +0 -355
- package/dist/preflight-schemas.json +0 -1110
- package/dist/preflight.d.ts +0 -1
- package/dist/preflight.generated.d.ts +0 -1965
- package/dist/preflight.generated.js +0 -556
- package/dist/preflight.js +0 -2
- package/dist/preparation.d.ts +0 -31
- package/dist/preparation.js +0 -164
- package/dist/prepared-processor.d.ts +0 -10
- package/dist/prepared-processor.js +0 -122
- package/dist/recurly.generated.d.ts +0 -1
- package/dist/recurly.generated.js +0 -87
- package/dist/registry.d.ts +0 -76
- package/dist/registry.js +0 -296
- package/dist/spreedly.generated.d.ts +0 -10
- package/dist/spreedly.generated.js +0 -332
- package/dist/stripe-checkout.d.ts +0 -81
- package/dist/stripe-checkout.generated.d.ts +0 -82
- package/dist/stripe-checkout.generated.js +0 -1004
- package/dist/stripe-checkout.js +0 -140
- package/dist/substitute.d.ts +0 -38
- package/dist/substitute.js +0 -23
- package/dist/substitutions.generated.d.ts +0 -10
- package/dist/substitutions.generated.js +0 -66
- package/examples/existing-browser.mjs +0 -63
- package/examples/preflight/classify-direct.mjs +0 -21
- package/examples/preflight/classify-kernel.mjs +0 -30
- package/examples/preflight/inspect-browser.mjs +0 -44
- package/examples/preflight/kernel-native/README.md +0 -112
- package/examples/preflight/kernel-native/documented-adapters.json +0 -113
- package/examples/preflight/kernel-native/inventory.json +0 -233
- package/examples/preflight/kernel-native/qualification.mjs +0 -182
- package/examples/preflight/kernel-profile.empty.json +0 -11
- package/examples/preflight/mollie-hosted.observations.json +0 -23
- package/examples/preflight/mollie-hosted.result.json +0 -103
- package/examples/preflight/stripe-script.direct.result.json +0 -92
- package/examples/preflight/stripe-script.observations.json +0 -16
- package/examples/preflight/stripe-script.result.json +0 -87
package/dist/stripe-checkout.js
DELETED
|
@@ -1,140 +0,0 @@
|
|
|
1
|
-
import { classifyStripeCheckoutRequest, createStripeCheckoutTokenizationStub, encodeStripeCheckoutContext, isStripeCheckoutEndpoint, STRIPE_CHECKOUT_CONTEXT_HEADER, captureStripeCheckoutBilling, attachStripeCheckoutBilling, validateStripeCheckoutIdentity, stripeCheckoutValidationCode, STRIPE_CHECKOUT_VALIDATION_CODES, } from './stripe-checkout.generated.js';
|
|
2
|
-
class StripeCheckoutRejectionError extends Error {
|
|
3
|
-
stage;
|
|
4
|
-
reason;
|
|
5
|
-
phase;
|
|
6
|
-
validationCode;
|
|
7
|
-
constructor(message, stage, reason, phase, validationCode) {
|
|
8
|
-
super(message);
|
|
9
|
-
this.stage = stage;
|
|
10
|
-
this.reason = reason;
|
|
11
|
-
this.phase = phase;
|
|
12
|
-
this.validationCode = validationCode;
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
export function stripeCheckoutReadinessError(reason) {
|
|
16
|
-
return new StripeCheckoutRejectionError('checkout_interception_not_ready', 'readiness', reason);
|
|
17
|
-
}
|
|
18
|
-
/** A valid competing request must not retire the request that already claimed this step. */
|
|
19
|
-
export class StripeCheckoutClaimConflictError extends StripeCheckoutRejectionError {
|
|
20
|
-
constructor(phase) {
|
|
21
|
-
super(phase === 'tokenization' ? 'Native Stripe Checkout preparation is already claimed.'
|
|
22
|
-
: 'Native Stripe Checkout confirmation is already claimed.', 'claim', phase === 'tokenization' ? 'duplicate_tokenization' : 'duplicate_confirmation', phase);
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
/** The two native browser requests belong to one attachment and one grant.
|
|
26
|
-
* Processor rules and form transformations live in the generated shared core.
|
|
27
|
-
* The local stub contains no selected-card data and starts no authorization.
|
|
28
|
-
*/
|
|
29
|
-
export class StripeCheckoutGate {
|
|
30
|
-
state = 'fresh';
|
|
31
|
-
billing;
|
|
32
|
-
binding;
|
|
33
|
-
constructor(options) {
|
|
34
|
-
if (!options.stripeCheckout)
|
|
35
|
-
return;
|
|
36
|
-
const { sessionId, publishableKey, environment } = options.stripeCheckout;
|
|
37
|
-
validateStripeCheckoutIdentity({ environment, sessionId, publishableKey });
|
|
38
|
-
if (options.executionMode !== 'autopilot' || !/^apg_[A-Za-z0-9_-]{1,128}(?![\s\S])/.test(options.grantId ?? '')
|
|
39
|
-
|| !Number.isSafeInteger(options.amount) || Number(options.amount) <= 0 || options.currency !== 'usd')
|
|
40
|
-
throw new Error('Native Stripe Checkout requires a mode-bound session, grant and numeric USD amount.');
|
|
41
|
-
this.binding = Object.freeze({ environment, sessionId, publishableKey, amountCents: options.amount,
|
|
42
|
-
syntheticPaymentMethodId: `pm_agentcard_checkout_${crypto.randomUUID().replaceAll('-', '')}` });
|
|
43
|
-
}
|
|
44
|
-
matches(url) { return !!this.binding && isStripeCheckoutEndpoint(url); }
|
|
45
|
-
isEnabled() { return !!this.binding; }
|
|
46
|
-
isPrepared() { return this.state !== 'fresh'; }
|
|
47
|
-
invalidate() { this.state = 'stopped'; this.billing = undefined; }
|
|
48
|
-
/** Observability only: this endpoint label never participates in a payment decision. */
|
|
49
|
-
describeRejection(error, rawUrl, stage, phase = 'unknown') {
|
|
50
|
-
let family = 'other';
|
|
51
|
-
try {
|
|
52
|
-
const url = new URL(rawUrl);
|
|
53
|
-
if (url.origin === 'https://api.stripe.com' && url.pathname === '/v1/payment_methods')
|
|
54
|
-
family = 'payment_methods';
|
|
55
|
-
else if (isStripeCheckoutEndpoint(rawUrl))
|
|
56
|
-
family = 'payment_page_confirm';
|
|
57
|
-
}
|
|
58
|
-
catch { /* No untrusted URL or parser error leaves this method. */ }
|
|
59
|
-
const fallback = {
|
|
60
|
-
request_read: 'request_unavailable', classification: 'request_validation_failed', claim: 'preparation_unavailable',
|
|
61
|
-
readiness: 'readiness_unavailable', document: 'document_unavailable', stub_response: 'stub_response_failed',
|
|
62
|
-
};
|
|
63
|
-
const rejection = error instanceof StripeCheckoutRejectionError ? error : undefined;
|
|
64
|
-
const reason = rejection?.reason ?? fallback[stage];
|
|
65
|
-
return { version: 1, processor: 'stripe', endpoint_family: family,
|
|
66
|
-
phase: rejection?.phase ?? phase, stage: rejection?.stage ?? stage, reason,
|
|
67
|
-
...(reason === 'request_validation_failed' ? { validation_code: rejection?.validationCode ?? STRIPE_CHECKOUT_VALIDATION_CODES.unclassified } : {}),
|
|
68
|
-
gate_state: this.state, disposition: error instanceof StripeCheckoutClaimConflictError ? 'active_claim_preserved' : 'checkout_stopped' };
|
|
69
|
-
}
|
|
70
|
-
assertClaim(step) {
|
|
71
|
-
if (this.state !== (step.phase === 'tokenization' ? 'stubbed' : 'submitted'))
|
|
72
|
-
throw new StripeCheckoutRejectionError('Native Stripe Checkout preparation is unavailable.', 'claim', 'claim_invalidated', step.phase);
|
|
73
|
-
}
|
|
74
|
-
/** Check the actual top-level hosted document before preparing either step. */
|
|
75
|
-
assertDocument(raw) {
|
|
76
|
-
if (!this.binding)
|
|
77
|
-
return;
|
|
78
|
-
const url = new URL(raw);
|
|
79
|
-
if (url.origin !== 'https://checkout.stripe.com' || url.username || url.password
|
|
80
|
-
|| ![`/c/pay/${this.binding.sessionId}`, `/pay/${this.binding.sessionId}`, `/g/pay/${this.binding.sessionId}`, `/f/pay/${this.binding.sessionId}`].includes(url.pathname)) {
|
|
81
|
-
this.invalidate();
|
|
82
|
-
throw new StripeCheckoutRejectionError('Native Stripe Checkout document changed.', 'document', 'document_changed');
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
claim(request) {
|
|
86
|
-
if (!this.binding)
|
|
87
|
-
return null;
|
|
88
|
-
if (request.method.toUpperCase() !== 'POST')
|
|
89
|
-
return null;
|
|
90
|
-
let phase;
|
|
91
|
-
try {
|
|
92
|
-
phase = classifyStripeCheckoutRequest(request, this.binding);
|
|
93
|
-
}
|
|
94
|
-
catch (error) {
|
|
95
|
-
throw new StripeCheckoutRejectionError('stripe_checkout_rejected', 'classification', 'request_validation_failed', 'unknown', stripeCheckoutValidationCode(error));
|
|
96
|
-
}
|
|
97
|
-
if (!phase)
|
|
98
|
-
return null;
|
|
99
|
-
const diagnosticPhase = phase.phase === 'tokenization' ? 'tokenization' : phase.phase === 'final' ? 'final' : 'unknown';
|
|
100
|
-
if (Object.keys(request.headers).some(name => name.toLowerCase() === STRIPE_CHECKOUT_CONTEXT_HEADER))
|
|
101
|
-
throw new StripeCheckoutRejectionError('Native Stripe Checkout already carries a context.', 'claim', 'context_already_present', diagnosticPhase);
|
|
102
|
-
if (phase.phase === 'tokenization') {
|
|
103
|
-
if (this.state === 'stubbed' || this.state === 'submitted')
|
|
104
|
-
throw new StripeCheckoutClaimConflictError('tokenization');
|
|
105
|
-
if (this.state !== 'fresh')
|
|
106
|
-
throw new StripeCheckoutRejectionError('Native Stripe Checkout preparation is unavailable.', 'claim', 'preparation_unavailable', 'tokenization');
|
|
107
|
-
this.state = 'stopped';
|
|
108
|
-
let response;
|
|
109
|
-
try {
|
|
110
|
-
this.billing = captureStripeCheckoutBilling(request, this.binding);
|
|
111
|
-
response = createStripeCheckoutTokenizationStub(request, this.binding);
|
|
112
|
-
}
|
|
113
|
-
catch (error) {
|
|
114
|
-
const code = stripeCheckoutValidationCode(error);
|
|
115
|
-
if (code !== STRIPE_CHECKOUT_VALIDATION_CODES.unclassified)
|
|
116
|
-
throw new StripeCheckoutRejectionError('stripe_checkout_rejected', 'classification', 'request_validation_failed', 'tokenization', code);
|
|
117
|
-
throw new StripeCheckoutRejectionError('stripe_checkout_rejected', 'stub_response', 'stub_response_failed', 'tokenization');
|
|
118
|
-
}
|
|
119
|
-
this.state = 'stubbed';
|
|
120
|
-
return { phase: 'tokenization', response };
|
|
121
|
-
}
|
|
122
|
-
if (phase.phase === 'final' && this.state === 'submitted')
|
|
123
|
-
throw new StripeCheckoutClaimConflictError('final');
|
|
124
|
-
if (phase.phase !== 'final' || this.state !== 'stubbed')
|
|
125
|
-
throw new StripeCheckoutRejectionError('Native Stripe Checkout has no matching prepared request.', 'claim', 'preparation_missing', diagnosticPhase);
|
|
126
|
-
let final;
|
|
127
|
-
try {
|
|
128
|
-
final = attachStripeCheckoutBilling(request, this.billing, this.binding);
|
|
129
|
-
}
|
|
130
|
-
catch (error) {
|
|
131
|
-
this.invalidate();
|
|
132
|
-
throw new StripeCheckoutRejectionError('stripe_checkout_rejected', 'classification', 'request_validation_failed', 'final', stripeCheckoutValidationCode(error));
|
|
133
|
-
}
|
|
134
|
-
this.state = 'submitted';
|
|
135
|
-
this.billing = undefined;
|
|
136
|
-
return { phase: 'final', request: { ...final, headers: { ...final.headers,
|
|
137
|
-
[STRIPE_CHECKOUT_CONTEXT_HEADER]: encodeStripeCheckoutContext({ version: 1,
|
|
138
|
-
session_id: this.binding.sessionId, synthetic_payment_method_id: this.binding.syntheticPaymentMethodId }) } } };
|
|
139
|
-
}
|
|
140
|
-
}
|
package/dist/substitute.d.ts
DELETED
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The cse half of a replay: put the vault's ciphertext into the paused body.
|
|
3
|
-
*
|
|
4
|
-
* On a client-side-encrypted processor (Adyen) the cardholder's device does
|
|
5
|
-
* not call the processor. It encrypts the card under the processor's public
|
|
6
|
-
* key and reports the ciphertext; the paused request then continues from the
|
|
7
|
-
* agent's own browser with those blobs in place of the dummy ones. Everything
|
|
8
|
-
* else in the body (session data, risk data, browser info, origin) stays the
|
|
9
|
-
* browser's own, so the processor's origin allowlist and risk checks see the
|
|
10
|
-
* request they expected.
|
|
11
|
-
*
|
|
12
|
-
* Deliberately narrow: only keys that ALREADY exist as strings at the named
|
|
13
|
-
* path are overwritten. A key is never added, a sibling is touched only when
|
|
14
|
-
* the API names it in `remove` (Adyen's `brand`, which adyen-web derived from
|
|
15
|
-
* the dummy digits the agent typed: left in place it describes the wrong card
|
|
16
|
-
* and Adyen refuses the mismatch), and a body that does not carry the fields
|
|
17
|
-
* is refused rather than guessed at.
|
|
18
|
-
*/
|
|
19
|
-
export interface Substitutions {
|
|
20
|
-
encoding: 'json';
|
|
21
|
-
/** Dotted path to the object that holds the fields, e.g. `paymentMethod`. */
|
|
22
|
-
at: string;
|
|
23
|
-
/** Field name -> ciphertext, exactly as the vault reported it. */
|
|
24
|
-
fields: Record<string, string>;
|
|
25
|
-
/**
|
|
26
|
-
* Sibling keys at `at` to delete when present (never an error when absent):
|
|
27
|
-
* values the page SDK derived from the agent's dummy card that would
|
|
28
|
-
* contradict the swapped ciphertext. Served by the API per processor.
|
|
29
|
-
*/
|
|
30
|
-
remove?: string[];
|
|
31
|
-
}
|
|
32
|
-
declare class SubstitutionErrorShape extends Error {
|
|
33
|
-
constructor(message: string);
|
|
34
|
-
}
|
|
35
|
-
export type SubstitutionError = SubstitutionErrorShape;
|
|
36
|
-
export declare const SubstitutionError: typeof SubstitutionErrorShape;
|
|
37
|
-
export declare const substituteEncryptedFields: (body: string, sub: Substitutions) => string;
|
|
38
|
-
export {};
|
package/dist/substitute.js
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The cse half of a replay: put the vault's ciphertext into the paused body.
|
|
3
|
-
*
|
|
4
|
-
* On a client-side-encrypted processor (Adyen) the cardholder's device does
|
|
5
|
-
* not call the processor. It encrypts the card under the processor's public
|
|
6
|
-
* key and reports the ciphertext; the paused request then continues from the
|
|
7
|
-
* agent's own browser with those blobs in place of the dummy ones. Everything
|
|
8
|
-
* else in the body (session data, risk data, browser info, origin) stays the
|
|
9
|
-
* browser's own, so the processor's origin allowlist and risk checks see the
|
|
10
|
-
* request they expected.
|
|
11
|
-
*
|
|
12
|
-
* Deliberately narrow: only keys that ALREADY exist as strings at the named
|
|
13
|
-
* path are overwritten. A key is never added, a sibling is touched only when
|
|
14
|
-
* the API names it in `remove` (Adyen's `brand`, which adyen-web derived from
|
|
15
|
-
* the dummy digits the agent typed: left in place it describes the wrong card
|
|
16
|
-
* and Adyen refuses the mismatch), and a body that does not carry the fields
|
|
17
|
-
* is refused rather than guessed at.
|
|
18
|
-
*/
|
|
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;
|
|
@@ -1,10 +0,0 @@
|
|
|
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 };
|
|
@@ -1,66 +0,0 @@
|
|
|
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 };
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
// Existing Kernel / Browserbase / custom Chromium session; does not create or close the provider's browser.
|
|
2
|
-
// Run: CHECKOUT_DRIVER=/absolute/path/to/instinct-driver.mjs node examples/existing-browser.mjs
|
|
3
|
-
// The driver owns the existing agent, its approved purchase and merchant-specific order verification.
|
|
4
|
-
import { pathToFileURL } from 'node:url';
|
|
5
|
-
import { chromium } from 'playwright-core';
|
|
6
|
-
import { VaultClient, attachToPlaywright } from '../dist/index.js';
|
|
7
|
-
|
|
8
|
-
const driverPath = process.env.CHECKOUT_DRIVER;
|
|
9
|
-
if (!driverPath?.startsWith('/')) throw new Error('CHECKOUT_DRIVER must name an absolute path to your integration module.');
|
|
10
|
-
const driver = await import(pathToFileURL(driverPath).href);
|
|
11
|
-
for (const method of ['prepareCheckout', 'submitCheckout', 'resolveMerchantResult', 'onUserAction', 'finishAfterPayment']) {
|
|
12
|
-
if (typeof driver[method] !== 'function') throw new Error(`Your driver must implement ${method}.`);
|
|
13
|
-
}
|
|
14
|
-
if (!process.env.CHECKOUT_CDP_URL) throw new Error('Set CHECKOUT_CDP_URL to your existing session connection URL. Never log it.');
|
|
15
|
-
for (const name of ['AGENTCARD_CLIENT_ID', 'AGENTCARD_CLIENT_SECRET']) {
|
|
16
|
-
if (!process.env[name]) throw new Error(`Set ${name} for your Agentcard organization.`);
|
|
17
|
-
}
|
|
18
|
-
const browser = await chromium.connectOverCDP(process.env.CHECKOUT_CDP_URL);
|
|
19
|
-
try {
|
|
20
|
-
const context = browser.contexts()[0];
|
|
21
|
-
if (!context) throw new Error('The provider returned no default browser context.');
|
|
22
|
-
const pages = context.pages();
|
|
23
|
-
const index = Number(process.env.CHECKOUT_PAGE_INDEX ?? 0);
|
|
24
|
-
const page = pages[index];
|
|
25
|
-
if (!page) throw new Error('CHECKOUT_PAGE_INDEX must select the existing agent checkout tab.');
|
|
26
|
-
|
|
27
|
-
const vault = new VaultClient({ clientId: process.env.AGENTCARD_CLIENT_ID, clientSecret: process.env.AGENTCARD_CLIENT_SECRET });
|
|
28
|
-
await vault.syncRegistry();
|
|
29
|
-
const checkout = await driver.prepareCheckout(page); // user, merchant, amountCents+currency, exact paymentEndpoints
|
|
30
|
-
const controller = await attachToPlaywright(page, {
|
|
31
|
-
...checkout,
|
|
32
|
-
vault,
|
|
33
|
-
requireMerchantResult: true,
|
|
34
|
-
onUserAction: action => driver.onUserAction(action, { page }),
|
|
35
|
-
resolveMerchantResult: state => driver.resolveMerchantResult({ page, state }),
|
|
36
|
-
// State has no PAN, replay body, approval capability, or provider connection URL.
|
|
37
|
-
onStateChange: state => console.log(JSON.stringify(state)),
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
// Must return after dispatching the click; do not block on a navigation that awaits user approval.
|
|
41
|
-
await driver.submitCheckout(page);
|
|
42
|
-
const deadline = Date.now() + 15 * 60_000;
|
|
43
|
-
while (Date.now() < deadline) {
|
|
44
|
-
let state = controller.getState();
|
|
45
|
-
if (['awaiting_merchant', 'requires_user_action', 'outcome_unknown'].includes(state.status)) {
|
|
46
|
-
state = await controller.reconcile();
|
|
47
|
-
}
|
|
48
|
-
if (state.status === 'completed' && state.orderId) {
|
|
49
|
-
await driver.finishAfterPayment({ page, orderId: state.orderId });
|
|
50
|
-
break;
|
|
51
|
-
}
|
|
52
|
-
if (['declined', 'timed_out', 'cancelled', 'unsupported', 'failed'].includes(state.status)) break;
|
|
53
|
-
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
54
|
-
}
|
|
55
|
-
if (controller.getState().status !== 'completed') {
|
|
56
|
-
controller.cancel(); // local stop only; reconcile any still-valid approval before creating a new one
|
|
57
|
-
process.exitCode = 2;
|
|
58
|
-
}
|
|
59
|
-
} finally {
|
|
60
|
-
// Disconnect this CDP client. The existing agent retains ownership of its provider session.
|
|
61
|
-
// Do not call page.close(), context.close(), or a provider session-delete API here.
|
|
62
|
-
await browser.close();
|
|
63
|
-
}
|
|
@@ -1,21 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,30 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,44 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
# Qualify Kernel native coverage
|
|
2
|
-
|
|
3
|
-
You can detect a checkout's PSP before entering card details with `@agent-cards/checkout@0.9.0`. Kernel native support needs a separate profile for the native adapter that actually runs the checkout. The direct SDK's coverage does not establish Kernel's native coverage.
|
|
4
|
-
|
|
5
|
-
No qualified native profile accompanies this kit. The inventory covers all 23 PSPs in the checkout catalog and reports `unknown` for every native runtime status. Kernel's pinned documentation lists Stripe, Shopify, Square, Recurly, and Razorpay. The other processors are unestablished, rather than explicitly unsupported. See the endpoint constraints and source hashes in [documented-adapters.json](./documented-adapters.json).
|
|
6
|
-
|
|
7
|
-
## Use the detector now
|
|
8
|
-
|
|
9
|
-
Install the published detector in your application:
|
|
10
|
-
|
|
11
|
-
```bash
|
|
12
|
-
npm install --save-exact @agent-cards/checkout@0.9.0
|
|
13
|
-
node node_modules/@agent-cards/checkout/examples/preflight/classify-kernel.mjs
|
|
14
|
-
```
|
|
15
|
-
|
|
16
|
-
The example reads synthetic Stripe observations and an empty native profile. The detector identifies Stripe and returns `unknown` for native support. The example needs no credentials and submits no payment. Follow [PREFLIGHT.md](../../../PREFLIGHT.md) to collect observations from a browser or supply normalized JSON from Kernel.
|
|
17
|
-
|
|
18
|
-
The qualification commands below are available in this source kit. The published `0.9.0` detector does not contain the new qualification commands. From the checkout package directory, build the source kit with Node.js 22 or later:
|
|
19
|
-
|
|
20
|
-
```bash
|
|
21
|
-
cd apps/agent-cards/packages/checkout
|
|
22
|
-
npm run build
|
|
23
|
-
node examples/preflight/kernel-native/qualification.mjs inventory
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
The inventory pairs each canonical PSP and mode with its documentation status. `documentation: "documented"` records a statement in the pinned source; `status: "unknown"` records the absence of qualified runtime evidence. The documentation baseline grants no permission to send card data anywhere.
|
|
27
|
-
|
|
28
|
-
The checker compares PSP membership and payment modes with the SDK's separate request registry before comparing the saved inventory. Removing a processor from both the preflight catalog and the inventory still fails this check.
|
|
29
|
-
|
|
30
|
-
When an installed SDK package includes this kit, run the commands directly from that package:
|
|
31
|
-
|
|
32
|
-
```bash
|
|
33
|
-
node node_modules/@agent-cards/checkout/examples/preflight/kernel-native/qualification.mjs inventory --check
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
## Identify the native build
|
|
37
|
-
|
|
38
|
-
Kernel must provide access to the native adapter source and build, or a test deployment whose adapter identity can be independently checked. Record the actual adapter's version and SHA-256 artifact digest in a runtime JSON file with the fields `version` and `artifact_sha256`.
|
|
39
|
-
|
|
40
|
-
Obtain the runtime identity from the deployed artifact or a trusted deployment record. Do not copy the identity from the test report, use the client SDK's npm version, or use the CLI version. Public API and client documentation do not currently supply a native adapter build identity.
|
|
41
|
-
|
|
42
|
-
Kernel must also provide a way to exercise that build with the intended payment environment. A documented sandbox processor hostname does not establish that the native card API permits sandbox testing. Agree on the supported test environment before preparing an execution run.
|
|
43
|
-
|
|
44
|
-
## Declare only tested coverage
|
|
45
|
-
|
|
46
|
-
Start from [kernel-profile.empty.json](../kernel-profile.empty.json). Set `integration.version` to the independently identified native adapter version. Keep unqualified declarations absent so that the detector returns `unknown`.
|
|
47
|
-
|
|
48
|
-
A `processor_entries` declaration covers a PSP, mode, and scenario. A flow entry in `entries` also binds the specific flow, operation, and operation version. A successful test of one checkout flow cannot justify processor-wide coverage. The reviewer must check that the tests cover the whole declaration being released.
|
|
49
|
-
|
|
50
|
-
Every declaration in this kit needs explicit `limitations`, including supported declarations. Use `unsupported` only for an established native exclusion, with evidence and a useful explanation. Shared Vault exclusions still apply; a profile cannot override them. The checker refuses supported subscription renewals and hosted-form scenarios other than `one_time`.
|
|
51
|
-
|
|
52
|
-
A profile is trusted application configuration. Never accept a profile from a merchant page. Continue to supply the actual running adapter version separately when calling `assessCheckoutSupport`.
|
|
53
|
-
|
|
54
|
-
## Test the native path
|
|
55
|
-
|
|
56
|
-
Run each declared case through Kernel's native Agentcard interception and approval path. A browser connected over CDP while the direct checkout SDK performs interception tests the direct SDK. Synthetic fixtures and unit tests verify the checker; they do not qualify a native adapter.
|
|
57
|
-
|
|
58
|
-
Each supported declaration requires the following passed checks and retained evidence:
|
|
59
|
-
|
|
60
|
-
| Check ID | What the evidence must establish |
|
|
61
|
-
| --- | --- |
|
|
62
|
-
| `native_recognition` | The identified native build recognizes the intended request layout and processor operation. |
|
|
63
|
-
| `approval_pause_resume` | The payment pauses for approval and resumes once after approval. |
|
|
64
|
-
| `processor_replay` | The approved request reaches the processor through the native path and its response returns to the merchant. |
|
|
65
|
-
| `merchant_confirmation` | The merchant confirms the intended payment outcome; approval or token creation alone is insufficient. |
|
|
66
|
-
| `unrecognized_request_passthrough` | A request outside the declared native recognition rules follows the documented unrecognized-request behavior. |
|
|
67
|
-
| `decline_or_error` | A processor refusal or error reaches the caller as a failure rather than a successful purchase. |
|
|
68
|
-
| `no_duplicate_submission` | Timeout, retry, and repeated approval handling do not produce duplicate submissions. |
|
|
69
|
-
| `authentication_or_explicit_exclusion` | Required authentication completes, or the profile states and the evidence establishes the excluded authentication cases. |
|
|
70
|
-
|
|
71
|
-
Supported `save_card` declarations also require `stored_card_consent` and `merchant_card_saved`. Supported `subscription_initial` declarations also require `stored_card_consent` and `merchant_subscription_confirmation`. An unsupported declaration requires `native_exclusion` instead of the support checks.
|
|
72
|
-
|
|
73
|
-
Retain sanitized evidence files without card details, credentials, or approval secrets. Store evidence under one directory and reference each file by a relative path and its SHA-256 digest.
|
|
74
|
-
|
|
75
|
-
## Bind the execution report
|
|
76
|
-
|
|
77
|
-
The report records `schema_version: 1`, `execution: "kernel_native"`, the adapter's `version` and `artifact_sha256`, the harness commit, and the reviewer's identity. `harness_commit` must be a full 40-character Git commit. `reviewed_by` identifies the person accountable for checking the native execution evidence.
|
|
78
|
-
|
|
79
|
-
The report's `catalog_sha256` and `profile_sha256` bind the exact catalog and profile JSON. Compute both using the exported `digest` function in `qualification.mjs`. The function sorts object keys recursively before hashing JSON; a hash of a pretty-printed file is different. Evidence digests hash the file's exact bytes.
|
|
80
|
-
|
|
81
|
-
The report contains exactly one `claims` entry per profile declaration. Each entry has a `key` and `checks`. Use these key formats:
|
|
82
|
-
|
|
83
|
-
```text
|
|
84
|
-
processor:<psp>:<mode>:<scenario>
|
|
85
|
-
flow:<flow>:<operation>:<operation_version>:<mode>:<scenario>
|
|
86
|
-
```
|
|
87
|
-
|
|
88
|
-
Each check contains `id`, `outcome: "passed"`, and `evidence: { "path": "relative-file", "sha256": "file-digest" }`. Failed, missing, duplicate, and extra checks prevent qualification. The checker also rejects evidence that escapes the evidence directory or differs from its retained digest.
|
|
89
|
-
|
|
90
|
-
## Check a candidate release
|
|
91
|
-
|
|
92
|
-
After a reviewed native execution run has produced the profile, report, independent runtime identity, and retained evidence, run:
|
|
93
|
-
|
|
94
|
-
```bash
|
|
95
|
-
node examples/preflight/kernel-native/qualification.mjs release \
|
|
96
|
-
--profile /tmp/kernel-native-validation/profile.json \
|
|
97
|
-
--report /tmp/kernel-native-validation/report.json \
|
|
98
|
-
--runtime /tmp/kernel-native-validation/runtime.json \
|
|
99
|
-
--evidence-dir /tmp/kernel-native-validation/evidence
|
|
100
|
-
```
|
|
101
|
-
|
|
102
|
-
The command prints `status: "qualified"` only when the supplied records satisfy the checks. The command does not publish a package, deploy the adapter, or install the profile in an application. Requalify after a change to the native build, profile, or catalog.
|
|
103
|
-
|
|
104
|
-
The checker verifies consistency and retained file bytes. A matching digest does not prove that a native execution happened, that the stated deployment identity is truthful, or that the evidence supports every declared case. A trusted reviewer must establish those facts before accepting the profile.
|
|
105
|
-
|
|
106
|
-
Without a complete candidate, the command exits with status `1`. The following stderr was captured by running `node examples/preflight/kernel-native/qualification.mjs release` without its required files:
|
|
107
|
-
|
|
108
|
-
```text
|
|
109
|
-
Native qualification failed. Check the arguments, catalog, build identity, profile, and retained evidence against README.md.
|
|
110
|
-
```
|
|
111
|
-
|
|
112
|
-
Supply the completed files with the command above. Keep native results `unknown` until the execution evidence and independent build identity are available.
|