@comfyorg/account-core 1.0.0-alpha.2 → 1.0.0-alpha.3

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 (69) hide show
  1. package/dist/core/billing/index.d.ts +4 -3
  2. package/dist/core/billing/index.js +2 -1
  3. package/dist/core/billing/operationLifecycle.d.ts +26 -4
  4. package/dist/core/billing/operationLifecycle.js +113 -14
  5. package/dist/core/billing/operationPointer.d.ts +24 -0
  6. package/dist/core/billing/operationPointer.js +14 -1
  7. package/dist/core/billing/operationState.d.ts +18 -6
  8. package/dist/core/billing/operationState.js +13 -15
  9. package/dist/core/billing/paymentFriction.d.ts +17 -0
  10. package/dist/core/billing/paymentFriction.js +41 -0
  11. package/dist/core/billing/subscriptionCommands.d.ts +38 -2
  12. package/dist/core/billing/subscriptionCommands.js +27 -7
  13. package/dist/core/billing/subscriptionDiscount.d.ts +26 -0
  14. package/dist/core/billing/subscriptionDiscount.js +6 -0
  15. package/dist/core/billing/telemetry/billingTelemetryEvent.d.ts +25 -0
  16. package/dist/core/billing/telemetry/billingTelemetryEvent.js +3 -0
  17. package/dist/core/billing/telemetry/cancelEvent.d.ts +19 -0
  18. package/dist/core/billing/telemetry/cancelEvent.js +1 -0
  19. package/dist/core/billing/telemetry/capabilityReadEvent.d.ts +4 -0
  20. package/dist/core/billing/telemetry/capabilityReadEvent.js +1 -0
  21. package/dist/core/billing/telemetry/checkoutChallengeEvent.d.ts +25 -0
  22. package/dist/core/billing/telemetry/checkoutChallengeEvent.js +1 -0
  23. package/dist/core/billing/telemetry/checkoutJourney.d.ts +176 -0
  24. package/dist/core/billing/telemetry/checkoutJourney.js +115 -0
  25. package/dist/core/billing/telemetry/checkoutRedirectEvent.d.ts +30 -0
  26. package/dist/core/billing/telemetry/checkoutRedirectEvent.js +1 -0
  27. package/dist/core/billing/telemetry/downgradeToPersonalEvent.d.ts +14 -0
  28. package/dist/core/billing/telemetry/downgradeToPersonalEvent.js +1 -0
  29. package/dist/core/billing/telemetry/entryEvent.d.ts +16 -0
  30. package/dist/core/billing/telemetry/entryEvent.js +1 -0
  31. package/dist/core/billing/telemetry/eventNames.d.ts +36 -0
  32. package/dist/core/billing/telemetry/eventNames.js +36 -0
  33. package/dist/core/billing/telemetry/index.d.ts +17 -0
  34. package/dist/core/billing/telemetry/index.js +6 -0
  35. package/dist/core/billing/telemetry/operationEvent.d.ts +25 -0
  36. package/dist/core/billing/telemetry/operationEvent.js +1 -0
  37. package/dist/core/billing/telemetry/operationLifecycleEvent.d.ts +9 -0
  38. package/dist/core/billing/telemetry/operationLifecycleEvent.js +79 -0
  39. package/dist/core/billing/telemetry/payload.d.ts +55 -0
  40. package/dist/core/billing/telemetry/payload.js +75 -0
  41. package/dist/core/billing/telemetry/portalEvent.d.ts +23 -0
  42. package/dist/core/billing/telemetry/portalEvent.js +1 -0
  43. package/dist/core/billing/telemetry/resubscribeEvent.d.ts +13 -0
  44. package/dist/core/billing/telemetry/resubscribeEvent.js +1 -0
  45. package/dist/core/billing/telemetry/stages.d.ts +45 -0
  46. package/dist/core/billing/telemetry/stages.js +1 -0
  47. package/dist/core/billing/telemetry/subscriptionCheckoutEvent.d.ts +23 -0
  48. package/dist/core/billing/telemetry/subscriptionCheckoutEvent.js +1 -0
  49. package/dist/core/billing/telemetry/topupEvent.d.ts +30 -0
  50. package/dist/core/billing/telemetry/topupEvent.js +6 -0
  51. package/dist/core/billing/telemetry/vocabulary.d.ts +9 -0
  52. package/dist/core/billing/telemetry/vocabulary.js +1 -0
  53. package/dist/core/billing/telemetry/webEntryEvent.d.ts +27 -0
  54. package/dist/core/billing/telemetry/webEntryEvent.js +1 -0
  55. package/dist/core/billing/telemetry/webHandoffEvent.d.ts +11 -0
  56. package/dist/core/billing/telemetry/webHandoffEvent.js +1 -0
  57. package/dist/core/billing/telemetry/webReturnEvent.d.ts +8 -0
  58. package/dist/core/billing/telemetry/webReturnEvent.js +1 -0
  59. package/dist/core/billing/telemetry/webSessionEvent.d.ts +17 -0
  60. package/dist/core/billing/telemetry/webSessionEvent.js +1 -0
  61. package/dist/core/webSession.d.ts +4 -3
  62. package/dist/core/webSession.js +6 -5
  63. package/dist/core/webSessionIdentity.d.ts +6 -6
  64. package/dist/signInSchemas.d.ts +1 -1
  65. package/dist/telemetry.d.ts +26 -0
  66. package/dist/telemetry.js +28 -7
  67. package/dist/testing.d.ts +2 -1
  68. package/dist/testing.js +5 -4
  69. package/package.json +3 -1
@@ -24,6 +24,8 @@ export declare const RESUBSCRIBE_ROUTE = "/billing/subscription/resubscribe";
24
24
  export declare const CANCEL_SUBSCRIPTION_ROUTE = "/billing/subscription/cancel";
25
25
  export declare const PAYMENT_PORTAL_ROUTE = "/billing/payment-portal";
26
26
  export declare const PREVIEW_SUBSCRIBE_ROUTE = "/billing/preview-subscribe";
27
+ export declare function cancelOperationRoute(operationId: string): string;
28
+ declare const CANCEL_REFUSAL_SERVER_CODES: readonly ["NOT_CANCELABLE", "PAYMENT_IN_FLIGHT"];
27
29
  export type SubscribeInput = z.infer<typeof zSubscribeRequest>;
28
30
  export type TerminalBillingOperation = Exclude<BillingOperationState, PendingBillingOperation>;
29
31
  /** Outcomes a subscription command adds to the shared `BillingErrorCode` set. */
@@ -64,6 +66,23 @@ export type SubscriptionCommandResult = {
64
66
  readonly status: 'ok';
65
67
  readonly value: SubscriptionCommandOutcome;
66
68
  } | SubscriptionCommandFailure;
69
+ /**
70
+ * Why the server kept a payment it was asked to cancel. `PAYMENT_IN_FLIGHT`:
71
+ * the payment won the race and the operation will settle on its own.
72
+ * `NOT_CANCELABLE`: the operation is not one waiting on authentication.
73
+ */
74
+ export type CancelRefusalCode = (typeof CANCEL_REFUSAL_SERVER_CODES)[number];
75
+ /**
76
+ * The server's answer to cancelling a pending operation. `canceled`: dropped,
77
+ * nothing charged, also for one already discarded. `cancel_requested`: the
78
+ * cancel was delivered but has not settled; asking again is safe.
79
+ */
80
+ export type CancelOperationResult = {
81
+ readonly status: 'canceled' | 'cancel_requested';
82
+ } | {
83
+ readonly status: 'not_canceled';
84
+ readonly code: CancelRefusalCode;
85
+ } | BillingFailure;
67
86
  export type PaymentPortalResult = {
68
87
  readonly status: 'ok';
69
88
  readonly value: {
@@ -90,6 +109,8 @@ declare const PreviewSchema: z.ZodObject<{
90
109
  cost_today_cents: z.ZodNumber;
91
110
  credits_next_period_cents: z.ZodNumber;
92
111
  credits_today_cents: z.ZodNumber;
112
+ credits_today: z.ZodOptional<z.ZodNumber>;
113
+ credits_next_period: z.ZodOptional<z.ZodNumber>;
93
114
  renewal_amount_cents: z.ZodOptional<z.ZodNumber>;
94
115
  subtotal_cents: z.ZodOptional<z.ZodNumber>;
95
116
  balance_applied_cents: z.ZodOptional<z.ZodNumber>;
@@ -218,6 +239,7 @@ declare const PreviewSchema: z.ZodObject<{
218
239
  duration: z.ZodOptional<z.ZodEnum<["once", "repeating", "forever"]>>;
219
240
  kind: z.ZodEnum<["plan", "promotion"]>;
220
241
  name: z.ZodOptional<z.ZodString>;
242
+ term: z.ZodOptional<z.ZodEnum<["this_payment", "first_month", "first_year", "months", "ongoing"]>>;
221
243
  } & {
222
244
  amount_off_cents: z.ZodOptional<z.ZodNumber>;
223
245
  duration_in_months: z.ZodOptional<z.ZodNumber>;
@@ -228,6 +250,7 @@ declare const PreviewSchema: z.ZodObject<{
228
250
  amount_off_cents?: number | undefined;
229
251
  duration?: "once" | "repeating" | "forever" | undefined;
230
252
  duration_in_months?: number | undefined;
253
+ term?: "this_payment" | "first_month" | "first_year" | "months" | "ongoing" | undefined;
231
254
  }, {
232
255
  code: string;
233
256
  kind: "plan" | "promotion";
@@ -235,6 +258,7 @@ declare const PreviewSchema: z.ZodObject<{
235
258
  amount_off_cents?: number | undefined;
236
259
  duration?: "once" | "repeating" | "forever" | undefined;
237
260
  duration_in_months?: number | undefined;
261
+ term?: "this_payment" | "first_month" | "first_year" | "months" | "ongoing" | undefined;
238
262
  }>, "many">>;
239
263
  }, "strip", z.ZodTypeAny, {
240
264
  effective_at: string;
@@ -263,6 +287,7 @@ declare const PreviewSchema: z.ZodObject<{
263
287
  };
264
288
  transition_type: "new_subscription" | "upgrade" | "downgrade" | "duration_change";
265
289
  currency?: string | undefined;
290
+ reason?: string | undefined;
266
291
  promotion_code?: string | undefined;
267
292
  proration_at?: string | undefined;
268
293
  quote_id?: string | undefined;
@@ -293,15 +318,17 @@ declare const PreviewSchema: z.ZodObject<{
293
318
  amount_off_cents?: number | undefined;
294
319
  duration?: "once" | "repeating" | "forever" | undefined;
295
320
  duration_in_months?: number | undefined;
321
+ term?: "this_payment" | "first_month" | "first_year" | "months" | "ongoing" | undefined;
296
322
  }[] | undefined;
297
323
  payment_method_configuration_id?: string | undefined;
298
324
  proration_remaining_cents?: number | undefined;
299
325
  proration_unused_cents?: number | undefined;
300
- reason?: string | undefined;
301
326
  renewal_amount_cents?: number | undefined;
302
327
  renewal_at?: string | undefined;
303
328
  requires_reactivation_confirmation?: boolean | undefined;
304
329
  subtotal_cents?: number | undefined;
330
+ credits_today?: number | undefined;
331
+ credits_next_period?: number | undefined;
305
332
  }, {
306
333
  effective_at: string;
307
334
  allowed: boolean;
@@ -329,6 +356,7 @@ declare const PreviewSchema: z.ZodObject<{
329
356
  };
330
357
  transition_type: "new_subscription" | "upgrade" | "downgrade" | "duration_change";
331
358
  currency?: string | undefined;
359
+ reason?: string | undefined;
332
360
  promotion_code?: string | undefined;
333
361
  proration_at?: string | undefined;
334
362
  quote_id?: string | undefined;
@@ -359,15 +387,17 @@ declare const PreviewSchema: z.ZodObject<{
359
387
  amount_off_cents?: number | undefined;
360
388
  duration?: "once" | "repeating" | "forever" | undefined;
361
389
  duration_in_months?: number | undefined;
390
+ term?: "this_payment" | "first_month" | "first_year" | "months" | "ongoing" | undefined;
362
391
  }[] | undefined;
363
392
  payment_method_configuration_id?: string | undefined;
364
393
  proration_remaining_cents?: number | undefined;
365
394
  proration_unused_cents?: number | undefined;
366
- reason?: string | undefined;
367
395
  renewal_amount_cents?: number | undefined;
368
396
  renewal_at?: string | undefined;
369
397
  requires_reactivation_confirmation?: boolean | undefined;
370
398
  subtotal_cents?: number | undefined;
399
+ credits_today?: number | undefined;
400
+ credits_next_period?: number | undefined;
371
401
  }>;
372
402
  /**
373
403
  * The quote the server returns for a plan change, in the generated field names.
@@ -412,6 +442,12 @@ export interface BillingCommands {
412
442
  previewSubscribe: (input: PreviewSubscribeInput, options?: PreviewSubscribeOptions) => Promise<PreviewSubscribeResult>;
413
443
  resubscribe: () => Promise<SubscriptionCommandResult>;
414
444
  cancelSubscription: () => Promise<SubscriptionCommandResult>;
445
+ /**
446
+ * Asks the server to drop an operation still waiting on the customer's
447
+ * authentication. Any answer that names the operation's fate wakes the
448
+ * lifecycle, so the operation it follows catches up with the server.
449
+ */
450
+ cancelOperation: (operationId: string) => Promise<CancelOperationResult>;
415
451
  /** Returns the portal URL; the host decides how to open it. */
416
452
  openPaymentPortal: (input: {
417
453
  readonly returnUrl?: string;
@@ -11,17 +11,22 @@
11
11
  * against the closed set named here; a code outside it reaches the caller only
12
12
  * as the transport's coded failure.
13
13
  */
14
- import { zCancelSubscriptionResponse2, zPaymentPortalResponse, zPreviewPlanInfo, zPreviewSubscribeRequest, zPreviewSubscribeResponse, zResubscribeResponse, zSubscribeRequest, zSubscribeResponse, zSubscriptionDiscount } from '@comfyorg/ingest-types/zod';
14
+ import { zCancelBillingOpResponse, zCancelSubscriptionResponse2, zPaymentPortalResponse, zPreviewPlanInfo, zPreviewSubscribeRequest, zPreviewSubscribeResponse, zResubscribeResponse, zSubscribeRequest, zSubscribeResponse } from '@comfyorg/ingest-types/zod';
15
15
  import { z } from 'zod';
16
16
  import { matchesServerCode } from './billingContracts.js';
17
+ import { operationRoute } from './operationLifecycle.js';
17
18
  import { validateActionUrl } from './operationState.js';
18
19
  import { readValidatedBillingResponse } from './sharedRead.js';
20
+ import { SubscriptionDiscountSchema } from './subscriptionDiscount.js';
19
21
  import { wireCents } from './wireCents.js';
20
22
  export const SUBSCRIBE_ROUTE = '/billing/subscribe';
21
23
  export const RESUBSCRIBE_ROUTE = '/billing/subscription/resubscribe';
22
24
  export const CANCEL_SUBSCRIPTION_ROUTE = '/billing/subscription/cancel';
23
25
  export const PAYMENT_PORTAL_ROUTE = '/billing/payment-portal';
24
26
  export const PREVIEW_SUBSCRIBE_ROUTE = '/billing/preview-subscribe';
27
+ export function cancelOperationRoute(operationId) {
28
+ return `${operationRoute(operationId)}/cancel`;
29
+ }
25
30
  /** The closed set of `serverCode` values these commands act on. */
26
31
  const NO_ACTIVE_SUBSCRIPTION_SERVER_CODE = 'NO_ACTIVE_SUBSCRIPTION';
27
32
  const REACTIVATION_CONFIRMATION_REQUIRED_SERVER_CODE = 'REACTIVATION_CONFIRMATION_REQUIRED';
@@ -29,6 +34,10 @@ const NOT_SCHEDULED_FOR_CANCELLATION_SERVER_CODE = 'NOT_SCHEDULED_FOR_CANCELLATI
29
34
  const ALREADY_CANCELED_SERVER_CODE = 'ALREADY_CANCELED';
30
35
  const SUBSCRIPTION_QUOTE_STALE_SERVER_CODE = 'SUBSCRIPTION_QUOTE_STALE';
31
36
  const SUBSCRIPTION_CHANGE_IN_PROGRESS_SERVER_CODE = 'SUBSCRIPTION_CHANGE_IN_PROGRESS';
37
+ const CANCEL_REFUSAL_SERVER_CODES = [
38
+ 'NOT_CANCELABLE',
39
+ 'PAYMENT_IN_FLIGHT'
40
+ ];
32
41
  const PlanInfoSchema = zPreviewPlanInfo.extend({
33
42
  credits_cents: wireCents,
34
43
  price_cents: wireCents,
@@ -46,6 +55,9 @@ const PreviewSchema = zPreviewSubscribeResponse.extend({
46
55
  cost_today_cents: wireCents,
47
56
  credits_next_period_cents: wireCents,
48
57
  credits_today_cents: wireCents,
58
+ /** Whole credits as granted. Not in ingest-types until cloud PR 11905 syncs. */
59
+ credits_today: wireCents.nonnegative().optional(),
60
+ credits_next_period: wireCents.nonnegative().optional(),
49
61
  renewal_amount_cents: wireCents.optional(),
50
62
  subtotal_cents: wireCents.optional(),
51
63
  balance_applied_cents: wireCents.optional(),
@@ -53,12 +65,7 @@ const PreviewSchema = zPreviewSubscribeResponse.extend({
53
65
  proration_unused_cents: wireCents.optional(),
54
66
  current_plan: PlanInfoSchema.optional(),
55
67
  new_plan: PlanInfoSchema,
56
- discounts: z
57
- .array(zSubscriptionDiscount.extend({
58
- amount_off_cents: wireCents.optional(),
59
- duration_in_months: wireCents.optional()
60
- }))
61
- .optional()
68
+ discounts: z.array(SubscriptionDiscountSchema).optional()
62
69
  });
63
70
  const ALREADY_HELD = {
64
71
  status: 'ok',
@@ -269,11 +276,24 @@ export function createBillingCommands(options) {
269
276
  }
270
277
  return { status: 'ok', value: { url } };
271
278
  }
279
+ async function cancelOperation(operationId) {
280
+ const response = await post(cancelOperationRoute(operationId), undefined, (body) => zCancelBillingOpResponse.safeParse(body));
281
+ if (response.status === 'error') {
282
+ const refusal = CANCEL_REFUSAL_SERVER_CODES.find((code) => refusedWith(response, code));
283
+ if (refusal === undefined)
284
+ return response;
285
+ lifecycle.wake();
286
+ return { status: 'not_canceled', code: refusal };
287
+ }
288
+ lifecycle.wake();
289
+ return { status: response.value.data.status };
290
+ }
272
291
  return {
273
292
  subscribe,
274
293
  previewSubscribe,
275
294
  resubscribe,
276
295
  cancelSubscription: () => settle('cancel', issueCancel),
296
+ cancelOperation,
277
297
  openPaymentPortal
278
298
  };
279
299
  }
@@ -0,0 +1,26 @@
1
+ export declare const SubscriptionDiscountSchema: import("zod").ZodObject<{
2
+ code: import("zod").ZodString;
3
+ duration: import("zod").ZodOptional<import("zod").ZodEnum<["once", "repeating", "forever"]>>;
4
+ kind: import("zod").ZodEnum<["plan", "promotion"]>;
5
+ name: import("zod").ZodOptional<import("zod").ZodString>;
6
+ term: import("zod").ZodOptional<import("zod").ZodEnum<["this_payment", "first_month", "first_year", "months", "ongoing"]>>;
7
+ } & {
8
+ amount_off_cents: import("zod").ZodOptional<import("zod").ZodNumber>;
9
+ duration_in_months: import("zod").ZodOptional<import("zod").ZodNumber>;
10
+ }, "strip", import("zod").ZodTypeAny, {
11
+ code: string;
12
+ kind: "plan" | "promotion";
13
+ name?: string | undefined;
14
+ amount_off_cents?: number | undefined;
15
+ duration?: "once" | "repeating" | "forever" | undefined;
16
+ duration_in_months?: number | undefined;
17
+ term?: "this_payment" | "first_month" | "first_year" | "months" | "ongoing" | undefined;
18
+ }, {
19
+ code: string;
20
+ kind: "plan" | "promotion";
21
+ name?: string | undefined;
22
+ amount_off_cents?: number | undefined;
23
+ duration?: "once" | "repeating" | "forever" | undefined;
24
+ duration_in_months?: number | undefined;
25
+ term?: "this_payment" | "first_month" | "first_year" | "months" | "ongoing" | undefined;
26
+ }>;
@@ -0,0 +1,6 @@
1
+ import { zSubscriptionDiscount } from '@comfyorg/ingest-types/zod';
2
+ import { wireCents } from './wireCents.js';
3
+ export const SubscriptionDiscountSchema = zSubscriptionDiscount.extend({
4
+ amount_off_cents: wireCents.optional(),
5
+ duration_in_months: wireCents.optional()
6
+ });
@@ -0,0 +1,25 @@
1
+ import type { CancelBillingEvent } from './cancelEvent.js';
2
+ import type { CapabilityReadBillingEvent } from './capabilityReadEvent.js';
3
+ import type { CheckoutChallengeBillingEvent } from './checkoutChallengeEvent.js';
4
+ import type { CheckoutRedirectBillingEvent } from './checkoutRedirectEvent.js';
5
+ import type { DowngradeToPersonalBillingEvent } from './downgradeToPersonalEvent.js';
6
+ import type { EntryBillingEvent } from './entryEvent.js';
7
+ import type { BillingOperationBillingEvent } from './operationEvent.js';
8
+ import type { PortalBillingEvent } from './portalEvent.js';
9
+ import type { ResubscribeBillingEvent } from './resubscribeEvent.js';
10
+ import type { SubscriptionCheckoutBillingEvent } from './subscriptionCheckoutEvent.js';
11
+ import type { TopupBillingEvent } from './topupEvent.js';
12
+ import type { WebEntryBillingEvent } from './webEntryEvent.js';
13
+ import type { WebHandoffBillingEvent } from './webHandoffEvent.js';
14
+ import type { WebReturnBillingEvent } from './webReturnEvent.js';
15
+ import type { WebSessionBillingEvent } from './webSessionEvent.js';
16
+ export type BillingSurface = 'cloud_app' | 'billing_web';
17
+ export type BillingClient = 'sdk' | 'legacy';
18
+ export type BillingTelemetryEvent = {
19
+ /** The rail of the code that emitted the event; absent when the emitter does not know it. */
20
+ billing_client?: BillingClient;
21
+ } & (CapabilityReadBillingEvent | SubscriptionCheckoutBillingEvent | BillingOperationBillingEvent | ResubscribeBillingEvent | TopupBillingEvent | DowngradeToPersonalBillingEvent | WebEntryBillingEvent | WebHandoffBillingEvent | WebReturnBillingEvent | WebSessionBillingEvent | EntryBillingEvent | CancelBillingEvent | PortalBillingEvent | CheckoutChallengeBillingEvent | CheckoutRedirectBillingEvent);
22
+ type BillingTelemetryEventNameFor<T extends BillingTelemetryEvent> = T extends BillingTelemetryEvent ? `billing.${T['operation']}.${T['stage']}` : never;
23
+ export type BillingTelemetryEventName = BillingTelemetryEventNameFor<BillingTelemetryEvent>;
24
+ export declare function getBillingTelemetryEventName(event: BillingTelemetryEvent): BillingTelemetryEventName;
25
+ export {};
@@ -0,0 +1,3 @@
1
+ export function getBillingTelemetryEventName(event) {
2
+ return `billing.${event.operation}.${event.stage}`;
3
+ }
@@ -0,0 +1,19 @@
1
+ import type { BillingFailed, BillingIntent } from './stages.js';
2
+ import type { BillingCycle, SubscriptionCheckoutTier } from './vocabulary.js';
3
+ type CancelAbandoned = {
4
+ stage: 'abandoned';
5
+ outcome: 'pending';
6
+ };
7
+ /**
8
+ * The customer's journey through a cancel flow, on any surface. The cancel
9
+ * request itself is `operation` events with `operation_type: 'cancel'`, so a
10
+ * flow ends in exactly one of those terminals, `abandoned`, or `failed` for a
11
+ * refusal before any operation exists.
12
+ */
13
+ export type CancelBillingEvent = {
14
+ operation: 'cancel';
15
+ /** The plan being cancelled; absent when unknown. */
16
+ current_tier?: SubscriptionCheckoutTier;
17
+ cycle?: BillingCycle;
18
+ } & (BillingIntent | CancelAbandoned | BillingFailed);
19
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,4 @@
1
+ import type { BillingFailed, BillingSucceeded } from './stages.js';
2
+ export type CapabilityReadBillingEvent = {
3
+ operation: 'capability_read';
4
+ } & (BillingSucceeded | Pick<BillingFailed, 'stage' | 'outcome'>);
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,25 @@
1
+ import type { BillingDeclineReason, BillingOperationKind, BillingPresentation } from '../operationState.js';
2
+ /**
3
+ * Payment friction inside one operation: a bank challenge the customer must
4
+ * complete, the verdict this tab saw, and each retryable decline the server
5
+ * reports while the operation stays open. None of these settle the
6
+ * operation, so every stage stays `pending`. A failure with a
7
+ * `decline_reason` is the server's retryable decline; one without is this
8
+ * tab's own challenge verdict.
9
+ */
10
+ export type CheckoutChallengeBillingEvent = {
11
+ operation: 'checkout';
12
+ outcome: 'pending';
13
+ billing_op_id: string;
14
+ operation_type: BillingOperationKind;
15
+ presentation: BillingPresentation;
16
+ /** True when this tab reattached to an operation it did not issue. */
17
+ resumed?: boolean;
18
+ } & ({
19
+ stage: 'challenge_required';
20
+ } | {
21
+ stage: 'challenge_completed';
22
+ } | {
23
+ stage: 'challenge_failed';
24
+ decline_reason?: BillingDeclineReason;
25
+ });
@@ -0,0 +1,176 @@
1
+ import type { CapabilityDenialReason } from '../capabilityDenials.js';
2
+ import type { BillingSurface } from './billingTelemetryEvent.js';
3
+ import type { BillingTelemetryErrorCode, BillingTelemetryFailureCategory } from './stages.js';
4
+ import type { PaymentIntentSource } from './vocabulary.js';
5
+ /**
6
+ * Checkout-journey lifecycle events for the embedded-checkout rollout.
7
+ *
8
+ * These intermediate stages are kept deliberately separate from the terminal
9
+ * billing taxonomy above (`billing.<operation>.<stage>`): entry, preview, and
10
+ * Payment Element observations are client observations of progress, never
11
+ * business success/failure/timeout. They share one frozen journey context so
12
+ * the two rollout arms can be compared on the same denominator.
13
+ */
14
+ export declare const CHECKOUT_JOURNEY_SCHEMA_VERSION = 1;
15
+ export type CheckoutJourneyArm = 'control' | 'treatment';
16
+ export type CheckoutAssignmentStatus = 'resolved' | 'unavailable';
17
+ export type CheckoutUiMode = 'embedded' | 'full_page' | 'hosted' | 'unknown';
18
+ export type CheckoutEntryFlow = 'initial_subscription' | 'paid_upgrade' | 'topup' | 'other' | 'unknown';
19
+ export type CheckoutEntrySource = 'pricing' | 'deep_link' | 'recovery' | 'settings_billing' | 'other' | 'unknown' | 'agent_paywall';
20
+ type CheckoutElementPhase = 'init' | 'mount' | 'update';
21
+ /** Which Stripe element in the shared group the observation came from. */
22
+ type CheckoutElementKind = 'payment' | 'address';
23
+ type CheckoutSubmitPhase = 'validation' | 'token_creation';
24
+ /**
25
+ * The frozen arm assignment. A resolved assignment always carries an arm; an
26
+ * unavailable one never does, so an unknown assignment cannot masquerade as a
27
+ * resolved `control`. Encoded as a discriminated union so the invariant is a
28
+ * compile-time guarantee rather than a convention.
29
+ */
30
+ type CheckoutJourneyAssignment = {
31
+ assignment_status: 'resolved';
32
+ assigned_arm: CheckoutJourneyArm;
33
+ } | {
34
+ assignment_status: 'unavailable';
35
+ assigned_arm?: never;
36
+ };
37
+ /**
38
+ * Non-sensitive entry context frozen at journey creation and replayed on every
39
+ * journey event.
40
+ */
41
+ export type CheckoutJourneyContext = {
42
+ checkout_journey_id: string;
43
+ /** UTC ISO-8601 timestamp captured at common intent, preserved across reload. */
44
+ checkout_entered_at: string;
45
+ ui_mode?: CheckoutUiMode;
46
+ entry_flow: CheckoutEntryFlow;
47
+ entry_source: CheckoutEntrySource;
48
+ billing_op_id?: string;
49
+ } & CheckoutJourneyAssignment;
50
+ type CheckoutJourneyEntered = {
51
+ phase: 'entered';
52
+ /** The click-time entry the link carried, at the shared source grain. */
53
+ payment_intent_source?: PaymentIntentSource;
54
+ };
55
+ type CheckoutJourneyPreviewReady = {
56
+ phase: 'preview_ready';
57
+ preview_revision?: string;
58
+ };
59
+ /** Refusals the checkout names itself, beside the codes the billing events share. */
60
+ type CheckoutPreviewErrorCode = 'quote_not_allowed' | 'plan_unavailable';
61
+ type CheckoutJourneyPreviewFailed = {
62
+ phase: 'preview_failed';
63
+ failure_category: BillingTelemetryFailureCategory;
64
+ error_code?: BillingTelemetryErrorCode | CheckoutPreviewErrorCode;
65
+ /** Why the capabilities read refused this checkout. */
66
+ denial_reason?: CapabilityDenialReason;
67
+ preview_revision?: string;
68
+ };
69
+ type CheckoutJourneyPaymentElementReady = {
70
+ phase: 'payment_element_ready';
71
+ element: CheckoutElementKind;
72
+ };
73
+ type CheckoutJourneyPaymentElementFailed = {
74
+ phase: 'payment_element_failed';
75
+ element: CheckoutElementKind;
76
+ element_phase: CheckoutElementPhase;
77
+ error_code?: string;
78
+ };
79
+ type CheckoutJourneyPaymentSubmitAttempted = {
80
+ phase: 'payment_submit_attempted';
81
+ };
82
+ type CheckoutJourneyPaymentSubmitFailed = {
83
+ phase: 'payment_submit_failed';
84
+ submit_phase: CheckoutSubmitPhase;
85
+ error_code?: string;
86
+ };
87
+ type CheckoutPaymentRail = 'saved' | 'new' | 'on_file';
88
+ export type CheckoutMethodKind = 'card' | 'alipay' | 'other';
89
+ type CheckoutPromoResult = 'applied' | 'rejected' | 'removed' | 'expired';
90
+ type CheckoutPayBlockedReason = 'reactivation_unconfirmed' | 'promo_unapplied';
91
+ type CheckoutJourneyMethodSelected = {
92
+ phase: 'method_selected';
93
+ rail: CheckoutPaymentRail;
94
+ method_kind?: CheckoutMethodKind;
95
+ };
96
+ type CheckoutJourneyPromo = {
97
+ phase: 'promo';
98
+ result: CheckoutPromoResult;
99
+ /** Whether the entry link carried the code; the code itself is never reported. */
100
+ prefilled: boolean;
101
+ };
102
+ type CheckoutJourneyPayBlocked = {
103
+ phase: 'pay_blocked';
104
+ reason: CheckoutPayBlockedReason;
105
+ };
106
+ type CheckoutJourneySubmitted = {
107
+ phase: 'submitted';
108
+ };
109
+ type CheckoutJourneyOperationLinked = {
110
+ phase: 'operation_linked';
111
+ billing_op_id: string;
112
+ };
113
+ type CheckoutJourneyProgressEvent = CheckoutJourneyEntered | CheckoutJourneyPreviewReady | CheckoutJourneyPreviewFailed | CheckoutJourneyPaymentElementReady | CheckoutJourneyPaymentElementFailed | CheckoutJourneyPaymentSubmitAttempted | CheckoutJourneyPaymentSubmitFailed | CheckoutJourneyMethodSelected | CheckoutJourneyPromo | CheckoutJourneyPayBlocked | CheckoutJourneySubmitted | CheckoutJourneyOperationLinked;
114
+ /** How the customer left: the page went away, a control of this page led out, or the cloud app's checkout dialog closed. */
115
+ export type CheckoutExit = 'page_exit' | 'back' | 'close' | 'host_link' | 'dialog_close';
116
+ /** The ending screens of the full-page checkout. */
117
+ export type CheckoutEndingKind = 'success' | 'completed' | 'already_completed' | 'in_progress' | 'received' | 'unconfirmed' | 'refused' | 'plan_unavailable' | 'link_invalid' | 'load_failed';
118
+ /** Whose payment an ending shows: this page's Pay, a return, one it followed, or one already settled. */
119
+ export type CheckoutEndingAttribution = 'started' | 'returned' | 'followed' | 'settled';
120
+ /** The customer left before the checkout reached an ending. */
121
+ type CheckoutJourneyAbandoned = {
122
+ phase: 'abandoned';
123
+ last_phase: CheckoutJourneyProgressEvent['phase'];
124
+ exit: CheckoutExit;
125
+ };
126
+ type CheckoutJourneyEnded = {
127
+ phase: 'ended';
128
+ ending_kind: CheckoutEndingKind;
129
+ attribution?: CheckoutEndingAttribution;
130
+ };
131
+ export type CheckoutJourneyPhaseEvent = CheckoutJourneyProgressEvent | CheckoutJourneyAbandoned | CheckoutJourneyEnded;
132
+ export type CheckoutJourneyPhase = CheckoutJourneyPhaseEvent['phase'];
133
+ export type CheckoutJourneyTelemetryEvent = CheckoutJourneyContext & CheckoutJourneyPhaseEvent;
134
+ export type CheckoutJourneyTelemetryEventName = `billing.checkout.${CheckoutJourneyPhase}`;
135
+ /**
136
+ * The wire name for every phase. Typed as a total `Record` over the phase
137
+ * union, so a phase added to the union without a name here fails to compile —
138
+ * and so the runtime list below can never drift from the emitted names.
139
+ */
140
+ export declare const CHECKOUT_JOURNEY_EVENT_NAME_BY_PHASE: Record<CheckoutJourneyPhase, CheckoutJourneyTelemetryEventName>;
141
+ export declare function getCheckoutJourneyTelemetryEventName(event: CheckoutJourneyTelemetryEvent): CheckoutJourneyTelemetryEventName;
142
+ export declare function getCheckoutJourneyTelemetryEventPayload(event: CheckoutJourneyTelemetryEvent): {
143
+ attribution?: CheckoutEndingAttribution | undefined;
144
+ ending_kind?: CheckoutEndingKind | undefined;
145
+ exit?: CheckoutExit | undefined;
146
+ last_phase?: "entered" | "preview_ready" | "preview_failed" | "payment_element_ready" | "payment_element_failed" | "payment_submit_attempted" | "payment_submit_failed" | "method_selected" | "promo" | "pay_blocked" | "submitted" | "operation_linked" | undefined;
147
+ reason?: CheckoutPayBlockedReason | undefined;
148
+ prefilled?: boolean | undefined;
149
+ result?: CheckoutPromoResult | undefined;
150
+ method_kind?: CheckoutMethodKind | undefined;
151
+ rail?: CheckoutPaymentRail | undefined;
152
+ payment_intent_source?: PaymentIntentSource | undefined;
153
+ submit_phase?: CheckoutSubmitPhase | undefined;
154
+ element_phase?: CheckoutElementPhase | undefined;
155
+ element?: CheckoutElementKind | undefined;
156
+ denial_reason?: CapabilityDenialReason | undefined;
157
+ error_code?: string | undefined;
158
+ failure_category?: BillingTelemetryFailureCategory | undefined;
159
+ preview_revision?: string | undefined;
160
+ billing_op_id?: string | undefined;
161
+ ui_mode?: CheckoutUiMode | undefined;
162
+ assigned_arm?: CheckoutJourneyArm | undefined;
163
+ schema_version: number;
164
+ phase: "ended" | "abandoned" | "entered" | "preview_ready" | "preview_failed" | "payment_element_ready" | "payment_element_failed" | "payment_submit_attempted" | "payment_submit_failed" | "method_selected" | "promo" | "pay_blocked" | "submitted" | "operation_linked";
165
+ checkout_journey_id: string;
166
+ checkout_entered_at: string;
167
+ assignment_status: "unavailable" | "resolved";
168
+ entry_flow: CheckoutEntryFlow;
169
+ entry_source: CheckoutEntrySource;
170
+ };
171
+ export type CheckoutJourneyTelemetryEventPayload = ReturnType<typeof getCheckoutJourneyTelemetryEventPayload>;
172
+ /** Only the cloud build registers the sinks that call this. */
173
+ export declare function getCloudAppCheckoutJourneyTelemetryEventPayload(event: CheckoutJourneyTelemetryEvent): CheckoutJourneyTelemetryEventPayload & {
174
+ billing_surface: BillingSurface;
175
+ };
176
+ export {};
@@ -0,0 +1,115 @@
1
+ /**
2
+ * Checkout-journey lifecycle events for the embedded-checkout rollout.
3
+ *
4
+ * These intermediate stages are kept deliberately separate from the terminal
5
+ * billing taxonomy above (`billing.<operation>.<stage>`): entry, preview, and
6
+ * Payment Element observations are client observations of progress, never
7
+ * business success/failure/timeout. They share one frozen journey context so
8
+ * the two rollout arms can be compared on the same denominator.
9
+ */
10
+ export const CHECKOUT_JOURNEY_SCHEMA_VERSION = 1;
11
+ /**
12
+ * The wire name for every phase. Typed as a total `Record` over the phase
13
+ * union, so a phase added to the union without a name here fails to compile —
14
+ * and so the runtime list below can never drift from the emitted names.
15
+ */
16
+ export const CHECKOUT_JOURNEY_EVENT_NAME_BY_PHASE = {
17
+ entered: 'billing.checkout.entered',
18
+ preview_ready: 'billing.checkout.preview_ready',
19
+ preview_failed: 'billing.checkout.preview_failed',
20
+ payment_element_ready: 'billing.checkout.payment_element_ready',
21
+ payment_element_failed: 'billing.checkout.payment_element_failed',
22
+ payment_submit_attempted: 'billing.checkout.payment_submit_attempted',
23
+ payment_submit_failed: 'billing.checkout.payment_submit_failed',
24
+ method_selected: 'billing.checkout.method_selected',
25
+ promo: 'billing.checkout.promo',
26
+ pay_blocked: 'billing.checkout.pay_blocked',
27
+ submitted: 'billing.checkout.submitted',
28
+ operation_linked: 'billing.checkout.operation_linked',
29
+ abandoned: 'billing.checkout.abandoned',
30
+ ended: 'billing.checkout.ended'
31
+ };
32
+ export function getCheckoutJourneyTelemetryEventName(event) {
33
+ return CHECKOUT_JOURNEY_EVENT_NAME_BY_PHASE[event.phase];
34
+ }
35
+ function getContextPayload(event) {
36
+ return {
37
+ schema_version: CHECKOUT_JOURNEY_SCHEMA_VERSION,
38
+ phase: event.phase,
39
+ checkout_journey_id: event.checkout_journey_id,
40
+ checkout_entered_at: event.checkout_entered_at,
41
+ assignment_status: event.assignment_status,
42
+ entry_flow: event.entry_flow,
43
+ entry_source: event.entry_source,
44
+ ...(event.assigned_arm !== undefined && {
45
+ assigned_arm: event.assigned_arm
46
+ }),
47
+ ...(event.ui_mode !== undefined && { ui_mode: event.ui_mode }),
48
+ ...(event.billing_op_id !== undefined && {
49
+ billing_op_id: event.billing_op_id
50
+ })
51
+ };
52
+ }
53
+ function getPreviewPayload(event) {
54
+ return {
55
+ ...('preview_revision' in event &&
56
+ event.preview_revision !== undefined && {
57
+ preview_revision: event.preview_revision
58
+ }),
59
+ ...('failure_category' in event && {
60
+ failure_category: event.failure_category
61
+ }),
62
+ ...('error_code' in event &&
63
+ event.error_code !== undefined && { error_code: event.error_code }),
64
+ ...('denial_reason' in event &&
65
+ event.denial_reason !== undefined && {
66
+ denial_reason: event.denial_reason
67
+ })
68
+ };
69
+ }
70
+ function getPaymentFormPayload(event) {
71
+ return {
72
+ ...('element' in event && { element: event.element }),
73
+ ...('element_phase' in event && { element_phase: event.element_phase }),
74
+ ...('submit_phase' in event && { submit_phase: event.submit_phase })
75
+ };
76
+ }
77
+ function getChoicePayload(event) {
78
+ return {
79
+ ...('payment_intent_source' in event &&
80
+ event.payment_intent_source !== undefined && {
81
+ payment_intent_source: event.payment_intent_source
82
+ }),
83
+ ...('rail' in event && { rail: event.rail }),
84
+ ...('method_kind' in event &&
85
+ event.method_kind !== undefined && { method_kind: event.method_kind }),
86
+ ...('result' in event && { result: event.result }),
87
+ ...('prefilled' in event && { prefilled: event.prefilled }),
88
+ ...('reason' in event && { reason: event.reason })
89
+ };
90
+ }
91
+ function getExitPayload(event) {
92
+ return {
93
+ ...('last_phase' in event && { last_phase: event.last_phase }),
94
+ ...('exit' in event && { exit: event.exit }),
95
+ ...('ending_kind' in event && { ending_kind: event.ending_kind }),
96
+ ...('attribution' in event &&
97
+ event.attribution !== undefined && { attribution: event.attribution })
98
+ };
99
+ }
100
+ export function getCheckoutJourneyTelemetryEventPayload(event) {
101
+ return {
102
+ ...getContextPayload(event),
103
+ ...getPreviewPayload(event),
104
+ ...getPaymentFormPayload(event),
105
+ ...getChoicePayload(event),
106
+ ...getExitPayload(event)
107
+ };
108
+ }
109
+ /** Only the cloud build registers the sinks that call this. */
110
+ export function getCloudAppCheckoutJourneyTelemetryEventPayload(event) {
111
+ return {
112
+ ...getCheckoutJourneyTelemetryEventPayload(event),
113
+ billing_surface: 'cloud_app'
114
+ };
115
+ }