@visa/cli 4.1.0-rc.29 → 4.1.0-rc.291

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 (94) hide show
  1. package/README.md +310 -46
  2. package/dist/checkout-engine/adapters/generic.d.ts +69 -0
  3. package/dist/checkout-engine/adapters/generic.js +383 -58
  4. package/dist/checkout-engine/adapters/index.d.ts +4 -1
  5. package/dist/checkout-engine/adapters/index.js +10 -3
  6. package/dist/checkout-engine/adapters/shopify.d.ts +98 -0
  7. package/dist/checkout-engine/adapters/shopify.js +744 -0
  8. package/dist/checkout-engine/amount.d.ts +17 -0
  9. package/dist/checkout-engine/amount.js +72 -0
  10. package/dist/checkout-engine/browser-launch.d.ts +9 -4
  11. package/dist/checkout-engine/browser-launch.js +19 -4
  12. package/dist/checkout-engine/browserbase-browser.d.ts +24 -0
  13. package/dist/checkout-engine/browserbase-browser.js +186 -0
  14. package/dist/checkout-engine/cli-engine.d.ts +241 -32
  15. package/dist/checkout-engine/cli-engine.js +960 -222
  16. package/dist/checkout-engine/confirmed-merchants.d.ts +31 -0
  17. package/dist/checkout-engine/confirmed-merchants.js +165 -0
  18. package/dist/checkout-engine/detect.d.ts +1 -1
  19. package/dist/checkout-engine/detect.js +6 -0
  20. package/dist/checkout-engine/evidence.d.ts +1 -1
  21. package/dist/checkout-engine/executor.d.ts +92 -4
  22. package/dist/checkout-engine/executor.js +688 -157
  23. package/dist/checkout-engine/hosted-approval.d.ts +69 -9
  24. package/dist/checkout-engine/hosted-approval.js +211 -21
  25. package/dist/checkout-engine/index.d.ts +9 -3
  26. package/dist/checkout-engine/index.js +7 -2
  27. package/dist/checkout-engine/instrument.d.ts +6 -0
  28. package/dist/checkout-engine/known-merchants.d.ts +10 -0
  29. package/dist/checkout-engine/known-merchants.js +38 -0
  30. package/dist/checkout-engine/live-fill-approval.d.ts +5 -11
  31. package/dist/checkout-engine/live-fill-approval.js +20 -34
  32. package/dist/checkout-engine/mandate/card-mandate.d.ts +6 -2
  33. package/dist/checkout-engine/mandate/card-mandate.js +10 -5
  34. package/dist/checkout-engine/mandate/mandate-ledger.d.ts +63 -23
  35. package/dist/checkout-engine/mandate/mandate-ledger.js +124 -17
  36. package/dist/checkout-engine/mandate.d.ts +8 -0
  37. package/dist/checkout-engine/mandate.js +44 -9
  38. package/dist/checkout-engine/receipt-dir.d.ts +6 -0
  39. package/dist/checkout-engine/receipt-dir.js +8 -0
  40. package/dist/checkout-engine/receipt.d.ts +56 -2
  41. package/dist/checkout-engine/receipt.js +55 -16
  42. package/dist/checkout-engine/shopify-primary-domain.d.ts +25 -0
  43. package/dist/checkout-engine/shopify-primary-domain.js +96 -0
  44. package/dist/checkout-engine/trace-handles.d.ts +8 -0
  45. package/dist/checkout-engine/trace-handles.js +12 -0
  46. package/dist/checkout-engine/types.d.ts +15 -2
  47. package/dist/checkout-engine/unresolved-charges.d.ts +34 -0
  48. package/dist/checkout-engine/unresolved-charges.js +134 -0
  49. package/dist/checkout-engine/vgs-gateway/server-mint-client.d.ts +78 -7
  50. package/dist/checkout-engine/vgs-gateway/server-mint-client.js +342 -27
  51. package/dist/checkout-engine/vgs-live-instrument.d.ts +11 -35
  52. package/dist/checkout-engine/vgs-live-instrument.js +14 -74
  53. package/dist/checkout-engine/vic-confirmation.d.ts +18 -0
  54. package/dist/checkout-engine/vic-confirmation.js +9 -3
  55. package/dist/checkout-engine/web-bot-auth.d.ts +98 -0
  56. package/dist/checkout-engine/web-bot-auth.js +218 -0
  57. package/dist/cli.js +936 -389
  58. package/dist/managed-runtime/resolve-and-update.mjs +268 -0
  59. package/dist/managed-runtime/runtime-readiness.mjs +126 -0
  60. package/dist/managed-runtime/update-and-restart.mjs +1079 -0
  61. package/dist/mcp-apps/ucp-checkout.html +280 -0
  62. package/dist/mcp-server/index.js +763 -257
  63. package/dist/merchant-ucp-mcp/index.js +7 -0
  64. package/dist/skills/pair-visa-agent/RUNTIMES.md +122 -79
  65. package/dist/skills/pair-visa-agent/SKILL.md +434 -318
  66. package/dist/skills/pair-visa-agent/scripts/__tests__/setup.test.mjs +407 -0
  67. package/dist/skills/pair-visa-agent/scripts/setup.mjs +310 -30
  68. package/dist/skills/visa-shopify-checkout/SKILL.md +122 -0
  69. package/dist/skills/visa-shopify-checkout/references/evidence-and-states.md +37 -0
  70. package/dist/skills/visa-ucp-shopping/SKILL.md +86 -0
  71. package/dist/subway-direct.mjs +1 -0
  72. package/install.ps1 +7 -6
  73. package/install.sh +3 -3
  74. package/native/bin/darwin-arm64/visa-runtime-signer +0 -0
  75. package/native/bin/darwin-x64/visa-runtime-signer +0 -0
  76. package/native/bin/linux-arm64/visa-runtime-signer +0 -0
  77. package/native/bin/linux-x64/visa-runtime-signer +0 -0
  78. package/native/bin/win32-arm64/visa-runtime-signer.exe +0 -0
  79. package/native/bin/win32-x64/visa-keychain-win.exe +0 -0
  80. package/native/bin/win32-x64/visa-runtime-signer.exe +0 -0
  81. package/package.json +33 -29
  82. package/server.json +4 -4
  83. package/dist/checkout-engine/inline-target.d.ts +0 -13
  84. package/dist/checkout-engine/inline-target.js +0 -37
  85. package/dist/checkout-engine/pay-args.d.ts +0 -14
  86. package/dist/checkout-engine/pay-args.js +0 -44
  87. package/dist/checkout-engine/pay.d.ts +0 -1
  88. package/dist/checkout-engine/pay.js +0 -13
  89. package/dist/checkout-engine/repo-env.d.ts +0 -11
  90. package/dist/checkout-engine/repo-env.js +0 -23
  91. package/dist/checkout-engine/run-live-fill.d.ts +0 -1
  92. package/dist/checkout-engine/run-live-fill.js +0 -493
  93. package/dist/checkout-engine/vgs-gateway/fetch-credential.d.mts +0 -74
  94. package/dist/checkout-engine/vgs-gateway/fetch-credential.mjs +0 -248
@@ -0,0 +1,31 @@
1
+ /** One confirmed charge, in the receipt's own terms. */
2
+ export type ConfirmedCharge = {
3
+ recordedAt: string;
4
+ amount: string;
5
+ currency: string;
6
+ /** Receipt file basename, so an operator can open the evidence log. */
7
+ receiptFile: string;
8
+ };
9
+ export type ConfirmedMerchant = {
10
+ /** The checkout page the card went through. */
11
+ url: string;
12
+ host: string;
13
+ /**
14
+ * Curated human identity (known-merchants.ts), present only when someone has
15
+ * identified who is behind this checkout URL. Hosted payment links carry an
16
+ * opaque path on the PSP's host, so without this a row names nobody.
17
+ */
18
+ name?: string;
19
+ category?: string;
20
+ website?: string;
21
+ confirmedCount: number;
22
+ lastConfirmedAt: string;
23
+ charges: ConfirmedCharge[];
24
+ };
25
+ /**
26
+ * Merchants this device has completed a real card checkout at, newest first.
27
+ *
28
+ * Never throws: a missing directory (nothing has ever been checked out here)
29
+ * and an unreadable one both read as an empty registry.
30
+ */
31
+ export declare function readConfirmedMerchants(receiptDir?: string): Promise<ConfirmedMerchant[]>;
@@ -0,0 +1,165 @@
1
+ // "Where can I use my card?", the merchant registry derived from this
2
+ // device's checkout receipts.
3
+ //
4
+ // Receipts (receipt.ts) are the only local record that a real merchant
5
+ // checkout completed on this box. This module reads them back and answers one
6
+ // question: which checkout pages has this card actually gone through?
7
+ //
8
+ // A merchant only counts when BOTH halves agree. The engine's own outcome must
9
+ // be 'confirmed' (the merchant showed a definitive success) AND the VIC
10
+ // confirmation must have posted APPROVED (the network side of the same
11
+ // purchase). Either half alone is a claim, not a completion: a 'confirmed' with
12
+ // no posted confirmation is exactly the reconciliation case receipt.ts flags,
13
+ // and a posted DECLINED is a completed report of a failure.
14
+ //
15
+ // Reading is best-effort by construction. The receipts directory is an operator
16
+ // artifact that anything on the box can touch, so an unreadable or malformed
17
+ // file is skipped rather than failing the whole read. One bad file must not
18
+ // hide every merchant behind it.
19
+ import { readdir, readFile } from 'node:fs/promises';
20
+ import { join } from 'node:path';
21
+ import { KNOWN_MERCHANT_IDENTITIES } from './known-merchants.js';
22
+ import { RECEIPT_DIR } from './receipt-dir.js';
23
+ /**
24
+ * The URL a receipt's charge happened at.
25
+ *
26
+ * A compact v2 receipt carries an exact checkout URL only when it is already in
27
+ * the curated identity map; otherwise it carries the host. Legacy v1 receipts
28
+ * keep the URL in the evidence log (the executor's first 'navigation' step is
29
+ * the page the run opened), so recover it there and fall back to the host when
30
+ * that evidence is absent or shaped differently.
31
+ */
32
+ function checkoutUrlOf(receipt) {
33
+ if (receipt.schema === 'checkout-agent-receipt/v2') {
34
+ const checkoutUrl = receipt.merchant.checkoutUrl;
35
+ return checkoutUrl && KNOWN_MERCHANT_IDENTITIES[checkoutUrl]
36
+ ? checkoutUrl
37
+ : `https://${receipt.merchant.host}/`;
38
+ }
39
+ const navigation = receipt.evidence.steps.find((step) => step?.type === 'navigation');
40
+ const data = navigation?.data;
41
+ const url = typeof data === 'object' && data !== null ? data.url : undefined;
42
+ return typeof url === 'string' && url.length > 0 ? url : `https://${receipt.merchant.host}/`;
43
+ }
44
+ function isConfirmedCompletion(receipt) {
45
+ if (receipt.schema === 'checkout-agent-receipt/v2') {
46
+ return receipt.outcome === 'confirmed' && receipt.network.confirmation === 'APPROVED';
47
+ }
48
+ return (receipt.outcome === 'confirmed' &&
49
+ receipt.vicConfirmation?.posted === true &&
50
+ receipt.vicConfirmation.transactionStatus === 'APPROVED');
51
+ }
52
+ /**
53
+ * Parse one supported receipt file, or null when its fields cannot safely feed
54
+ * the registry. Deliberately permissive about everything the grouping does not
55
+ * touch: older and newer engines only have to carry the fields read below.
56
+ */
57
+ function parseReceipt(json) {
58
+ let parsed;
59
+ try {
60
+ parsed = JSON.parse(json);
61
+ }
62
+ catch {
63
+ return null;
64
+ }
65
+ if (typeof parsed !== 'object' || parsed === null)
66
+ return null;
67
+ const receipt = parsed;
68
+ if (receipt.schema === 'checkout-agent-receipt/v1') {
69
+ const v1 = receipt;
70
+ if (typeof v1.recordedAt !== 'string')
71
+ return null;
72
+ if (typeof v1.merchant?.host !== 'string')
73
+ return null;
74
+ if (typeof v1.transaction?.amount !== 'string')
75
+ return null;
76
+ if (typeof v1.transaction?.currency !== 'string')
77
+ return null;
78
+ if (!Array.isArray(v1.evidence?.steps))
79
+ return null;
80
+ return v1;
81
+ }
82
+ if (receipt.schema === 'checkout-agent-receipt/v2') {
83
+ const v2 = receipt;
84
+ if (typeof v2.recordedAt !== 'string')
85
+ return null;
86
+ if (typeof v2.merchant?.host !== 'string')
87
+ return null;
88
+ if (v2.merchant.checkoutUrl !== null && typeof v2.merchant.checkoutUrl !== 'string')
89
+ return null;
90
+ if (typeof v2.transaction?.amount !== 'string')
91
+ return null;
92
+ if (typeof v2.transaction?.currency !== 'string')
93
+ return null;
94
+ if (v2.network?.confirmation !== null &&
95
+ v2.network?.confirmation !== 'APPROVED' &&
96
+ v2.network?.confirmation !== 'DECLINED') {
97
+ return null;
98
+ }
99
+ return v2;
100
+ }
101
+ return null;
102
+ }
103
+ /**
104
+ * Merchants this device has completed a real card checkout at, newest first.
105
+ *
106
+ * Never throws: a missing directory (nothing has ever been checked out here)
107
+ * and an unreadable one both read as an empty registry.
108
+ */
109
+ export async function readConfirmedMerchants(receiptDir = RECEIPT_DIR) {
110
+ let names;
111
+ try {
112
+ names = await readdir(receiptDir);
113
+ }
114
+ catch {
115
+ return [];
116
+ }
117
+ const byUrl = new Map();
118
+ for (const name of names) {
119
+ if (!name.endsWith('.json'))
120
+ continue;
121
+ let raw;
122
+ try {
123
+ raw = await readFile(join(receiptDir, name), 'utf8');
124
+ }
125
+ catch {
126
+ continue;
127
+ }
128
+ const receipt = parseReceipt(raw);
129
+ if (!receipt || !isConfirmedCompletion(receipt))
130
+ continue;
131
+ const url = checkoutUrlOf(receipt);
132
+ const charge = {
133
+ recordedAt: receipt.recordedAt,
134
+ amount: receipt.transaction.amount,
135
+ currency: receipt.transaction.currency,
136
+ receiptFile: name,
137
+ };
138
+ const existing = byUrl.get(url);
139
+ if (existing) {
140
+ existing.confirmedCount += 1;
141
+ existing.charges.push(charge);
142
+ if (charge.recordedAt > existing.lastConfirmedAt) {
143
+ existing.lastConfirmedAt = charge.recordedAt;
144
+ }
145
+ continue;
146
+ }
147
+ const identity = KNOWN_MERCHANT_IDENTITIES[url];
148
+ byUrl.set(url, {
149
+ url,
150
+ host: receipt.merchant.host,
151
+ // Conditional spread on purpose: an unidentified merchant carries no
152
+ // identity keys at all, rather than keys holding undefined.
153
+ ...(identity ?? {}),
154
+ confirmedCount: 1,
155
+ lastConfirmedAt: charge.recordedAt,
156
+ charges: [charge],
157
+ });
158
+ }
159
+ const merchants = [...byUrl.values()];
160
+ for (const merchant of merchants) {
161
+ merchant.charges.sort((left, right) => (left.recordedAt < right.recordedAt ? 1 : -1));
162
+ }
163
+ merchants.sort((left, right) => (left.lastConfirmedAt < right.lastConfirmedAt ? 1 : -1));
164
+ return merchants;
165
+ }
@@ -1,6 +1,6 @@
1
1
  import type { Page } from 'playwright-core';
2
2
  export type FieldSource = 'autocomplete' | 'attr-heuristic' | 'label-text' | 'iframe-psp' | 'type-inference';
3
- export type FieldRole = 'number' | 'expMonth' | 'expYear' | 'expCombined' | 'cvc' | 'name' | 'nameFirst' | 'nameLast' | 'email' | 'oneTimeCode' | 'addressLine1' | 'addressLine2' | 'city' | 'state' | 'postalCode' | 'country';
3
+ export type FieldRole = 'number' | 'expMonth' | 'expYear' | 'expCombined' | 'cvc' | 'name' | 'nameFirst' | 'nameLast' | 'email' | 'phone' | 'oneTimeCode' | 'addressLine1' | 'addressLine2' | 'city' | 'state' | 'postalCode' | 'country';
4
4
  export declare const ALL_ROLES: FieldRole[];
5
5
  export type SelectOption = {
6
6
  value: string;
@@ -21,6 +21,7 @@ export const ALL_ROLES = [
21
21
  'nameFirst',
22
22
  'nameLast',
23
23
  'email',
24
+ 'phone',
24
25
  'oneTimeCode',
25
26
  'addressLine1',
26
27
  'addressLine2',
@@ -38,6 +39,7 @@ const expMonthRe = /(exp.?month|expmonth|exp_mm|(^|[^a-z])mm([^a-z]|$)|(^|[^a-z]
38
39
  const expYearRe = /(exp.?year|expyear|exp_yy|(^|[^a-z])yy(yy)?([^a-z]|$)|(^|[^a-z])year([^a-z]|$)|jahr|annee|(^|[^a-z])ano([^a-z]|$))/;
39
40
  const expCombinedRe = /(exp(iry|iration)?(.?date)?|mm.?\/.?yy|mm.?jj|valid.?thru|ablauf|caducidad)/;
40
41
  const emailRe = /(e-?mail|correo|courriel)/;
42
+ const phoneRe = /(phone|telephone|mobile|(^|[^a-z])tel([^a-z]|$)|telefono|téléphone)/;
41
43
  // One-time verification code (merchant email/SMS OTP). Ordered AFTER cvcRe in
42
44
  // attrClassify so a card CVC ("security code"/"card code") still wins — this
43
45
  // regex deliberately omits those card phrasings. It also OMITS a bare "code":
@@ -67,6 +69,8 @@ const AUTOCOMPLETE_MAP = {
67
69
  'cc-name': 'name',
68
70
  name: 'name',
69
71
  email: 'email',
72
+ tel: 'phone',
73
+ 'tel-national': 'phone',
70
74
  'one-time-code': 'oneTimeCode',
71
75
  'given-name': 'nameFirst',
72
76
  'family-name': 'nameLast',
@@ -167,6 +171,8 @@ function attrClassify(text, m) {
167
171
  // OTP comes after cvc (checked above) so card CVC still classifies as 'cvc'.
168
172
  if (oneTimeCodeRe.test(text))
169
173
  return 'oneTimeCode';
174
+ if (phoneRe.test(text))
175
+ return 'phone';
170
176
  if (postalRe.test(text))
171
177
  return 'postalCode';
172
178
  if (countryRe.test(text))
@@ -1,4 +1,4 @@
1
- export type EvidenceStepType = 'navigation' | 'dom-stable' | 'review' | 'approval' | 'adapter-selected' | 'reveal' | 'detect' | 'field-fill' | 'amount-fill' | 'credential-minted' | 'fill-complete' | 'psp-detected' | 'mandate-verdict' | 'submit' | 'challenge-hold' | 'outcome' | 'note';
1
+ export type EvidenceStepType = 'navigation' | 'dom-stable' | 'review' | 'approval' | 'adapter-selected' | 'reveal' | 'detect' | 'field-fill' | 'amount-fill' | 'contact-prefill' | 'credential-minted' | 'credential-skipped' | 'credential-expiry-check' | 'fill-complete' | 'psp-detected' | 'mandate-verdict' | 'submit' | 'challenge-hold' | 'outcome' | 'note';
2
2
  export type EvidenceStep = {
3
3
  ts: string;
4
4
  type: EvidenceStepType;
@@ -1,12 +1,43 @@
1
1
  import type { Browser, BrowserContext, Page } from 'playwright-core';
2
2
  import { type FieldMap } from './detect.js';
3
- import { type Mandate } from './mandate.js';
3
+ import { type Mandate, type MandateRefusalCode } from './mandate.js';
4
4
  import type { Instrument } from './instrument.js';
5
5
  import type { Contact, OtpResolver } from './types.js';
6
6
  import { EvidenceLog } from './evidence.js';
7
7
  import { type ObservedOutcome } from './outcome.js';
8
+ import { type WebBotAuthConfig } from './web-bot-auth.js';
9
+ import { type NavigationRedirectEvidence } from './shopify-primary-domain.js';
10
+ export { minorFromDecimal, pageCurrency } from './amount.js';
8
11
  export type CheckoutMode = 'dry-run' | 'submit';
9
- export type CheckoutOutcome = 'filled-dry-run' | 'partial-fill' | 'adapter-required' | 'confirmed' | 'declined' | 'action-required' | 'cancelled' | 'blocked-by-mandate' | 'failed';
12
+ export type CheckoutRoute = 'guest-card';
13
+ export type CheckoutOutcome = 'reviewed-dry-run'
14
+ /** Historical receipt value from the credential-disclosing dry-run. */
15
+ | 'filled-dry-run' | 'partial-fill' | 'adapter-required' | 'confirmed' | 'declined' | 'action-required' | 'cancelled' | 'blocked-by-mandate'
16
+ /**
17
+ * KNOWN-NOT-CHARGED. Every `failed` path ends before the pay control was
18
+ * clicked (or before a credential existed at all), so a caller may retry it
19
+ * without risking a second charge.
20
+ */
21
+ | 'failed'
22
+ /**
23
+ * SUBMITTED, OUTCOME UNKNOWN — the pay control WAS clicked and neither a
24
+ * confirmation nor a decline was observed before the deadline. The charge may
25
+ * have captured. NEVER retry this automatically.
26
+ *
27
+ * This case used to be reported as `failed`, and the difference is not
28
+ * academic: on 2026-08-17 a whop.com checkout returned `failed` here, the
29
+ * caller read that as "nothing happened" and re-ran the purchase, and the
30
+ * second run drew a second $5 against the owner's mandate. `outcome.ts`
31
+ * already treats an unrecognized post-submit page as the SAFE direction
32
+ * ('unknown' → operator verification); collapsing it into `failed` at the
33
+ * boundary is what threw that safety away. The distinction has to survive all
34
+ * the way to the value consumers branch on.
35
+ */
36
+ | 'unverified';
37
+ /** Stable machine-readable cause for an expected terminal checkout result. */
38
+ export type CheckoutFailureCode = 'card-number-field-unavailable' | 'human-action-required' | 'mandate-blocked'
39
+ /** A detected, visible field we held a value for refused every fill attempt. */
40
+ | 'required-field-unfillable';
10
41
  export type CredentialLifecycle = 'not-requested' | 'minted-not-exposed' | 'partially-exposed' | 'fully-filled';
11
42
  export type CredentialTiming = {
12
43
  approvedAt?: string;
@@ -22,15 +53,31 @@ export type CheckoutResult = {
22
53
  requiresAdapter: string[];
23
54
  credentialLifecycle: CredentialLifecycle;
24
55
  credentialTiming: CredentialTiming;
56
+ failureCode?: CheckoutFailureCode;
57
+ /** Bounded reason behind a `blocked-by-mandate` outcome; absent otherwise. */
58
+ refusalCode?: MandateRefusalCode;
25
59
  detail?: string;
26
60
  };
27
61
  export type PrepareCheckoutOptions = {
28
62
  url: string;
63
+ checkoutRoute: CheckoutRoute;
29
64
  mandate: Mandate;
30
65
  browser: Browser;
66
+ contact?: Contact;
31
67
  amountMinor?: number;
32
68
  currency?: string;
33
69
  debugShotsDir?: string;
70
+ webBotAuth?: WebBotAuthConfig | null;
71
+ /**
72
+ * Process-internal UCP provenance. Public callers may relay only handoffId;
73
+ * this origin set is recovered by the CLI from its single-use registry.
74
+ */
75
+ trustedMerchantIdentity?: Readonly<{
76
+ handoffId: string;
77
+ checkoutId: string;
78
+ allowedOrigins: readonly string[];
79
+ expiresAt: string;
80
+ }>;
34
81
  };
35
82
  export type RunCheckoutOptions = PrepareCheckoutOptions & {
36
83
  instrument: Instrument;
@@ -43,6 +90,8 @@ export type CheckoutReview = {
43
90
  id: string;
44
91
  url: string;
45
92
  merchantHost: string;
93
+ /** Exact origin frozen for a trusted UCP checkout; omitted for generic flows. */
94
+ merchantOrigin?: string;
46
95
  amountMinor: number;
47
96
  currency: string;
48
97
  mandateMaxAmountMinor: number;
@@ -50,6 +99,7 @@ export type CheckoutReview = {
50
99
  submitTarget: string | null;
51
100
  submitTargetFingerprint: Readonly<CheckoutSubmitTargetFingerprint> | null;
52
101
  detectedRoles: readonly string[];
102
+ checkoutRoute: CheckoutRoute;
53
103
  };
54
104
  export type CheckoutSubmitTargetFingerprint = {
55
105
  kind: 'submit-control' | 'text-button';
@@ -67,6 +117,7 @@ export type PreparedCheckout = {
67
117
  readonly fields: Readonly<FieldMap>;
68
118
  readonly evidence: EvidenceLog;
69
119
  readonly requiresAdapter: readonly string[];
120
+ readonly checkoutRoute: CheckoutRoute;
70
121
  };
71
122
  export type PrepareCheckoutResult = {
72
123
  status: 'ready';
@@ -89,8 +140,17 @@ export type SubmitApprovedCheckoutOptions = {
89
140
  onChallengeHold?: (signal: string | null) => void;
90
141
  resolveEmailOtp?: OtpResolver;
91
142
  };
92
- export declare function minorFromDecimal(text: string): number | null;
93
- export declare function pageCurrency(text: string): string | null;
143
+ export type SubmitCandidateMeta = {
144
+ label: string;
145
+ ariaHidden: boolean;
146
+ tabIndex: number | null;
147
+ visible: boolean;
148
+ area: number;
149
+ };
150
+ export declare function preferredSubmitIndex(cands: readonly SubmitCandidateMeta[], opts?: {
151
+ allowDeferred?: boolean;
152
+ }): number;
153
+ export declare function snapshotOrigin(rawUrl: string): string;
94
154
  export type PreparedCheckoutSession = {
95
155
  checkout: PreparedCheckout;
96
156
  context: BrowserContext;
@@ -133,6 +193,34 @@ export declare class InMemoryPreparedCheckoutStore implements PreparedCheckoutSe
133
193
  private scheduleReaper;
134
194
  private closeState;
135
195
  }
196
+ export type TrustedOriginVerdict = Readonly<{
197
+ code: MandateRefusalCode;
198
+ reason: string;
199
+ }>;
200
+ export declare function trustedMerchantOriginVerdict(options: PrepareCheckoutOptions, pageUrl: string, expectedOrigin?: string): TrustedOriginVerdict | null;
201
+ export declare function trustedMerchantOriginRefusal(options: PrepareCheckoutOptions, pageUrl: string, expectedOrigin?: string): string | null;
202
+ /**
203
+ * Bind the storefront a trusted UCP continuation actually lands on (#8669).
204
+ *
205
+ * The merchant published its UCP business profile at its own business origin
206
+ * and declared the permanent `*.myshopify.com` service that issued the
207
+ * continuation; the CLI verified both before minting the handoff. That is
208
+ * independently verified merchant provenance, so the review may bind the final
209
+ * page origin when, and only when: the navigation started on that declared
210
+ * myshopify origin, the final origin is plain HTTPS, and its host is the
211
+ * declared business host modulo a leading `www.` label. The redirect chain in
212
+ * between (Shopify's primary-domain hop, its shop.app bounce, #8496) carries no
213
+ * authority either way: a redirect cannot land on the merchant's own business
214
+ * domain unless the merchant controls it, and any other final host stays an
215
+ * undeclared origin. Shopify's `primary_domain_redirection` proof is recorded
216
+ * as evidence when present but is not required.
217
+ */
218
+ export declare function trustedShopifyAliasOrigin(args: {
219
+ allowedOrigins: readonly string[];
220
+ initialUrl: string;
221
+ finalUrl: string;
222
+ redirects: readonly NavigationRedirectEvidence[];
223
+ }): string | null;
136
224
  export declare function reconcileHeldOutcome(original: ObservedOutcome, held: ObservedOutcome): ObservedOutcome;
137
225
  export declare function debugShotMaskPlan(fields: FieldMap): {
138
226
  skipReason: string | null;