@agent-cards/sdk 0.20.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 (121) hide show
  1. package/CHANGELOG.md +198 -0
  2. package/PREFLIGHT.md +314 -0
  3. package/README.md +864 -0
  4. package/dist/adyen-merchant-hosted.generated.d.ts +277 -0
  5. package/dist/adyen-merchant-hosted.generated.js +1902 -0
  6. package/dist/adyen.generated.d.ts +24 -0
  7. package/dist/adyen.generated.js +64 -0
  8. package/dist/agentcard.d.ts +72 -0
  9. package/dist/agentcard.js +71 -0
  10. package/dist/api-module.d.ts +54 -0
  11. package/dist/api-module.js +85 -0
  12. package/dist/attachment.d.ts +11 -0
  13. package/dist/attachment.js +50 -0
  14. package/dist/braintree.d.ts +2 -0
  15. package/dist/braintree.generated.d.ts +10 -0
  16. package/dist/braintree.generated.js +302 -0
  17. package/dist/braintree.js +2 -0
  18. package/dist/builtin-registry.generated.d.ts +2 -0
  19. package/dist/builtin-registry.generated.js +1 -0
  20. package/dist/buy.d.ts +374 -0
  21. package/dist/buy.js +314 -0
  22. package/dist/card-fields.generated.d.ts +3 -0
  23. package/dist/card-fields.generated.js +88 -0
  24. package/dist/cdp.d.ts +202 -0
  25. package/dist/cdp.js +2540 -0
  26. package/dist/checkout-com.generated.d.ts +4 -0
  27. package/dist/checkout-com.generated.js +183 -0
  28. package/dist/client.d.ts +929 -0
  29. package/dist/client.js +1896 -0
  30. package/dist/cse-body.d.ts +25 -0
  31. package/dist/cse-body.js +41 -0
  32. package/dist/fiserv.d.ts +65 -0
  33. package/dist/fiserv.generated.d.ts +73 -0
  34. package/dist/fiserv.generated.js +830 -0
  35. package/dist/fiserv.js +104 -0
  36. package/dist/hosted-form.d.ts +44 -0
  37. package/dist/hosted-form.js +78 -0
  38. package/dist/index.d.ts +37 -0
  39. package/dist/index.js +26 -0
  40. package/dist/languages.d.ts +23 -0
  41. package/dist/languages.js +76 -0
  42. package/dist/lifecycle.d.ts +235 -0
  43. package/dist/lifecycle.js +405 -0
  44. package/dist/mercado-checkout.d.ts +20 -0
  45. package/dist/mercado-checkout.generated.d.ts +52 -0
  46. package/dist/mercado-checkout.generated.js +198 -0
  47. package/dist/mercado-checkout.js +108 -0
  48. package/dist/merchant-handoff.d.ts +54 -0
  49. package/dist/merchant-handoff.js +100 -0
  50. package/dist/merchant-hosted.d.ts +140 -0
  51. package/dist/merchant-hosted.js +170 -0
  52. package/dist/merchant-total-watch.d.ts +115 -0
  53. package/dist/merchant-total-watch.js +268 -0
  54. package/dist/merchant-total.d.ts +257 -0
  55. package/dist/merchant-total.js +383 -0
  56. package/dist/owned-shop.generated.d.ts +24 -0
  57. package/dist/owned-shop.generated.js +108 -0
  58. package/dist/paysafe.generated.d.ts +12 -0
  59. package/dist/paysafe.generated.js +87 -0
  60. package/dist/playwright.d.ts +3 -0
  61. package/dist/playwright.js +3 -0
  62. package/dist/pre-claim.d.ts +123 -0
  63. package/dist/pre-claim.js +386 -0
  64. package/dist/preflight-capabilities.generated.d.ts +1311 -0
  65. package/dist/preflight-capabilities.generated.js +2007 -0
  66. package/dist/preflight-catalog.json +5031 -0
  67. package/dist/preflight-playwright.d.ts +34 -0
  68. package/dist/preflight-playwright.js +355 -0
  69. package/dist/preflight-schemas.json +1144 -0
  70. package/dist/preflight.d.ts +1 -0
  71. package/dist/preflight.generated.d.ts +2023 -0
  72. package/dist/preflight.generated.js +604 -0
  73. package/dist/preflight.js +2 -0
  74. package/dist/preparation.d.ts +38 -0
  75. package/dist/preparation.js +191 -0
  76. package/dist/prepared-processor.d.ts +43 -0
  77. package/dist/prepared-processor.js +172 -0
  78. package/dist/recurly.generated.d.ts +1 -0
  79. package/dist/recurly.generated.js +87 -0
  80. package/dist/registry.d.ts +145 -0
  81. package/dist/registry.js +338 -0
  82. package/dist/requests.d.ts +66 -0
  83. package/dist/requests.js +76 -0
  84. package/dist/spreedly.generated.d.ts +10 -0
  85. package/dist/spreedly.generated.js +332 -0
  86. package/dist/stripe-checkout.d.ts +81 -0
  87. package/dist/stripe-checkout.generated.d.ts +82 -0
  88. package/dist/stripe-checkout.generated.js +1153 -0
  89. package/dist/stripe-checkout.js +140 -0
  90. package/dist/substitute.d.ts +38 -0
  91. package/dist/substitute.js +23 -0
  92. package/dist/substitutions.generated.d.ts +11 -0
  93. package/dist/substitutions.generated.js +818 -0
  94. package/dist/voice-pkce.d.ts +49 -0
  95. package/dist/voice-pkce.js +147 -0
  96. package/dist/voice-retry.d.ts +6 -0
  97. package/dist/voice-retry.js +27 -0
  98. package/dist/voice-stream.generated.d.ts +118 -0
  99. package/dist/voice-stream.generated.js +170 -0
  100. package/dist/voice-types.d.ts +708 -0
  101. package/dist/voice-types.js +16 -0
  102. package/dist/voice.d.ts +365 -0
  103. package/dist/voice.js +807 -0
  104. package/dist/webhooks.d.ts +87 -0
  105. package/dist/webhooks.js +136 -0
  106. package/examples/existing-browser.mjs +62 -0
  107. package/examples/preflight/classify-direct.mjs +21 -0
  108. package/examples/preflight/classify-kernel.mjs +30 -0
  109. package/examples/preflight/inspect-browser.mjs +44 -0
  110. package/examples/preflight/kernel-native/README.md +112 -0
  111. package/examples/preflight/kernel-native/documented-adapters.json +113 -0
  112. package/examples/preflight/kernel-native/inventory.json +242 -0
  113. package/examples/preflight/kernel-native/qualification.mjs +182 -0
  114. package/examples/preflight/kernel-profile.empty.json +11 -0
  115. package/examples/preflight/mollie-hosted.observations.json +23 -0
  116. package/examples/preflight/mollie-hosted.result.json +103 -0
  117. package/examples/preflight/stripe-script.direct.result.json +92 -0
  118. package/examples/preflight/stripe-script.observations.json +16 -0
  119. package/examples/preflight/stripe-script.result.json +87 -0
  120. package/package.json +53 -0
  121. package/readme.test.mjs +53 -0
@@ -0,0 +1,405 @@
1
+ import { AdyenTestPlatformRefusedError, ApprovalDeclinedError, ApprovalTimeoutError, CheckoutCancelledError, CheckoutPreparationError, IntentNotConfirmableError, PaymentOutcomeUnknownError, ProcessorRefusedError } from './client.js';
2
+ import { parseOwnedShopReceipt, OWNED_SHOP_SKU, OWNED_SHOP_AMOUNT_CENTS, OWNED_SHOP_CURRENCY } from './owned-shop.generated.js';
3
+ /** How many times one approval's card-free Stripe confirmation may be checked before it is simply held. */
4
+ export const MAX_STRIPE_CONTINUATION_CHECKS = 3;
5
+ /** Shared by the raw CDP and Playwright transports. No browser ownership or payment execution lives here. */
6
+ export class CheckoutLifecycle {
7
+ options;
8
+ state = { status: 'idle', authorizationId: null };
9
+ held = false;
10
+ active = false;
11
+ cancelled = false;
12
+ merchantAborted = false;
13
+ unboundStripeToken = false;
14
+ // The page's own confirmation paying with that token: see claimStripeContinuation.
15
+ stripeTokenPrepared = false;
16
+ stripeContinuation = 'none';
17
+ stripeContinuationChecks = 0;
18
+ ownedShopOrderId = null;
19
+ reconciliation = null;
20
+ preparationHandler;
21
+ preparationUsed = false;
22
+ resumeHandler = null;
23
+ abort = new AbortController();
24
+ constructor(options) {
25
+ this.options = options;
26
+ }
27
+ getState() {
28
+ return { ...this.state, ...(this.state.confirmation ? { confirmation: { ...this.state.confirmation } } : {}) };
29
+ }
30
+ setPreparationHandler(handler) { this.preparationHandler = handler; }
31
+ prepare(options) {
32
+ if (!this.preparationHandler)
33
+ return Promise.reject(new CheckoutPreparationError(null, 'transport_unavailable'));
34
+ return this.preparationHandler(options);
35
+ }
36
+ /** The transport's hand while one request is paused for the business; null once it is answered. */
37
+ setResumeHandler(handler) { this.resumeHandler = handler; }
38
+ resume(result) {
39
+ if (!this.resumeHandler)
40
+ return Promise.reject(new Error('No card request is paused for the business to answer; attach with pauseOnly: true and wait for onPaused.'));
41
+ if (!result || typeof result !== 'object' || typeof result.status !== 'string')
42
+ return Promise.reject(new Error('resume takes the authorization as read: an object with a status.'));
43
+ return this.resumeHandler(result);
44
+ }
45
+ preparing() {
46
+ this.preparationUsed = true;
47
+ this.set({ status: 'awaiting_approval', authorizationId: null });
48
+ }
49
+ preparationCreated(preparationId) { this.set({ ...this.state, preparationId }); }
50
+ prepared(preparation) {
51
+ this.set({ status: 'ready_to_submit', authorizationId: null, preparationId: preparation.id });
52
+ }
53
+ preparationFailed(error) {
54
+ this.held = true;
55
+ if (this.cancelled)
56
+ return;
57
+ this.set({ ...this.state, status: error.reason === 'expired' ? 'timed_out' : error.reason === 'cancelled' ? 'cancelled' : 'failed', reason: error.reason });
58
+ }
59
+ isBlocked() { return this.held || this.cancelled; }
60
+ isCancelled() { return this.cancelled; }
61
+ begin() {
62
+ this.active = true;
63
+ this.set({ status: 'awaiting_approval', authorizationId: null, ...(this.state.preparationId ? { preparationId: this.state.preparationId } : {}) });
64
+ }
65
+ end() { this.active = false; }
66
+ set(state) {
67
+ this.state = state;
68
+ // Application telemetry must never interrupt a paused payment after it reaches the processor.
69
+ try {
70
+ Promise.resolve(this.options.onStateChange?.(this.getState())).catch(() => { });
71
+ }
72
+ catch { /* observer only */ }
73
+ }
74
+ approvalCreated(authorizationId) {
75
+ this.set({ ...this.state, authorizationId });
76
+ }
77
+ approvalUrl(approvalUrl) {
78
+ this.notify({ reason: 'approval', authorizationId: this.state.authorizationId, approvalUrl });
79
+ }
80
+ notify(action) {
81
+ try {
82
+ Promise.resolve(this.options.onUserAction?.(action)).catch(() => { });
83
+ }
84
+ catch { /* observer only */ }
85
+ }
86
+ cancel() {
87
+ this.cancelled = true;
88
+ this.held = true;
89
+ this.abort.abort();
90
+ this.set({ ...this.state, status: this.active || this.state.authorizationId ? 'outcome_unknown' : 'cancelled', reason: 'local_cancel' });
91
+ }
92
+ /** The exact browser request is gone; a late approval cannot reopen it. */
93
+ merchantRequestAborted() {
94
+ this.merchantAborted = true;
95
+ this.held = true;
96
+ this.set({ ...this.state, status: 'outcome_unknown', reason: 'merchant_request_aborted' });
97
+ }
98
+ /**
99
+ * The page abandoned its card request (its own script timed out) while
100
+ * the cardholder is still deciding. The approval stays pending and the
101
+ * page's next matching request will be answered from it, so the status
102
+ * stays `awaiting_approval`; the reason says the page has to ask again.
103
+ * Not a hold: nothing is refused, and no card or token has moved.
104
+ */
105
+ merchantRequestLost() {
106
+ this.set({ ...this.state, reason: 'merchant_request_lost' });
107
+ }
108
+ /**
109
+ * The cardholder approved, and there is no live page request to hand the
110
+ * answer to: the page gave up on its own request while they decided.
111
+ * `ready_to_submit` is the state a prepared checkout uses for "consent in
112
+ * hand, click Pay": the application's next Pay action produces the request
113
+ * this approval answers, with no second prompt on the phone.
114
+ */
115
+ awaitingMerchantRetry(authorizationId) {
116
+ this.set({ status: 'ready_to_submit', authorizationId, reason: 'awaiting_merchant_retry',
117
+ ...(this.state.preparationId ? { preparationId: this.state.preparationId } : {}) });
118
+ }
119
+ /**
120
+ * The page never asked again inside the retry wait and the API confirmed
121
+ * the approval is retired: `declined`, like any other approval that ended
122
+ * with nothing charged, and not held, because the next card request is a
123
+ * new question (the cardholder is told to ask their agent to try again).
124
+ * A retirement the API refused or did not confirm is an unknown outcome and
125
+ * is reported through failed() instead, which holds.
126
+ */
127
+ merchantNeverRetried(authorizationId) {
128
+ if (this.cancelled)
129
+ return;
130
+ this.set({ ...this.state, authorizationId, status: 'declined', reason: 'merchant_never_retried' });
131
+ }
132
+ /**
133
+ * A card request this SDK cannot pay was aborted. `reason`: an unrecognized
134
+ * declared payment endpoint (the default), a merchant profile Agentcard has not
135
+ * turned on (merchant_profile_observe), or a card request the profile cannot
136
+ * finish (merchant_request_unsupported).
137
+ */
138
+ unsupported(reason = 'unrecognized_payment_endpoint') {
139
+ this.held = true;
140
+ if (this.state.authorizationId)
141
+ return; // do not hide an outstanding/completed payment behind a later unsupported request
142
+ this.set({ ...this.state, status: 'unsupported', reason });
143
+ }
144
+ /**
145
+ * A card request that only a prepared checkout pays (an Adyen merchant
146
+ * profile's, or a Fiserv card capture) arrived with no preparation on this
147
+ * attachment and was aborted before any pause, prompt or authorization.
148
+ * Nothing is outstanding, so nothing is held; prepare() stays refused here, as
149
+ * after any first card request.
150
+ */
151
+ preparationRequired() {
152
+ if (this.cancelled || this.state.authorizationId)
153
+ return;
154
+ this.set({ ...this.state, status: 'failed', reason: 'preparation_required' });
155
+ }
156
+ prepareHandoff(replay, requestUrl) {
157
+ if (replay.mode === 'token' && replay.executionMode === 'autopilot' && replay.shopOrderId) {
158
+ let receipt;
159
+ try {
160
+ receipt = parseOwnedShopReceipt(JSON.parse(replay.body), {
161
+ order_id: replay.shopOrderId, sku: OWNED_SHOP_SKU, amount: OWNED_SHOP_AMOUNT_CENTS, currency: OWNED_SHOP_CURRENCY,
162
+ });
163
+ }
164
+ catch { /* An unrecognized response remains subject to the token continuation guard. */ }
165
+ if (receipt) {
166
+ this.ownedShopOrderId = receipt.order_id;
167
+ this.held = true;
168
+ return;
169
+ }
170
+ }
171
+ // A tokenization approval has no authoritative PaymentIntent or amount
172
+ // binding. Neither the first observed confirm nor the returned opaque
173
+ // token can supply one. Hold even in compatibility mode: this attachment
174
+ // must never turn that token into an unreviewed payment continuation.
175
+ if (!replay.mode || replay.mode === 'token') {
176
+ try {
177
+ const url = new URL(requestUrl);
178
+ if (url.origin === 'https://api.stripe.com' && ['/v1/payment_methods', '/v1/tokens', '/v1/sources', '/v1/confirmation_tokens'].includes(url.pathname)) {
179
+ // Set before delivery: a lost browser acknowledgement must not reset
180
+ // an already-issued token into a retryable authorization.
181
+ this.unboundStripeToken = true;
182
+ this.stripeTokenPrepared = true;
183
+ this.held = true;
184
+ }
185
+ }
186
+ catch { /* unknown URL cannot establish a token binding */ }
187
+ }
188
+ }
189
+ /**
190
+ * The approval a page's card-free Stripe confirmation may continue, or null.
191
+ * Only once a Stripe tokenization is being handed to the page, and while
192
+ * the checkout still awaits it (the page can send its confirmation before
193
+ * the browser acknowledges the delivery) or the merchant; for one check at
194
+ * a time and at most MAX_STRIPE_CONTINUATION_CHECKS checks. Reserves the check synchronously,
195
+ * so two confirmations can never both continue: the caller settles it with
196
+ * stripeContinuationChecked(). The hold stays on throughout.
197
+ */
198
+ claimStripeContinuation() {
199
+ if (!this.stripeContinuationOpen() || this.stripeContinuation !== 'none'
200
+ || this.stripeContinuationChecks >= MAX_STRIPE_CONTINUATION_CHECKS)
201
+ return null;
202
+ this.stripeContinuation = 'checking';
203
+ this.stripeContinuationChecks++;
204
+ return this.state.authorizationId;
205
+ }
206
+ /**
207
+ * Settle a claimed check. True only when the API allowed it and the checkout
208
+ * still awaits the merchant (nothing cancelled, aborted or reconciled it
209
+ * while the API answered): the page's confirmation continues, and no later
210
+ * one will.
211
+ */
212
+ stripeContinuationChecked(allowed) {
213
+ if (this.stripeContinuation !== 'checking')
214
+ return false;
215
+ const proceed = allowed && this.stripeContinuationOpen();
216
+ this.stripeContinuation = proceed ? 'continued' : 'none';
217
+ if (proceed)
218
+ this.set({ ...this.state, reason: 'stripe_payment_continued' });
219
+ return proceed;
220
+ }
221
+ stripeContinuationOpen() {
222
+ return this.stripeTokenPrepared && !this.cancelled && !this.merchantAborted && !!this.state.authorizationId
223
+ && (this.state.status === 'awaiting_approval' || this.state.status === 'awaiting_merchant');
224
+ }
225
+ handedOff(replay) {
226
+ if (this.merchantAborted)
227
+ return;
228
+ if (this.ownedShopOrderId) {
229
+ this.held = true;
230
+ this.set({ status: 'completed', authorizationId: replay.authorizationId, mode: 'token', orderId: this.ownedShopOrderId });
231
+ return;
232
+ }
233
+ const mismatch = (!replay.mode || replay.mode === 'token') && replay.amountVerified === false;
234
+ this.held = !!this.options.requireMerchantResult || replay.mode === 'hosted_form' || mismatch || this.unboundStripeToken;
235
+ if (mismatch)
236
+ this.stripeTokenPrepared = false;
237
+ this.set({ status: 'awaiting_merchant', authorizationId: replay.authorizationId, mode: replay.mode ?? 'token',
238
+ ...(this.state.preparationId ? { preparationId: this.state.preparationId } : {}),
239
+ ...(mismatch ? { reason: 'charged_amount_mismatch' }
240
+ : this.stripeContinuation === 'continued' ? { reason: 'stripe_payment_continued' }
241
+ : this.unboundStripeToken ? { reason: 'stripe_tokenization_unbound' } : {}) });
242
+ }
243
+ failed(error, handoffStarted = false) {
244
+ if (this.cancelled)
245
+ return;
246
+ if (this.merchantAborted) {
247
+ this.merchantRequestAborted();
248
+ return;
249
+ }
250
+ const authorizationId = error instanceof PaymentOutcomeUnknownError || error instanceof ProcessorRefusedError ? error.authorizationId : this.state.authorizationId;
251
+ if (!handoffStarted && error instanceof PaymentOutcomeUnknownError && error.reason === 'autopilot_action_required') {
252
+ this.held = true;
253
+ this.set({ ...this.state, authorizationId, status: 'requires_user_action', reason: 'other' });
254
+ this.notify({ reason: 'other', authorizationId });
255
+ }
256
+ else if (handoffStarted || error instanceof PaymentOutcomeUnknownError || error instanceof IntentNotConfirmableError) {
257
+ this.held = true;
258
+ this.set({ ...this.state, authorizationId, status: 'outcome_unknown', reason: error instanceof PaymentOutcomeUnknownError ? error.reason : error instanceof IntentNotConfirmableError ? 'intent_not_confirmable' : 'browser_handoff_failed' });
259
+ }
260
+ else if (error instanceof CheckoutCancelledError) {
261
+ this.cancel();
262
+ }
263
+ else if (error instanceof CheckoutPreparationError) {
264
+ this.preparationFailed(error);
265
+ }
266
+ else if (error instanceof ApprovalTimeoutError) {
267
+ this.set({ ...this.state, status: 'timed_out', reason: 'approval_expired' });
268
+ }
269
+ else if (error instanceof ProcessorRefusedError) {
270
+ // A rejected processor request is not proof that the merchant cannot
271
+ // collect this payment. Keep native retries held until the application
272
+ // checks the merchant and explicitly starts another attempt.
273
+ this.held = true;
274
+ this.set({ ...this.state, authorizationId, status: 'declined', reason: 'processor_refused' });
275
+ }
276
+ else if (error instanceof AdyenTestPlatformRefusedError) {
277
+ // Agentcard's own rule, not a person's answer: refused at create before
278
+ // anyone was asked, the checkout failed as it did before the error was
279
+ // typed; declined before the card was sent, the authorization is declined.
280
+ // Either way the reason names the rule.
281
+ this.set({ ...this.state, status: error.stage === 'create' ? 'failed' : 'declined', reason: error.code });
282
+ }
283
+ else if (error instanceof ApprovalDeclinedError) {
284
+ this.set({ ...this.state, status: 'declined', reason: 'approval_declined' });
285
+ }
286
+ else {
287
+ this.set({ ...this.state, status: 'failed', reason: 'checkout_failed' });
288
+ }
289
+ }
290
+ async requestUserAction(reason) {
291
+ if (this.state.status === 'completed')
292
+ return;
293
+ this.held = true;
294
+ this.set({ ...this.state, status: 'requires_user_action', reason });
295
+ // Hooks deliver a link/live view owned by the integrator; the SDK does not invent challenge URLs.
296
+ try {
297
+ await this.options.onUserAction?.({ reason, authorizationId: this.state.authorizationId });
298
+ }
299
+ catch { /* observer only */ }
300
+ }
301
+ reconcile() {
302
+ if (this.state.status === 'completed')
303
+ return Promise.resolve(this.getState());
304
+ if (this.reconciliation)
305
+ return this.reconciliation;
306
+ if (!this.options.resolveMerchantResult)
307
+ return Promise.resolve(this.getState());
308
+ if (this.active)
309
+ throw new Error('Wait for the paused request to finish before reconciling the merchant order.');
310
+ this.reconciliation = this.resolve().finally(() => { this.reconciliation = null; });
311
+ return this.reconciliation;
312
+ }
313
+ async resolve() {
314
+ let result;
315
+ try {
316
+ result = await this.options.resolveMerchantResult(this.getState());
317
+ }
318
+ catch {
319
+ result = { status: 'unknown' };
320
+ }
321
+ if (!result || typeof result !== 'object' || Array.isArray(result))
322
+ result = { status: 'unknown' };
323
+ // The resolver is trusted application code: it must check the original
324
+ // request, amount, currency, selected card and authoritative merchant
325
+ // result. The SDK binds its explicit confirmation to this authorization;
326
+ // it does not turn an HTTP response or a success URL into payment proof.
327
+ const orderConfirmed = result.status === 'completed' && result.confirmation === undefined
328
+ && typeof result.orderId === 'string' && result.orderId.length > 0;
329
+ const paymentConfirmed = result.status === 'completed' && result.orderId === undefined
330
+ && result.confirmation && typeof result.confirmation === 'object' && !Array.isArray(result.confirmation)
331
+ && result.confirmation.kind === 'merchant_payment'
332
+ && typeof this.state.authorizationId === 'string' && this.state.authorizationId.length > 0
333
+ && result.confirmation.authorizationId === this.state.authorizationId;
334
+ if (result.status === 'completed' && (orderConfirmed || paymentConfirmed)) {
335
+ this.held = true;
336
+ // Completion supersedes an earlier failure/challenge, while retaining
337
+ // preparation and authorization context. Never copy arbitrary evidence
338
+ // into public state or release the hold on additional payment requests.
339
+ const { reason: _previousReason, orderId: _previousOrder, confirmation: _previousConfirmation, ...context } = this.state;
340
+ this.set({ ...context, status: 'completed', ...(orderConfirmed
341
+ ? { orderId: result.orderId }
342
+ : { confirmation: { kind: 'merchant_payment', authorizationId: this.state.authorizationId } }) });
343
+ }
344
+ else if (result.status === 'failed') {
345
+ // Keep the guard armed until the application deliberately starts another attempt.
346
+ this.held = true;
347
+ this.set({ ...this.state, status: 'failed', reason: 'merchant_confirmed_failure' });
348
+ }
349
+ else if (result.status === 'requires_user_action') {
350
+ await this.requestUserAction(result.reason);
351
+ }
352
+ else {
353
+ this.held = true;
354
+ this.set({ ...this.state, status: result.status === 'pending' ? 'awaiting_merchant' : 'outcome_unknown', reason: 'merchant_not_confirmed' });
355
+ }
356
+ return this.getState();
357
+ }
358
+ retryAfterMerchantFailure(result) {
359
+ if (this.ownedShopOrderId)
360
+ throw new Error('This shop payment already has a receipt; another payment is not permitted.');
361
+ if (this.preparationUsed)
362
+ throw new Error('Prepared checkouts are single use. Reconcile this attempt and create a new attachment.');
363
+ if (this.active || this.reconciliation)
364
+ throw new Error('Cannot start another attempt while payment or reconciliation is in progress.');
365
+ if (this.cancelled)
366
+ throw new Error('This attachment was cancelled; attach again after reconciling the merchant order.');
367
+ if (result?.status !== 'failed')
368
+ throw new Error('A merchant-confirmed failure is required before retrying.');
369
+ if (this.state.status === 'completed')
370
+ throw new Error('This order is already complete. Use a new attachment for another order.');
371
+ if (this.unboundStripeToken)
372
+ throw new Error('This attachment delivered an unbound Stripe token; reconcile the merchant order and use a separately validated checkout flow.');
373
+ this.held = false;
374
+ this.merchantAborted = false;
375
+ this.set({ status: 'idle', authorizationId: null });
376
+ }
377
+ }
378
+ export function paymentEndpointGuards(input = []) {
379
+ const guards = input.map((guard) => {
380
+ const origin = new URL(guard.origin);
381
+ if (!['http:', 'https:'].includes(origin.protocol) || origin.origin !== guard.origin
382
+ || !guard.pathname.startsWith('/') || /[?#*]/.test(guard.pathname)) {
383
+ throw new Error('Payment endpoint guards require a canonical http(s) origin and an exact path without wildcards or query strings.');
384
+ }
385
+ const methods = (guard.methods ?? ['POST', 'PUT', 'PATCH']).map((method) => method.toUpperCase());
386
+ if (!methods.length || methods.some((method) => !['POST', 'PUT', 'PATCH', 'DELETE'].includes(method))) {
387
+ throw new Error('Payment endpoint guards can only block explicit mutation methods.');
388
+ }
389
+ return { ...guard, methods };
390
+ });
391
+ return {
392
+ patterns: guards.map((guard) => `${guard.origin}${guard.pathname}*`),
393
+ matches(raw, method) {
394
+ let url;
395
+ try {
396
+ url = new URL(raw);
397
+ }
398
+ catch {
399
+ return false;
400
+ }
401
+ return guards.some((guard) => url.origin === guard.origin && url.pathname === guard.pathname
402
+ && (method === undefined || guard.methods.includes(method.toUpperCase())));
403
+ },
404
+ };
405
+ }
@@ -0,0 +1,20 @@
1
+ import type { ReplayResponse } from './client.js';
2
+ /** Capture only the native Checkout Pro configuration. A late configuration,
3
+ * navigation or repeated token cannot replace the context of an issued token.
4
+ * Final payment requests retain the attachment's existing payment guards.
5
+ */
6
+ export declare class MercadoCheckoutGate {
7
+ private context;
8
+ private started;
9
+ private stopped;
10
+ private sequence;
11
+ private associatedFlow;
12
+ private association;
13
+ matches(url: string): boolean;
14
+ requiresDocument(url: string, method: string): boolean;
15
+ invalidate(): void;
16
+ configuration(url: string, method: string, body: string, readDocument: () => Promise<string>): Promise<boolean>;
17
+ claimToken(url: string, method: string, documentUrl: string): unknown;
18
+ arm(replay: ReplayResponse, tokenUrl: string): Promise<void>;
19
+ associationBody(url: string, method: string, body: string, documentUrl: string): string | undefined;
20
+ }
@@ -0,0 +1,52 @@
1
+ declare var MERCADO_CHECKOUT_ORIGIN: string;
2
+ declare var MERCADO_CONFIGURATION_URL: string;
3
+ declare var MERCADO_CHECKOUT_PATTERN: string;
4
+ declare function mercadoCheckoutDocument(raw: any): {
5
+ flowId: string;
6
+ preferenceId: string | null;
7
+ query: string;
8
+ };
9
+ declare function parseMercadoCheckoutContext(value: any): any;
10
+ declare function captureMercadoCheckoutContext(url: any, method: any, body: any, documentUrl: any): any;
11
+ declare function isMercadoTokenRequest(url: any, method: any): boolean;
12
+ declare function mercadoDigest(value: any): Promise<string>;
13
+ declare function mercadoContextDigest(context: any, tokenUrl: any): Promise<string>;
14
+ declare function prepareMercadoConfiguration(context: any, card: any): {
15
+ url: string;
16
+ body: string;
17
+ };
18
+ declare function parseMercadoConfiguration(value: any): {
19
+ paymentMethodId: any;
20
+ paymentTypeId: any;
21
+ paymentMethodName: any;
22
+ issuerId: string;
23
+ issuerName: any;
24
+ };
25
+ declare function createMercadoProcessorContext(configuration: any, context: any, tokenUrl: any, tokenBody: any): Promise<{
26
+ paymentMethodId: any;
27
+ paymentTypeId: any;
28
+ paymentMethodName: any;
29
+ issuerId: string;
30
+ issuerName: any;
31
+ version: number;
32
+ psp: string;
33
+ source: string;
34
+ contextDigest: string;
35
+ tokenDigest: string;
36
+ }>;
37
+ declare function validateMercadoProcessorContext(value: any, context: any, tokenUrl: any, tokenBody: any): Promise<any>;
38
+ declare function createMercadoAssociationGate({ context, tokenUrl, tokenBody, processorContext }: {
39
+ context: any;
40
+ tokenUrl: any;
41
+ tokenBody: any;
42
+ processorContext: any;
43
+ }): Promise<{
44
+ take({ url, method, body, documentUrl }: {
45
+ url: any;
46
+ method: any;
47
+ body: any;
48
+ documentUrl: any;
49
+ }): string;
50
+ readonly claimed: boolean;
51
+ }>;
52
+ export { MERCADO_CHECKOUT_ORIGIN, MERCADO_CHECKOUT_PATTERN, MERCADO_CONFIGURATION_URL, captureMercadoCheckoutContext, createMercadoAssociationGate, createMercadoProcessorContext, isMercadoTokenRequest, mercadoCheckoutDocument, mercadoContextDigest, mercadoDigest, parseMercadoCheckoutContext, parseMercadoConfiguration, prepareMercadoConfiguration, validateMercadoProcessorContext };