@aforoai/storefront-widgets 1.0.1
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/CHANGELOG.md +327 -0
- package/LICENSE +21 -0
- package/README.md +249 -0
- package/SECURITY.md +138 -0
- package/dist/AforoEmbed-BqQOFsl3.d.cts +67 -0
- package/dist/AforoEmbed-CmqjlYlt.d.ts +67 -0
- package/dist/index.cjs +12320 -0
- package/dist/index.d.cts +1228 -0
- package/dist/index.d.ts +1228 -0
- package/dist/index.mjs +12286 -0
- package/dist/loader.js +3 -0
- package/dist/loader.mjs +3 -0
- package/dist/sri.json +15 -0
- package/dist/types-Dm6cp_Cc.d.cts +1185 -0
- package/dist/types-Dm6cp_Cc.d.ts +1185 -0
- package/dist/vanilla/index.cjs +12567 -0
- package/dist/vanilla/index.d.cts +22 -0
- package/dist/vanilla/index.d.ts +22 -0
- package/dist/vanilla/index.mjs +12543 -0
- package/dist/vue/index.cjs +12634 -0
- package/dist/vue/index.d.cts +157 -0
- package/dist/vue/index.d.ts +157 -0
- package/dist/vue/index.mjs +12605 -0
- package/dist/widgets/checkout-flow.js +55 -0
- package/dist/widgets/invoice-list.js +60 -0
- package/dist/widgets/payment-method.js +55 -0
- package/dist/widgets/pricing-card.js +60 -0
- package/dist/widgets/subscribe-button.js +61 -0
- package/dist/widgets/subscription-manager.js +55 -0
- package/dist/widgets/upgrade-cancel.js +55 -0
- package/dist/widgets/usage-meter.js +55 -0
- package/loader.sri.txt +1 -0
- package/package.json +118 -0
|
@@ -0,0 +1,1185 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local types used internally by the storefront-widgets SDK. Most public
|
|
3
|
+
* types come from `@aforoai/types/embed-studio` (e.g. EmbedKey,
|
|
4
|
+
* EmbedKeyEnvironment) — anything customer-facing belongs there.
|
|
5
|
+
*
|
|
6
|
+
* Types in this file MAY be re-exported from `src/index.ts` if they form
|
|
7
|
+
* part of the locked public surface; check `tests/sdk-contract.test.ts`.
|
|
8
|
+
*/
|
|
9
|
+
/** Canonical 8 widget ids served by Prompts 4–7. */
|
|
10
|
+
type WidgetId = 'pricing-card' | 'subscribe-button' | 'checkout-flow' | 'subscription-manager' | 'invoice-list' | 'usage-meter' | 'payment-method' | 'upgrade-cancel';
|
|
11
|
+
/** Event payload envelope. `version` allows non-breaking evolution. */
|
|
12
|
+
interface AforoEventPayload<T = unknown> {
|
|
13
|
+
type: string;
|
|
14
|
+
payload: T;
|
|
15
|
+
version: '1';
|
|
16
|
+
/**
|
|
17
|
+
* Optional widget id the event originated from. Set automatically by
|
|
18
|
+
* widget components; not required for parent-to-widget messages.
|
|
19
|
+
*/
|
|
20
|
+
source?: WidgetId;
|
|
21
|
+
/**
|
|
22
|
+
* Optional monotonic id for diagnostics. Auto-stamped by EventBus.
|
|
23
|
+
*/
|
|
24
|
+
emittedAt?: number;
|
|
25
|
+
}
|
|
26
|
+
/** Known event types (extended in Prompts 4-7 as widgets ship). */
|
|
27
|
+
type AforoEventType = `aforo.${WidgetId}.ready` | `aforo.${WidgetId}.error` | 'aforo.session.refreshed' | 'aforo.session.cleared' | 'aforo.session.magic_link_requested' | 'aforo.session.magic_link_verified' | 'aforo.session.magic_link_failed' | 'aforo.theme.changed' | 'aforo.subscribe-button.ready' | 'aforo.subscribe-button.disabled' | 'aforo.subscribe.checkout_requested' | 'aforo.subscription.created' | 'aforo.subscribe.error' | 'aforo.invoice-list.invoice.downloaded' | 'aforo.invoice-list.pay_requested' | 'aforo.invoice-list.invoice.expanded' | 'aforo.invoice-list.filter_changed' | 'aforo.invoice-list.search_changed' | 'aforo.invoice-list.invoice.paid' | 'aforo.checkout-flow.step_changed' | 'aforo.checkout-flow.customer_details_submitted' | 'aforo.checkout-flow.payment_initiated' | 'aforo.checkout-flow.payment_completed' | 'aforo.checkout-flow.confirmed' | 'aforo.checkout-flow.cancelled' | 'aforo.checkout-flow.expired' | 'aforo.subscription-manager.subscription.clicked' | 'aforo.subscription-manager.upgrade_requested' | 'aforo.subscription-manager.cancel_requested' | 'aforo.subscription-manager.filter_changed' | 'aforo.usage-meter.metric_clicked' | 'aforo.usage-meter.threshold_reached' | 'aforo.upgrade-cancel.step_changed' | 'aforo.upgrade-cancel.preview_fetched' | 'aforo.upgrade-cancel.deflection_offer_shown' | 'aforo.upgrade-cancel.deflection_offer_accepted' | 'aforo.upgrade-cancel.completed' | 'aforo.upgrade-cancel.abandoned' | 'aforo.payment-method.update_requested' | 'aforo.payment-method.method_set_default' | 'aforo.payment-method.method_removed' | 'aforo.payment-method.updated' | string;
|
|
28
|
+
/** Theme tokens read by widgets. */
|
|
29
|
+
interface ThemeTokens {
|
|
30
|
+
primary: string;
|
|
31
|
+
primaryContrast: string;
|
|
32
|
+
text: string;
|
|
33
|
+
textMuted: string;
|
|
34
|
+
bg: string;
|
|
35
|
+
border: string;
|
|
36
|
+
radius: string;
|
|
37
|
+
fontFamily: string;
|
|
38
|
+
/** Source tier — useful for debugging which layer of the cascade applied. */
|
|
39
|
+
source: 'css-variables' | 'brand-kit' | 'neutral-default' | 'overrides';
|
|
40
|
+
}
|
|
41
|
+
interface ThemeTokenOverrides {
|
|
42
|
+
primary?: string;
|
|
43
|
+
primaryContrast?: string;
|
|
44
|
+
text?: string;
|
|
45
|
+
textMuted?: string;
|
|
46
|
+
bg?: string;
|
|
47
|
+
border?: string;
|
|
48
|
+
radius?: string;
|
|
49
|
+
fontFamily?: string;
|
|
50
|
+
}
|
|
51
|
+
interface SessionConfig {
|
|
52
|
+
tenantSlug: string;
|
|
53
|
+
embedKey: string;
|
|
54
|
+
/** Pre-issued customer bridge token (JWT). Optional for anonymous. */
|
|
55
|
+
bridgeToken?: string;
|
|
56
|
+
/**
|
|
57
|
+
* Magic-link auth mode (Prompt 9 / FR-AUTH-6). When `true`, the session
|
|
58
|
+
* lazy-initialises by consuming an {@code aforo_magic_token} URL param on
|
|
59
|
+
* mount. Mutually exclusive with {@link bridgeToken} — when both are set,
|
|
60
|
+
* {@link bridgeToken} wins (anonymous host portals + magic-link callers
|
|
61
|
+
* naturally only ever set one).
|
|
62
|
+
*/
|
|
63
|
+
magicLinkMode?: boolean;
|
|
64
|
+
/** Override the default `https://embed.aforo.ai` origin. Tests + dev. */
|
|
65
|
+
apiBaseUrl?: string;
|
|
66
|
+
/** Override embed-key authenticated endpoint base. */
|
|
67
|
+
embedBaseUrl?: string;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Body for {@code AforoSession.requestMagicLink} / {@code BridgeClient.requestMagicLink}.
|
|
71
|
+
* Mirrors the storefront-service {@code RequestMagicLinkRequest} DTO.
|
|
72
|
+
*/
|
|
73
|
+
interface RequestMagicLinkRequest {
|
|
74
|
+
email: string;
|
|
75
|
+
externalId?: string;
|
|
76
|
+
returnUrl: string;
|
|
77
|
+
}
|
|
78
|
+
/** Always {@code {accepted: true}} on the wire — anti-enumeration. */
|
|
79
|
+
interface RequestMagicLinkResponse {
|
|
80
|
+
accepted: boolean;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Success envelope from {@code POST /api/v1/portal/embed/auth/magic-link/verify}.
|
|
84
|
+
* Mirrors the storefront-service {@code VerifyMagicLinkResponse} DTO.
|
|
85
|
+
*/
|
|
86
|
+
interface VerifyMagicLinkResponse {
|
|
87
|
+
sessionJwt: string;
|
|
88
|
+
expiresIn: number;
|
|
89
|
+
customerId: string;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Typed error code emitted via {@code aforo.session.magic_link_failed}.
|
|
93
|
+
* {@code INVALID_OR_EXPIRED} maps to the backend's generic 401 envelope
|
|
94
|
+
* (anti-enumeration: not-found / consumed / expired all collapse). Network
|
|
95
|
+
* failures and configuration gaps surface as distinct codes so widgets can
|
|
96
|
+
* branch on retry-ability.
|
|
97
|
+
*/
|
|
98
|
+
type MagicLinkFailureCode = 'INVALID_OR_EXPIRED' | 'NO_CUSTOMER_FOR_EMAIL' | 'NETWORK_ERROR' | 'CONFIG_MISSING';
|
|
99
|
+
interface SessionState {
|
|
100
|
+
sessionJwt: string | null;
|
|
101
|
+
expiresAt: number | null;
|
|
102
|
+
tenantSlug: string;
|
|
103
|
+
customerId: string | null;
|
|
104
|
+
}
|
|
105
|
+
interface WidgetMountConfig {
|
|
106
|
+
widget: WidgetId;
|
|
107
|
+
tenantSlug: string;
|
|
108
|
+
embedKey: string;
|
|
109
|
+
bridgeToken?: string;
|
|
110
|
+
layout?: string;
|
|
111
|
+
themeOverrides?: ThemeTokenOverrides;
|
|
112
|
+
/**
|
|
113
|
+
* Additional widget-specific config — passed as a Record so each widget
|
|
114
|
+
* shell can validate its own subset.
|
|
115
|
+
*/
|
|
116
|
+
config?: Record<string, unknown>;
|
|
117
|
+
}
|
|
118
|
+
interface TenantBrandKit {
|
|
119
|
+
primaryColor?: string | null;
|
|
120
|
+
secondaryColor?: string | null;
|
|
121
|
+
logoUrl?: string | null;
|
|
122
|
+
fontFamily?: string | null;
|
|
123
|
+
}
|
|
124
|
+
/** Inferred consent posture. Set via parent `aforo:consent-state` message. */
|
|
125
|
+
interface ConsentState {
|
|
126
|
+
telemetry: boolean;
|
|
127
|
+
marketing: boolean;
|
|
128
|
+
}
|
|
129
|
+
/** A single feature row inside a plan card. */
|
|
130
|
+
interface FeaturePayload {
|
|
131
|
+
/** Stable id — used as React key when rendering. */
|
|
132
|
+
id?: string;
|
|
133
|
+
/** Visible label. */
|
|
134
|
+
label: string;
|
|
135
|
+
/** Optional cell value for the comparison-table layout (e.g. "Unlimited"). */
|
|
136
|
+
value?: string;
|
|
137
|
+
/** Truthy → row renders ✓ ; falsy → row renders × (table layout). */
|
|
138
|
+
included?: boolean;
|
|
139
|
+
/** Optional explanation surfaced via `title` attribute / aria-describedby. */
|
|
140
|
+
description?: string;
|
|
141
|
+
}
|
|
142
|
+
/** Per-tier pricing breakpoint (for graduated/tiered models — display only). */
|
|
143
|
+
interface RatePlanPayload {
|
|
144
|
+
ratePlanId: string;
|
|
145
|
+
/** Display unit, e.g. "API call", "GB-hour". */
|
|
146
|
+
unitLabel?: string;
|
|
147
|
+
/** Free quota included before per-unit pricing kicks in. */
|
|
148
|
+
includedUnits?: number | null;
|
|
149
|
+
/** Per-unit price in minor units (cents/paise). */
|
|
150
|
+
perUnitPriceCents?: number | null;
|
|
151
|
+
/** Tiered model identifier — operator-set, surfaced to telemetry. */
|
|
152
|
+
pricingModel?: string;
|
|
153
|
+
}
|
|
154
|
+
/** A single offering as rendered in the pricing card. */
|
|
155
|
+
interface OfferingPayload {
|
|
156
|
+
offeringId: string;
|
|
157
|
+
/** Human-visible name (e.g. "Pro", "Enterprise"). */
|
|
158
|
+
name: string;
|
|
159
|
+
/** Optional localized name override (FR-WIDGET-PC-9). */
|
|
160
|
+
localizedDisplayName?: string;
|
|
161
|
+
/** Optional short subtitle. */
|
|
162
|
+
description?: string;
|
|
163
|
+
/** Optional localized description override (FR-WIDGET-PC-9). */
|
|
164
|
+
localizedDescription?: string;
|
|
165
|
+
/** ISO 4217 currency code, e.g. "USD". */
|
|
166
|
+
currency: string;
|
|
167
|
+
/** Subscription price in MINOR units (cents/paise). NaN/undefined → "Contact us". */
|
|
168
|
+
priceCents: number | null | undefined;
|
|
169
|
+
/** Billing period — `monthly` / `annual` / `one_time` / `custom`. */
|
|
170
|
+
billingCycle: string;
|
|
171
|
+
/** Free-form CTA label (e.g. "Start trial"). Operator may override per-offering. */
|
|
172
|
+
ctaText?: string;
|
|
173
|
+
/** Optional CTA url override (FR-WIDGET-PC-7). When unset the widget's ctaUrl prop wins. */
|
|
174
|
+
ctaUrl?: string;
|
|
175
|
+
/** Featured plan flag (FR-WIDGET-PC-8). */
|
|
176
|
+
featured?: boolean;
|
|
177
|
+
/** Render order — ascending. Stable per FR-WIDGET-PC-1. */
|
|
178
|
+
displayOrder?: number;
|
|
179
|
+
/** Plan status — only PUBLISHED offerings render. */
|
|
180
|
+
status?: 'DRAFT' | 'PUBLISHED' | 'ARCHIVED' | string;
|
|
181
|
+
/** Features list rendered inside the card. */
|
|
182
|
+
features?: FeaturePayload[];
|
|
183
|
+
/** Optional bound rate plan(s) — surfaced in the cta_clicked event payload. */
|
|
184
|
+
ratePlans?: RatePlanPayload[];
|
|
185
|
+
/** Primary rate plan id for telemetry + the cta_clicked envelope. */
|
|
186
|
+
primaryRatePlanId?: string;
|
|
187
|
+
/** Free-form metadata bag — kept opaque, surfaced verbatim in events. */
|
|
188
|
+
metadata?: Record<string, unknown>;
|
|
189
|
+
}
|
|
190
|
+
/** Branding block in the headless config (intersects TenantBrandKit). */
|
|
191
|
+
interface HeadlessBrandingPayload {
|
|
192
|
+
primaryColor?: string | null;
|
|
193
|
+
secondaryColor?: string | null;
|
|
194
|
+
logoUrl?: string | null;
|
|
195
|
+
fontFamily?: string | null;
|
|
196
|
+
/** Optional contact for the "Contact sales" fallback CTA. */
|
|
197
|
+
contactSalesEmail?: string | null;
|
|
198
|
+
}
|
|
199
|
+
/** Top-level shape returned by `GET /api/v1/portal/headless/config`. */
|
|
200
|
+
interface HeadlessConfigResponse {
|
|
201
|
+
/** Tenant slug echo. */
|
|
202
|
+
tenantSlug?: string;
|
|
203
|
+
/** Display name (rendered in operator-empty-state copy). */
|
|
204
|
+
tenantName?: string | null;
|
|
205
|
+
branding?: HeadlessBrandingPayload | null;
|
|
206
|
+
/** Ordered list of PUBLISHED offerings. */
|
|
207
|
+
offerings?: OfferingPayload[];
|
|
208
|
+
/** Locale hint from operator config — widget's prop still wins. */
|
|
209
|
+
defaultLocale?: string | null;
|
|
210
|
+
/**
|
|
211
|
+
* 2026-06-01 — Tenant-wide defaults for embeddable widgets, authored in
|
|
212
|
+
* the admin `EmbeddableWidgetTab`. Read by widgets at mount time as
|
|
213
|
+
* fallback defaults; the customer's mount config (`<script>` data-*
|
|
214
|
+
* attributes) still wins on a per-widget basis.
|
|
215
|
+
*
|
|
216
|
+
* Field shape mirrors `EmbeddableWidgetConfig` in `@aforoai/types`. Kept
|
|
217
|
+
* loose here (`Record<string, unknown>`) so the SDK doesn't have a hard
|
|
218
|
+
* compile dependency on the admin types — widgets pick the fields they
|
|
219
|
+
* recognise and ignore the rest.
|
|
220
|
+
*/
|
|
221
|
+
embeddableWidget?: EmbeddableWidgetDefaults | null;
|
|
222
|
+
/**
|
|
223
|
+
* 2026-06-01 — Up to 5 tenant-authored page-context-scoped CSS files.
|
|
224
|
+
* The storefont-ui's `<PageScopedStylesheets>` injector consumes these
|
|
225
|
+
* from `publishedConfig.customStylesheets`; we re-expose them here so
|
|
226
|
+
* headless third-party storefronts can replicate the same cascade in
|
|
227
|
+
* their own builds.
|
|
228
|
+
*/
|
|
229
|
+
customStylesheets?: HeadlessCustomStylesheet[] | null;
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Subset of {@link EmbeddableWidgetConfig} (in `@aforoai/types`) the SDK
|
|
233
|
+
* recognises. Loose typing — extra fields are dropped, missing fields fall
|
|
234
|
+
* back to widget-level defaults.
|
|
235
|
+
*/
|
|
236
|
+
interface EmbeddableWidgetDefaults {
|
|
237
|
+
layout?: 'horizontal' | 'vertical' | string;
|
|
238
|
+
theme?: 'light' | 'dark' | 'auto' | string;
|
|
239
|
+
ctaText?: string;
|
|
240
|
+
showFeatures?: boolean;
|
|
241
|
+
maxPlans?: number;
|
|
242
|
+
borderRadius?: number;
|
|
243
|
+
}
|
|
244
|
+
/** Page-context-scoped CSS stylesheet emitted by the headless config endpoint. */
|
|
245
|
+
interface HeadlessCustomStylesheet {
|
|
246
|
+
id: string;
|
|
247
|
+
name: string;
|
|
248
|
+
description?: string;
|
|
249
|
+
css: string;
|
|
250
|
+
enabled: boolean;
|
|
251
|
+
sortOrder?: number;
|
|
252
|
+
pageContext?: 'LANDING' | 'PRICING' | 'DOCS' | 'CATALOG' | 'PORTAL' | 'ALL' | string;
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Live-subscription status set the BFF reports back to the widget for
|
|
256
|
+
* anti-double-subscribe decisions. Mirrors backend pricing-service
|
|
257
|
+
* SubscriptionStatus enum (subset shown — only states that gate a
|
|
258
|
+
* re-subscribe attempt).
|
|
259
|
+
*/
|
|
260
|
+
type LiveSubscriptionStatus = 'ACTIVE' | 'TRIALING' | 'PAST_DUE' | 'EXPIRING_SOON';
|
|
261
|
+
/**
|
|
262
|
+
* Response shape for {@code GET /api/v1/portal/embed/subscriptions/by-offering/{offeringId}}.
|
|
263
|
+
*
|
|
264
|
+
* <p>{@code subscriptionId} + {@code status} are omitted when
|
|
265
|
+
* {@code hasActiveSubscription=false} — same shape as a legitimate "not
|
|
266
|
+
* subscribed" response, no existence-leak via differential field presence
|
|
267
|
+
* (Pattern #9).</p>
|
|
268
|
+
*/
|
|
269
|
+
interface SubscriptionForOfferingResponse {
|
|
270
|
+
hasActiveSubscription: boolean;
|
|
271
|
+
subscriptionId?: string;
|
|
272
|
+
status?: LiveSubscriptionStatus | string;
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* Request body for {@code POST /api/v1/portal/embed/checkout/initiate}.
|
|
276
|
+
*
|
|
277
|
+
* <p>Idempotency contract per FR-SEC-19: callers MUST supply a UUID v4 (or
|
|
278
|
+
* other RFC 4122 shape) in {@code idempotencyKey}; same key + same payload
|
|
279
|
+
* → cached response replay, same key + different payload → 409 Conflict.</p>
|
|
280
|
+
*/
|
|
281
|
+
interface InitiateCheckoutRequest {
|
|
282
|
+
offeringId: string;
|
|
283
|
+
/** UUID v4 — generated client-side per CTA click. */
|
|
284
|
+
idempotencyKey: string;
|
|
285
|
+
/**
|
|
286
|
+
* Where hosted checkout returns the customer to on success. Validated
|
|
287
|
+
* server-side against the embed key's {@code allowed_domains} list — host
|
|
288
|
+
* outside the allowlist returns 400 (defense vs. open-redirect).
|
|
289
|
+
*/
|
|
290
|
+
returnUrl: string;
|
|
291
|
+
/** Optional coupon code applied to the underlying checkout session. */
|
|
292
|
+
couponCode?: string;
|
|
293
|
+
/**
|
|
294
|
+
* Mode advisory — v1 always produces a real checkoutUrl; the
|
|
295
|
+
* {@code embedded-flow} mode is reserved for Prompt 7 (CheckoutFlow widget).
|
|
296
|
+
*/
|
|
297
|
+
mode?: 'redirect' | 'event-only' | 'embedded-flow';
|
|
298
|
+
}
|
|
299
|
+
/** Response shape for {@code POST /api/v1/portal/embed/checkout/initiate}. */
|
|
300
|
+
interface InitiateCheckoutResponse {
|
|
301
|
+
checkoutUrl: string;
|
|
302
|
+
checkoutSessionId: string;
|
|
303
|
+
expiresAt: string;
|
|
304
|
+
}
|
|
305
|
+
/**
|
|
306
|
+
* Payload for the {@code aforo.subscription.created} outbound event +
|
|
307
|
+
* the {@code onSubscribed} React callback. Backend webhook is the
|
|
308
|
+
* authoritative source for subscription state; this event is advisory.
|
|
309
|
+
*/
|
|
310
|
+
interface SubscriptionPayload {
|
|
311
|
+
subscriptionId: string;
|
|
312
|
+
offeringId: string;
|
|
313
|
+
/** Optional plan name for display. */
|
|
314
|
+
planName?: string;
|
|
315
|
+
/** ISO-4217 currency code echoed from offering. */
|
|
316
|
+
currency?: string;
|
|
317
|
+
/** Price in MINOR units (cents/paise) at the time of subscription. */
|
|
318
|
+
priceCents?: number | null;
|
|
319
|
+
/** Billing period at time of subscription. */
|
|
320
|
+
billingCycle?: string;
|
|
321
|
+
/** Status returned by the BFF after checkout-complete signal. */
|
|
322
|
+
status?: LiveSubscriptionStatus | string;
|
|
323
|
+
/** Server-side timestamp the subscription was created (ISO-8601). */
|
|
324
|
+
createdAt?: string;
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* Payload for the {@code aforo.subscribe.error} (and other widget error)
|
|
328
|
+
* events + the {@code onError} React callback. Surfaced when a fetch /
|
|
329
|
+
* checkout-initiate fails or when an inbound checkout-completed event
|
|
330
|
+
* carries an error state.
|
|
331
|
+
*/
|
|
332
|
+
interface WidgetErrorPayload {
|
|
333
|
+
/** Stable machine-readable code (e.g. SESSION_EXPIRED, NETWORK_ERROR). */
|
|
334
|
+
code: string;
|
|
335
|
+
/** Human-readable message — suitable for surfacing in UI banners. */
|
|
336
|
+
message: string;
|
|
337
|
+
/** HTTP status when the error originated from a network call. */
|
|
338
|
+
status?: number;
|
|
339
|
+
/** Whether the operation is safe to retry (e.g. transient 5xx / timeout). */
|
|
340
|
+
retryable?: boolean;
|
|
341
|
+
/** Optional widget id for cross-event correlation. */
|
|
342
|
+
widget?: WidgetId;
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
345
|
+
* Customer-facing status vocabulary for the AforoInvoiceList widget. The
|
|
346
|
+
* upstream {@code Invoice.status} enum (DRAFT/PENDING/OPEN/PAID/OVERDUE/
|
|
347
|
+
* CANCELLED/VOID/UNCOLLECTIBLE) is normalised to this 4-state set at the
|
|
348
|
+
* BFF slimming boundary — DRAFT invoices are filtered out, and
|
|
349
|
+
* CANCELLED/UNCOLLECTIBLE/VOID collapse to {@code VOID}.
|
|
350
|
+
*/
|
|
351
|
+
type InvoiceStatus = 'UNPAID' | 'PAID' | 'OVERDUE' | 'VOID';
|
|
352
|
+
/**
|
|
353
|
+
* Filter chip value for the widget UI + {@code status} query param sent to
|
|
354
|
+
* the BFF. Lowercase per the prompt's API (the BFF uppercases on receive).
|
|
355
|
+
*/
|
|
356
|
+
type InvoiceStatusFilter = 'all' | 'unpaid' | 'paid' | 'overdue' | 'void';
|
|
357
|
+
/**
|
|
358
|
+
* Invoice type — STANDARD invoices are the common case; AMENDMENT renders
|
|
359
|
+
* a small "Amendment" badge per the B2 2026-05-09 ship parity. The widget
|
|
360
|
+
* doesn't distinguish AMENDMENT vs AMENDMENT_CREDIT visually beyond the
|
|
361
|
+
* badge — both are post-creation adjustments.
|
|
362
|
+
*/
|
|
363
|
+
type InvoiceTypeKind = 'STANDARD' | 'AMENDMENT' | 'AMENDMENT_CREDIT';
|
|
364
|
+
/**
|
|
365
|
+
* Per-row invoice payload — mirrors
|
|
366
|
+
* {@code com.aforo.billing.storefront.embed.dto.EmbedInvoiceResponse}
|
|
367
|
+
* field-for-field. {@code BigDecimal} amounts are serialised by Jackson
|
|
368
|
+
* as JSON numbers (Spring's default {@code JsonNumber} mapping); we type
|
|
369
|
+
* them as {@code number | string} so a future serialiser switch (e.g. to
|
|
370
|
+
* {@code String} for arbitrary precision) doesn't break the widget.
|
|
371
|
+
*
|
|
372
|
+
* <p>NaN-defensive formatting: {@code total}, {@code amountPaid}, and
|
|
373
|
+
* {@code amountDue} render as {@code '—'} when null / undefined / non-
|
|
374
|
+
* finite per FR-WIDGET-X-9.</p>
|
|
375
|
+
*/
|
|
376
|
+
interface InvoicePayload {
|
|
377
|
+
id: string;
|
|
378
|
+
invoiceNumber: string;
|
|
379
|
+
/**
|
|
380
|
+
* Customer-facing status (UNPAID / PAID / OVERDUE / VOID). DRAFT
|
|
381
|
+
* invoices are filtered server-side; widget never sees them.
|
|
382
|
+
*/
|
|
383
|
+
status: InvoiceStatus | string;
|
|
384
|
+
invoiceType?: InvoiceTypeKind | string;
|
|
385
|
+
issuedAt?: string | null;
|
|
386
|
+
dueAt?: string | null;
|
|
387
|
+
billingPeriodStart?: string | null;
|
|
388
|
+
billingPeriodEnd?: string | null;
|
|
389
|
+
total?: number | string | null;
|
|
390
|
+
amountPaid?: number | string | null;
|
|
391
|
+
amountDue?: number | string | null;
|
|
392
|
+
currency?: string | null;
|
|
393
|
+
lineItemCount?: number | null;
|
|
394
|
+
/**
|
|
395
|
+
* Server-derived overdue flag. Widget UI prefers this over re-computing
|
|
396
|
+
* from {@code dueAt} so all widget instances on a page agree regardless
|
|
397
|
+
* of client clock drift.
|
|
398
|
+
*/
|
|
399
|
+
isOverdue?: boolean;
|
|
400
|
+
/**
|
|
401
|
+
* Days from now until {@code dueAt} — negative when overdue. Null when
|
|
402
|
+
* {@code dueAt} is missing.
|
|
403
|
+
*/
|
|
404
|
+
daysUntilDue?: number | null;
|
|
405
|
+
}
|
|
406
|
+
/** Paginated envelope returned by {@code GET /api/v1/portal/embed/invoices}. */
|
|
407
|
+
interface InvoicePageResponse {
|
|
408
|
+
content: InvoicePayload[];
|
|
409
|
+
totalElements: number;
|
|
410
|
+
totalPages: number;
|
|
411
|
+
page: number;
|
|
412
|
+
size: number;
|
|
413
|
+
}
|
|
414
|
+
/**
|
|
415
|
+
* Filter query for {@code BridgeClient.fetchInvoices}. The widget builds
|
|
416
|
+
* one of these on every fetch, and a stable JSON encoding of it is the
|
|
417
|
+
* cache key (so refetching the same page is a noop).
|
|
418
|
+
*/
|
|
419
|
+
interface InvoiceListFilter {
|
|
420
|
+
page: number;
|
|
421
|
+
size: number;
|
|
422
|
+
/** {@code 'all'} → unspecified (BFF ignores the param entirely). */
|
|
423
|
+
status?: InvoiceStatusFilter;
|
|
424
|
+
/** Substring match against {@code invoice_number}. Max 200 chars (BFF clamps). */
|
|
425
|
+
search?: string;
|
|
426
|
+
/**
|
|
427
|
+
* Server-side sort. v1 widget UI doesn't expose this (defaults to
|
|
428
|
+
* {@code issuedAt,desc}); kept on the API for forward-compat with
|
|
429
|
+
* sortable-column work (Phase 1 follow-up).
|
|
430
|
+
*/
|
|
431
|
+
sort?: string;
|
|
432
|
+
}
|
|
433
|
+
/** Response shape for {@code GET /api/v1/portal/embed/invoices/{id}/pdf-url}. */
|
|
434
|
+
interface InvoicePdfUrlResponse {
|
|
435
|
+
/**
|
|
436
|
+
* Absolute URL pointing at the session-authenticated companion endpoint
|
|
437
|
+
* (v1) or the standalone signed URL (v2). Widget opens in a new tab via
|
|
438
|
+
* {@code window.open(url, '_blank', 'noopener,noreferrer')}.
|
|
439
|
+
*/
|
|
440
|
+
downloadUrl: string;
|
|
441
|
+
/** ISO-8601 timestamp the widget should consider {@code downloadUrl} stale. */
|
|
442
|
+
expiresAt: string;
|
|
443
|
+
fileName: string;
|
|
444
|
+
/** Always null in v1 (upstream doesn't emit Content-Length yet). */
|
|
445
|
+
sizeBytes?: number | null;
|
|
446
|
+
}
|
|
447
|
+
/**
|
|
448
|
+
* Request body for {@code POST /api/v1/portal/embed/invoices/{id}/pay/initiate}.
|
|
449
|
+
*
|
|
450
|
+
* <p>Idempotency-Key is sent as an HTTP header (FR-SEC-19), NOT in the body —
|
|
451
|
+
* mirrors the EmbedCheckoutController convention. The widget generates a
|
|
452
|
+
* UUID v4 on every CTA click.</p>
|
|
453
|
+
*/
|
|
454
|
+
interface InitiateInvoicePaymentRequest {
|
|
455
|
+
returnUrl: string;
|
|
456
|
+
}
|
|
457
|
+
/**
|
|
458
|
+
* Response shape for {@code POST /api/v1/portal/embed/invoices/{id}/pay/initiate}.
|
|
459
|
+
*
|
|
460
|
+
* <p><b>Phase 0 stub (Prompt 6A, 2026-05-27):</b> the BFF always returns
|
|
461
|
+
* {@code {supported: false, reason: 'EMBEDDED_CHECKOUT_NOT_YET_WIRED'}} with
|
|
462
|
+
* HTTP 200. Prompt 7 (CheckoutFlow widget) drop-in replaces the response
|
|
463
|
+
* to populate {@code checkoutUrl} / {@code checkoutSessionId} /
|
|
464
|
+
* {@code expiresAt} — the type stays the same so the widget's call site
|
|
465
|
+
* doesn't change.</p>
|
|
466
|
+
*/
|
|
467
|
+
interface InitiateInvoicePaymentResponse {
|
|
468
|
+
supported: boolean;
|
|
469
|
+
reason?: string;
|
|
470
|
+
/** Populated when Prompt 7 ships the real CheckoutFlow integration. */
|
|
471
|
+
checkoutUrl?: string;
|
|
472
|
+
checkoutSessionId?: string;
|
|
473
|
+
expiresAt?: string;
|
|
474
|
+
}
|
|
475
|
+
/**
|
|
476
|
+
* Payload for the {@code aforo.invoice-list.invoice.downloaded} outbound
|
|
477
|
+
* event + the {@code onDownload} React callback (when the widget's
|
|
478
|
+
* downloaded action fires).
|
|
479
|
+
*/
|
|
480
|
+
interface InvoiceDownloadedPayload {
|
|
481
|
+
invoiceId: string;
|
|
482
|
+
invoiceNumber: string;
|
|
483
|
+
fileName: string;
|
|
484
|
+
sizeBytes?: number | null;
|
|
485
|
+
}
|
|
486
|
+
/**
|
|
487
|
+
* Payload for the {@code aforo.invoice-list.pay_requested} outbound event +
|
|
488
|
+
* the {@code onPayRequested} React callback. Phase 0: customer's parent
|
|
489
|
+
* page handles payment in its own flow. Phase 1 (Prompt 7): the embedded
|
|
490
|
+
* CheckoutFlow widget consumes this signal in-place.
|
|
491
|
+
*/
|
|
492
|
+
interface InvoicePayRequestedPayload {
|
|
493
|
+
invoiceId: string;
|
|
494
|
+
invoiceNumber: string;
|
|
495
|
+
total: number | string | null;
|
|
496
|
+
currency: string | null;
|
|
497
|
+
status: string;
|
|
498
|
+
dueAt: string | null;
|
|
499
|
+
returnUrl: string;
|
|
500
|
+
}
|
|
501
|
+
/** Payload for the informational {@code aforo.invoice-list.filter_changed} event. */
|
|
502
|
+
interface InvoiceFilterChangedPayload {
|
|
503
|
+
status: InvoiceStatusFilter;
|
|
504
|
+
}
|
|
505
|
+
/** Payload for the informational {@code aforo.invoice-list.search_changed} event. */
|
|
506
|
+
interface InvoiceSearchChangedPayload {
|
|
507
|
+
/** Empty string when the search was cleared. */
|
|
508
|
+
query: string;
|
|
509
|
+
}
|
|
510
|
+
/** Payload for the informational {@code aforo.invoice-list.invoice.expanded} event. */
|
|
511
|
+
interface InvoiceExpandedPayload {
|
|
512
|
+
invoiceId: string;
|
|
513
|
+
invoiceNumber: string;
|
|
514
|
+
expanded: boolean;
|
|
515
|
+
}
|
|
516
|
+
/**
|
|
517
|
+
* Payload for the {@code aforo.invoice-list.invoice.paid} outbound event.
|
|
518
|
+
* Fires when the parent posts inbound {@code aforo:invoice-paid} OR when
|
|
519
|
+
* Prompt 7's CheckoutFlow signals a successful invoice payment. Backend
|
|
520
|
+
* webhook is still authoritative per FR-SEC-23.
|
|
521
|
+
*/
|
|
522
|
+
interface InvoicePaidPayload {
|
|
523
|
+
invoiceId: string;
|
|
524
|
+
invoiceNumber?: string;
|
|
525
|
+
}
|
|
526
|
+
/** Cart type — SUBSCRIBE creates a new subscription; INVOICE_PAYMENT pays an existing invoice. */
|
|
527
|
+
type CheckoutCartType = 'SUBSCRIBE' | 'INVOICE_PAYMENT';
|
|
528
|
+
/** Backend cart status — must stay in lock-step with `EmbedCheckoutCartStatus.java`. */
|
|
529
|
+
type CheckoutCartStatus = 'CREATED' | 'CUSTOMER_DETAILS' | 'PAYMENT_REDIRECTED' | 'COMPLETED' | 'EXPIRED' | 'CANCELLED';
|
|
530
|
+
/**
|
|
531
|
+
* Client-side phase — combines backend status with UI-layer states. The
|
|
532
|
+
* widget renders different content per phase:
|
|
533
|
+
*
|
|
534
|
+
* - `bootstrapping` — bridge-token → session-JWT exchange + cart create
|
|
535
|
+
* (loading skeleton, role=status)
|
|
536
|
+
* - `customer-details` — Step 1 form (name + email + billing address)
|
|
537
|
+
* - `payment` — Step 2 payment-provider iframe
|
|
538
|
+
* - `confirming` — Step 3 confirm + provisioning spinner
|
|
539
|
+
* - `completed` — Step 4 success screen
|
|
540
|
+
* - `cancelled` / `expired` / `error` — terminal/recoverable states
|
|
541
|
+
*/
|
|
542
|
+
type CheckoutFlowPhase = 'bootstrapping' | 'customer-details' | 'payment' | 'confirming' | 'completed' | 'cancelled' | 'expired' | 'error';
|
|
543
|
+
/** Payment provider — drives which iframe contract the widget renders. */
|
|
544
|
+
type CheckoutPaymentProvider = 'stripe' | 'razorpay' | 'paypal';
|
|
545
|
+
/**
|
|
546
|
+
* Billing address — captured at the customer-details step. All fields
|
|
547
|
+
* optional per backend `UpdateCartCustomerDetailsRequest.BillingAddress`.
|
|
548
|
+
*/
|
|
549
|
+
interface CheckoutBillingAddress {
|
|
550
|
+
line1?: string;
|
|
551
|
+
line2?: string;
|
|
552
|
+
city?: string;
|
|
553
|
+
state?: string;
|
|
554
|
+
postalCode?: string;
|
|
555
|
+
/** ISO 3166-1 alpha-2 — e.g. "US", "DE". 2 chars max. */
|
|
556
|
+
country?: string;
|
|
557
|
+
}
|
|
558
|
+
/**
|
|
559
|
+
* Request body for {@code POST /api/v1/portal/embed/checkout/cart}.
|
|
560
|
+
*
|
|
561
|
+
* <p>Idempotency-Key header carries the per-click UUID v4; not part of the
|
|
562
|
+
* body. Cart-create is the only POST that doesn't take an explicit
|
|
563
|
+
* idempotencyKey field — the {@code BridgeClient.createCart} method accepts
|
|
564
|
+
* one as a separate parameter, mirroring the {@code initiateInvoicePayment}
|
|
565
|
+
* call shape from Prompt 6B.</p>
|
|
566
|
+
*/
|
|
567
|
+
interface CreateCartRequest {
|
|
568
|
+
cartType: CheckoutCartType;
|
|
569
|
+
/** Offering id when cartType=SUBSCRIBE; invoice id when cartType=INVOICE_PAYMENT. */
|
|
570
|
+
targetId: string;
|
|
571
|
+
/**
|
|
572
|
+
* Where to return the customer if the payment flow leaves the iframe
|
|
573
|
+
* (PayPal redirect, 3DS challenges in a new tab). Validated server-side
|
|
574
|
+
* against the embed key's allowed_domains list.
|
|
575
|
+
*/
|
|
576
|
+
returnUrl: string;
|
|
577
|
+
}
|
|
578
|
+
/**
|
|
579
|
+
* Request body for {@code PATCH /api/v1/portal/embed/checkout/cart/{cartId}/customer-details}.
|
|
580
|
+
* Both fields optional — widget pre-fills + skips for returning customers.
|
|
581
|
+
*/
|
|
582
|
+
interface UpdateCustomerDetailsRequest {
|
|
583
|
+
billingAddress?: CheckoutBillingAddress;
|
|
584
|
+
displayName?: string;
|
|
585
|
+
}
|
|
586
|
+
/**
|
|
587
|
+
* Request body for {@code POST /api/v1/portal/embed/checkout/cart/{cartId}/initiate-payment}.
|
|
588
|
+
*
|
|
589
|
+
* <p>Called AFTER the customer enters card details in the provider iframe.
|
|
590
|
+
* The {@code gatewayPaymentIntentId} MUST match the cart's stored intent
|
|
591
|
+
* id (defense against intent-swap attacks per Prompt 7A round-2 audit).</p>
|
|
592
|
+
*/
|
|
593
|
+
interface InitiateCartPaymentRequest {
|
|
594
|
+
gatewayPaymentMethodId: string;
|
|
595
|
+
gatewayPaymentIntentId: string;
|
|
596
|
+
}
|
|
597
|
+
/**
|
|
598
|
+
* Response shape for {@code POST /cart}, {@code GET /cart/{id}}, and
|
|
599
|
+
* {@code PATCH /cart/{id}/customer-details}.
|
|
600
|
+
*
|
|
601
|
+
* <p>Gateway secrets ({@code gatewayClientSecret}, {@code gatewayOrderId},
|
|
602
|
+
* {@code gatewayApprovalUrl}, {@code gatewaySandboxMode}) are populated
|
|
603
|
+
* ONLY on the cart-create response per FR-SEC-9. Subsequent GET / PATCH
|
|
604
|
+
* reads return null for those fields — widget MUST sessionStorage them.</p>
|
|
605
|
+
*/
|
|
606
|
+
interface CheckoutCartResponse {
|
|
607
|
+
cartId: string;
|
|
608
|
+
cartType: CheckoutCartType;
|
|
609
|
+
targetId: string;
|
|
610
|
+
status: CheckoutCartStatus;
|
|
611
|
+
totalCents: number;
|
|
612
|
+
currency: string;
|
|
613
|
+
gatewayProvider?: CheckoutPaymentProvider | string | null;
|
|
614
|
+
/** Stripe — null on read after create. */
|
|
615
|
+
gatewayClientSecret?: string | null;
|
|
616
|
+
/** Razorpay / hosted — null on read after create. */
|
|
617
|
+
gatewayOrderId?: string | null;
|
|
618
|
+
/** PayPal redirect URL — null on read after create. */
|
|
619
|
+
gatewayApprovalUrl?: string | null;
|
|
620
|
+
/** PayPal — true when test creds, null for others. */
|
|
621
|
+
gatewaySandboxMode?: boolean | null;
|
|
622
|
+
/** Populated when status=COMPLETED + cartType=SUBSCRIBE. */
|
|
623
|
+
subscriptionId?: string | null;
|
|
624
|
+
/** Populated when status=COMPLETED + cartType=INVOICE_PAYMENT. */
|
|
625
|
+
invoiceId?: string | null;
|
|
626
|
+
/** ISO-8601 — widget shows countdown when within last 5 min. */
|
|
627
|
+
expiresAt: string;
|
|
628
|
+
completedAt?: string | null;
|
|
629
|
+
}
|
|
630
|
+
/**
|
|
631
|
+
* Response shape for {@code POST /cart/{cartId}/confirm}.
|
|
632
|
+
*
|
|
633
|
+
* <p>SUBSCRIBE-variant populates {@code subscriptionId} + {@code subscriptionStatus};
|
|
634
|
+
* INVOICE_PAYMENT-variant populates {@code invoiceId} + {@code invoiceStatus}.</p>
|
|
635
|
+
*
|
|
636
|
+
* <p><b>FR-SEC-23 reminder:</b> these fields are returned synchronously for
|
|
637
|
+
* widget UX but the authoritative state comes via Kafka webhook events.</p>
|
|
638
|
+
*/
|
|
639
|
+
interface ConfirmCartResponse {
|
|
640
|
+
cartId: string;
|
|
641
|
+
status: CheckoutCartStatus;
|
|
642
|
+
subscriptionId?: string | null;
|
|
643
|
+
subscriptionStatus?: string | null;
|
|
644
|
+
invoiceId?: string | null;
|
|
645
|
+
invoiceStatus?: string | null;
|
|
646
|
+
completedAt?: string | null;
|
|
647
|
+
}
|
|
648
|
+
/**
|
|
649
|
+
* Typed error envelope from the embed checkout endpoints — mirrors backend
|
|
650
|
+
* {@code CartErrorResponse} record. Surfaces 402 Payment Required (declined
|
|
651
|
+
* cards, 3DS challenges) + 409 Conflict (already-subscribed, invoice-already-paid,
|
|
652
|
+
* cart-state-invalid).
|
|
653
|
+
*
|
|
654
|
+
* <p>Codes the widget UI branches on:</p>
|
|
655
|
+
* <ul>
|
|
656
|
+
* <li>{@code PAYMENT_FAILED} — show error + retry affordance</li>
|
|
657
|
+
* <li>{@code REQUIRES_ACTION} — open {@code nextActionUrl} in popup; listen
|
|
658
|
+
* for {@code aforo:payment-challenge-completed} inbound event</li>
|
|
659
|
+
* <li>{@code ALREADY_SUBSCRIBED} — show "Already subscribed" with link to
|
|
660
|
+
* {@code existingSubscriptionId} subscription</li>
|
|
661
|
+
* <li>{@code INVOICE_ALREADY_PAID} — show "Invoice already paid" + dismiss</li>
|
|
662
|
+
* <li>{@code CART_STATE_INVALID} — cart not in expected state for transition</li>
|
|
663
|
+
* </ul>
|
|
664
|
+
*/
|
|
665
|
+
interface CheckoutCartErrorResponse {
|
|
666
|
+
code: string;
|
|
667
|
+
message: string;
|
|
668
|
+
requiresAction?: boolean;
|
|
669
|
+
nextActionUrl?: string | null;
|
|
670
|
+
gatewayCode?: string | null;
|
|
671
|
+
existingSubscriptionId?: string | null;
|
|
672
|
+
}
|
|
673
|
+
/**
|
|
674
|
+
* Payload for {@code aforo.checkout-flow.step_changed} outbound event.
|
|
675
|
+
* Fires whenever the widget's phase changes (e.g. customer-details → payment).
|
|
676
|
+
*/
|
|
677
|
+
interface CheckoutStepChangedPayload {
|
|
678
|
+
from: CheckoutFlowPhase;
|
|
679
|
+
to: CheckoutFlowPhase;
|
|
680
|
+
cartId: string;
|
|
681
|
+
cartType: CheckoutCartType;
|
|
682
|
+
}
|
|
683
|
+
/**
|
|
684
|
+
* Payload for {@code aforo.checkout-flow.customer_details_submitted} event.
|
|
685
|
+
* Fires after a successful PATCH /customer-details — does NOT include the
|
|
686
|
+
* raw billing address (PII), only the fields that were populated.
|
|
687
|
+
*/
|
|
688
|
+
interface CheckoutCustomerDetailsSubmittedPayload {
|
|
689
|
+
cartId: string;
|
|
690
|
+
/** True when the customer supplied a non-empty billing address (any field). */
|
|
691
|
+
billingAddressProvided: boolean;
|
|
692
|
+
/** True when displayName was supplied. */
|
|
693
|
+
displayNameProvided: boolean;
|
|
694
|
+
}
|
|
695
|
+
/**
|
|
696
|
+
* Payload for {@code aforo.checkout-flow.payment_initiated} event. Fires
|
|
697
|
+
* AFTER /initiate-payment returns 200 — i.e. payment method id is captured
|
|
698
|
+
* but the cart is not yet confirmed (still in PAYMENT_REDIRECTED state).
|
|
699
|
+
*/
|
|
700
|
+
interface CheckoutPaymentInitiatedPayload {
|
|
701
|
+
cartId: string;
|
|
702
|
+
cartType: CheckoutCartType;
|
|
703
|
+
/** Provider that owned the tokenization step. */
|
|
704
|
+
gatewayProvider: CheckoutPaymentProvider | string;
|
|
705
|
+
}
|
|
706
|
+
/**
|
|
707
|
+
* Payload for {@code aforo.checkout-flow.payment_completed} event. Fires
|
|
708
|
+
* AFTER the gateway tokenization succeeds AND the cart is confirmed —
|
|
709
|
+
* the customer's payment has cleared.
|
|
710
|
+
*/
|
|
711
|
+
interface CheckoutPaymentCompletedPayload {
|
|
712
|
+
cartId: string;
|
|
713
|
+
cartType: CheckoutCartType;
|
|
714
|
+
gatewayProvider: CheckoutPaymentProvider | string;
|
|
715
|
+
}
|
|
716
|
+
/**
|
|
717
|
+
* Payload for {@code aforo.checkout-flow.confirmed} event. Fires at the
|
|
718
|
+
* same moment as the SUBSCRIBE-variant aforo.subscription.created OR the
|
|
719
|
+
* INVOICE_PAYMENT-variant aforo.invoice-list.invoice.paid event, but
|
|
720
|
+
* carries the unified shape regardless of cart type.
|
|
721
|
+
*/
|
|
722
|
+
interface CheckoutConfirmedPayload {
|
|
723
|
+
cartId: string;
|
|
724
|
+
cartType: CheckoutCartType;
|
|
725
|
+
/** Populated for SUBSCRIBE carts. */
|
|
726
|
+
subscriptionId?: string | null;
|
|
727
|
+
/** Populated for INVOICE_PAYMENT carts. */
|
|
728
|
+
invoiceId?: string | null;
|
|
729
|
+
}
|
|
730
|
+
/**
|
|
731
|
+
* Payload for {@code aforo.checkout-flow.cancelled} event. Fires when the
|
|
732
|
+
* customer dismisses the flow OR the parent posts {@code aforo:checkout-flow-dismissed}.
|
|
733
|
+
*/
|
|
734
|
+
interface CheckoutCancelledPayload {
|
|
735
|
+
cartId: string;
|
|
736
|
+
cartType: CheckoutCartType;
|
|
737
|
+
/** UI vs. parent-driven dismissal. */
|
|
738
|
+
source: 'customer' | 'parent';
|
|
739
|
+
}
|
|
740
|
+
/**
|
|
741
|
+
* Payload for {@code aforo.checkout-flow.expired} event. Fires when the
|
|
742
|
+
* cart's {@code expiresAt} window elapses while the widget is still
|
|
743
|
+
* mounted in a non-terminal phase.
|
|
744
|
+
*/
|
|
745
|
+
interface CheckoutExpiredPayload {
|
|
746
|
+
cartId: string;
|
|
747
|
+
cartType: CheckoutCartType;
|
|
748
|
+
expiresAt: string;
|
|
749
|
+
}
|
|
750
|
+
/**
|
|
751
|
+
* Customer-facing subscription status vocabulary. Mirrors backend
|
|
752
|
+
* {@code SubscriptionStatus} enum minus operator-internal states.
|
|
753
|
+
*
|
|
754
|
+
* <p>Status meanings:</p>
|
|
755
|
+
* <ul>
|
|
756
|
+
* <li>{@code ACTIVE} — billing normally</li>
|
|
757
|
+
* <li>{@code TRIALING} — in trial period</li>
|
|
758
|
+
* <li>{@code PAST_DUE} — payment failed, dunning in progress</li>
|
|
759
|
+
* <li>{@code PAUSED} — customer-initiated pause</li>
|
|
760
|
+
* <li>{@code EXPIRING_SOON} — auto-renew off, within 30 days of expiry</li>
|
|
761
|
+
* <li>{@code EXPIRED} — past renewal date, no auto-renew</li>
|
|
762
|
+
* <li>{@code CANCELLED} — customer cancelled (terminal)</li>
|
|
763
|
+
* <li>{@code SUSPENDED} — operator suspended (terminal-ish)</li>
|
|
764
|
+
* </ul>
|
|
765
|
+
*/
|
|
766
|
+
type SubscriptionStatus = 'ACTIVE' | 'TRIALING' | 'PAST_DUE' | 'PAUSED' | 'EXPIRING_SOON' | 'EXPIRED' | 'CANCELLED' | 'SUSPENDED';
|
|
767
|
+
/**
|
|
768
|
+
* Filter chip value for the SubscriptionManager widget UI + {@code status}
|
|
769
|
+
* query param sent to the BFF. Lowercase per the prompt's API; the BFF
|
|
770
|
+
* uppercases on receive.
|
|
771
|
+
*/
|
|
772
|
+
type SubscriptionStatusFilter = 'all' | 'ACTIVE' | 'TRIALING' | 'PAST_DUE' | 'PAUSED' | 'EXPIRING_SOON' | 'EXPIRED' | 'CANCELLED' | 'SUSPENDED';
|
|
773
|
+
/**
|
|
774
|
+
* Per-row subscription payload — mirrors {@code EmbedSubscriptionResponse}
|
|
775
|
+
* field-for-field. {@code BigDecimal} amounts are serialised by Jackson as
|
|
776
|
+
* JSON numbers; we type them as {@code number | string} so a future
|
|
777
|
+
* serializer switch (e.g. to {@code String} for arbitrary precision) doesn't
|
|
778
|
+
* break the widget.
|
|
779
|
+
*
|
|
780
|
+
* <p>NaN-defensive formatting: {@code mrr}, {@code nextInvoiceAmount} render
|
|
781
|
+
* as {@code '—'} when null / undefined / non-finite per FR-WIDGET-X-9.</p>
|
|
782
|
+
*/
|
|
783
|
+
interface SubscriptionPayloadV2 {
|
|
784
|
+
id: string;
|
|
785
|
+
planName: string;
|
|
786
|
+
offeringId: string;
|
|
787
|
+
status: SubscriptionStatus | string;
|
|
788
|
+
startedAt?: string | null;
|
|
789
|
+
currentPeriodStart?: string | null;
|
|
790
|
+
currentPeriodEnd?: string | null;
|
|
791
|
+
renewsAt?: string | null;
|
|
792
|
+
mrr?: number | string | null;
|
|
793
|
+
currency?: string | null;
|
|
794
|
+
nextInvoiceAmount?: number | string | null;
|
|
795
|
+
trialEndsAt?: string | null;
|
|
796
|
+
isExpiringSoon?: boolean;
|
|
797
|
+
daysUntilRenewal?: number | null;
|
|
798
|
+
}
|
|
799
|
+
/** Paginated envelope returned by {@code GET /api/v1/portal/embed/subscriptions}. */
|
|
800
|
+
interface SubscriptionPageResponse {
|
|
801
|
+
content: SubscriptionPayloadV2[];
|
|
802
|
+
totalElements: number;
|
|
803
|
+
totalPages: number;
|
|
804
|
+
page: number;
|
|
805
|
+
size: number;
|
|
806
|
+
}
|
|
807
|
+
/**
|
|
808
|
+
* Filter query for {@code BridgeClient.fetchSubscriptions}. The widget builds
|
|
809
|
+
* one of these on every fetch; the BFF receives it via query params.
|
|
810
|
+
*/
|
|
811
|
+
interface SubscriptionListFilter {
|
|
812
|
+
page: number;
|
|
813
|
+
size: number;
|
|
814
|
+
/** {@code 'all'} → unspecified (BFF ignores the param entirely). */
|
|
815
|
+
status?: SubscriptionStatusFilter;
|
|
816
|
+
/**
|
|
817
|
+
* Server-side sort. Default {@code startedAt,desc}; allowed values
|
|
818
|
+
* {@code startedAt,desc/asc}, {@code renewsAt,asc/desc}, {@code mrr,desc/asc}
|
|
819
|
+
* (mrr falls back to default at pricing-service today — Phase 1 follow-up).
|
|
820
|
+
*/
|
|
821
|
+
sort?: string;
|
|
822
|
+
}
|
|
823
|
+
/**
|
|
824
|
+
* Per-metric usage row — mirrors {@code EmbedUsageMetricResponse}.
|
|
825
|
+
*
|
|
826
|
+
* <p>{@code quotaLimit=0} signals an unlimited / no-quota metric (e.g.
|
|
827
|
+
* PERCENTAGE / FLAT_RATE pricing) — the widget renders "Unlimited" in that
|
|
828
|
+
* case. {@code quotaUsedPct=0.0} with {@code quotaLimit=0} is consistent
|
|
829
|
+
* (no limit known → no pct).</p>
|
|
830
|
+
*
|
|
831
|
+
* <p>{@code isApproximated=true} for TIERED / VOLUME / STAIRCASE /
|
|
832
|
+
* INCLUDED_QUOTA pricing models — the widget renders a ⚠ icon next to the
|
|
833
|
+
* metric name with a tooltip explaining the upper-bound approximation.</p>
|
|
834
|
+
*/
|
|
835
|
+
interface UsageMetricPayload {
|
|
836
|
+
metricName: string;
|
|
837
|
+
displayName?: string;
|
|
838
|
+
quotaLimit: number;
|
|
839
|
+
quotaUsed: number;
|
|
840
|
+
quotaUsedPct: number;
|
|
841
|
+
unit?: string | null;
|
|
842
|
+
pricingModel?: string | null;
|
|
843
|
+
isApproximated: boolean;
|
|
844
|
+
projectedEndOfPeriod?: number | null;
|
|
845
|
+
currency?: string | null;
|
|
846
|
+
estimatedSpend?: number | string | null;
|
|
847
|
+
}
|
|
848
|
+
/**
|
|
849
|
+
* Current-period usage payload — mirrors {@code EmbedUsageResponse}.
|
|
850
|
+
*
|
|
851
|
+
* <p>{@code periodProgress} is server-computed (0.0 - 1.0) for clock-drift
|
|
852
|
+
* resistance — the widget renders the progress bar at this width directly
|
|
853
|
+
* rather than recomputing client-side.</p>
|
|
854
|
+
*
|
|
855
|
+
* <p>{@code approximationNote} is non-null when ANY metric in {@code metrics}
|
|
856
|
+
* has {@code isApproximated=true} — the widget renders it as a single
|
|
857
|
+
* envelope-level banner instead of per-metric duplication.</p>
|
|
858
|
+
*/
|
|
859
|
+
interface UsageResponse {
|
|
860
|
+
subscriptionId: string;
|
|
861
|
+
periodStart?: string | null;
|
|
862
|
+
periodEnd?: string | null;
|
|
863
|
+
periodProgress: number;
|
|
864
|
+
metrics: UsageMetricPayload[];
|
|
865
|
+
totalEstimatedSpend?: number | string | null;
|
|
866
|
+
approximationNote?: string | null;
|
|
867
|
+
computedAt?: string | null;
|
|
868
|
+
}
|
|
869
|
+
/** Payload for the {@code aforo.subscription-manager.subscription.clicked} event. */
|
|
870
|
+
interface SubscriptionClickedPayload {
|
|
871
|
+
subscriptionId: string;
|
|
872
|
+
offeringId: string;
|
|
873
|
+
planName: string;
|
|
874
|
+
status: string;
|
|
875
|
+
}
|
|
876
|
+
/** Payload for the {@code aforo.subscription-manager.upgrade_requested} event. */
|
|
877
|
+
interface SubscriptionUpgradeRequestedPayload {
|
|
878
|
+
subscriptionId: string;
|
|
879
|
+
offeringId: string;
|
|
880
|
+
planName: string;
|
|
881
|
+
status: string;
|
|
882
|
+
/**
|
|
883
|
+
* When {@code true}, the parent should drive the customer to a payment-
|
|
884
|
+
* update flow rather than an upgrade flow. Fired when the customer
|
|
885
|
+
* clicks "Update payment" on a PAST_DUE row.
|
|
886
|
+
*/
|
|
887
|
+
paymentUpdate?: boolean;
|
|
888
|
+
}
|
|
889
|
+
/** Payload for the {@code aforo.subscription-manager.cancel_requested} event. */
|
|
890
|
+
interface SubscriptionCancelRequestedPayload {
|
|
891
|
+
subscriptionId: string;
|
|
892
|
+
offeringId: string;
|
|
893
|
+
planName: string;
|
|
894
|
+
status: string;
|
|
895
|
+
}
|
|
896
|
+
/** Payload for the {@code aforo.subscription-manager.filter_changed} event. */
|
|
897
|
+
interface SubscriptionFilterChangedPayload {
|
|
898
|
+
status: SubscriptionStatusFilter;
|
|
899
|
+
}
|
|
900
|
+
/** Payload for the {@code aforo.usage-meter.metric_clicked} event (compact mode). */
|
|
901
|
+
interface UsageMetricClickedPayload {
|
|
902
|
+
subscriptionId: string;
|
|
903
|
+
metricName: string;
|
|
904
|
+
quotaUsed: number;
|
|
905
|
+
quotaLimit: number;
|
|
906
|
+
quotaUsedPct: number;
|
|
907
|
+
}
|
|
908
|
+
/**
|
|
909
|
+
* Threshold values the {@code aforo.usage-meter.threshold_reached} event
|
|
910
|
+
* fires at. Crossings detected client-side by comparing current vs prior
|
|
911
|
+
* {@code quotaUsedPct}.
|
|
912
|
+
*/
|
|
913
|
+
type UsageThreshold = 70 | 90 | 100;
|
|
914
|
+
/** Payload for the {@code aforo.usage-meter.threshold_reached} event. */
|
|
915
|
+
interface UsageThresholdReachedPayload {
|
|
916
|
+
subscriptionId: string;
|
|
917
|
+
metricName: string;
|
|
918
|
+
quotaUsed: number;
|
|
919
|
+
quotaLimit: number;
|
|
920
|
+
quotaUsedPct: number;
|
|
921
|
+
threshold: UsageThreshold;
|
|
922
|
+
/** When non-null, the projected end-of-period quantity at the time of crossing. */
|
|
923
|
+
projectedEndOfPeriod?: number | null;
|
|
924
|
+
}
|
|
925
|
+
/** When the change applies. UPGRADE forces IMMEDIATE; DOWNGRADE/CANCEL default PERIOD_END. */
|
|
926
|
+
type ApplyAt = 'IMMEDIATE' | 'PERIOD_END';
|
|
927
|
+
/** Direction of the requested subscription change. Derived server-side from price comparison. */
|
|
928
|
+
type SubscriptionChangeType = 'UPGRADE' | 'DOWNGRADE' | 'LATERAL';
|
|
929
|
+
/** Compact offering summary used by the upgrade preview "from/to" comparison cards. */
|
|
930
|
+
interface OfferingSummary {
|
|
931
|
+
id: string;
|
|
932
|
+
name: string;
|
|
933
|
+
priceCents: number;
|
|
934
|
+
currency: string;
|
|
935
|
+
}
|
|
936
|
+
/** Request body for {@code POST /preview-change}. NO Idempotency-Key (read). */
|
|
937
|
+
interface PreviewChangeRequest {
|
|
938
|
+
targetOfferingId: string;
|
|
939
|
+
applyAt: ApplyAt;
|
|
940
|
+
}
|
|
941
|
+
/**
|
|
942
|
+
* Response body for {@code POST /preview-change}. Pattern #18 fail-soft:
|
|
943
|
+
* {@code BridgeClient.previewSubscriptionChange} returns {@code null} on
|
|
944
|
+
* transport / non-2xx / malformed JSON so the widget can render an inline
|
|
945
|
+
* error + retry without blocking Back navigation.
|
|
946
|
+
*
|
|
947
|
+
* Amounts are in MINOR units (cents). Net positive → customer charged today;
|
|
948
|
+
* net negative → customer credited; zero → "No charge today".
|
|
949
|
+
*/
|
|
950
|
+
interface PreviewChangeResponse {
|
|
951
|
+
currentOffering: OfferingSummary;
|
|
952
|
+
targetOffering: OfferingSummary;
|
|
953
|
+
changeType: SubscriptionChangeType;
|
|
954
|
+
applyAt: ApplyAt;
|
|
955
|
+
/** Credit for the unused portion of the current period (positive value). */
|
|
956
|
+
creditCents: number;
|
|
957
|
+
/** Prorated charge for the target plan (positive value). */
|
|
958
|
+
debitCents: number;
|
|
959
|
+
/** debitCents - creditCents. Positive = customer pays today; negative = customer credited. */
|
|
960
|
+
netCents: number;
|
|
961
|
+
currency: string;
|
|
962
|
+
/** Fraction of the period remaining at change-effective time, 0..1. */
|
|
963
|
+
prorationFactor: number;
|
|
964
|
+
/** ISO-8601 timestamp of the current period end. */
|
|
965
|
+
currentPeriodEnd: string;
|
|
966
|
+
/** Backend-generated plain-English summary suitable for Step 3 confirm. */
|
|
967
|
+
summary: string;
|
|
968
|
+
/** Caveats list rendered as footnote (e.g. "Discount expires at next renewal"). */
|
|
969
|
+
caveats: string[];
|
|
970
|
+
}
|
|
971
|
+
/** Request body for {@code POST /upgrade}. Idempotency-Key REQUIRED on header. */
|
|
972
|
+
interface UpgradeRequest {
|
|
973
|
+
targetOfferingId: string;
|
|
974
|
+
applyAt: ApplyAt;
|
|
975
|
+
}
|
|
976
|
+
/**
|
|
977
|
+
* Response body for {@code POST /upgrade}. Mutation path — throws
|
|
978
|
+
* {@link BridgeClientError} on failure.
|
|
979
|
+
*
|
|
980
|
+
* - {@code status=ACTIVE} + {@code appliedAt} set when IMMEDIATE succeeded.
|
|
981
|
+
* - {@code status=ACTIVE_PENDING_CHANGE} + {@code scheduledFor} set when PERIOD_END.
|
|
982
|
+
* - {@code invoiceId} present on net-positive upgrades; {@code creditNoteId} on net-negative downgrades.
|
|
983
|
+
*/
|
|
984
|
+
interface UpgradeResponse {
|
|
985
|
+
subscriptionId: string;
|
|
986
|
+
previousOfferingId: string;
|
|
987
|
+
newOfferingId: string;
|
|
988
|
+
status: 'ACTIVE' | 'ACTIVE_PENDING_CHANGE';
|
|
989
|
+
/** ISO-8601 timestamp when an IMMEDIATE change applied, else null. */
|
|
990
|
+
appliedAt: string | null;
|
|
991
|
+
/** ISO-8601 timestamp when a PERIOD_END change will apply, else null. */
|
|
992
|
+
scheduledFor: string | null;
|
|
993
|
+
/** Invoice produced by an IMMEDIATE upgrade with net-positive charge. */
|
|
994
|
+
invoiceId: string | null;
|
|
995
|
+
/** Credit note produced by an IMMEDIATE downgrade with net-negative refund. */
|
|
996
|
+
creditNoteId: string | null;
|
|
997
|
+
}
|
|
998
|
+
/** 6 canonical cancel reason categories surfaced as Step 1 radio options. */
|
|
999
|
+
type CancelReason = 'TOO_EXPENSIVE' | 'NOT_USING_ENOUGH' | 'MISSING_FEATURE' | 'TECHNICAL_ISSUES' | 'SWITCHED_TO_COMPETITOR' | 'OTHER';
|
|
1000
|
+
/** Which deflection offer the widget surfaced at Step 2. Captured for V50 audit. */
|
|
1001
|
+
type RetentionOfferShown = 'DISCOUNT_20_PCT' | 'PAUSE_3_MONTHS' | 'DOWNGRADE' | 'NONE';
|
|
1002
|
+
/**
|
|
1003
|
+
* What the customer did with the offer. Backend maps the widget's
|
|
1004
|
+
* {@code CANCELLED_ANYWAY} + {@code ACCEPTED_OFFER} to V50's
|
|
1005
|
+
* {@code DECLINED_OFFERS} — this is a BFF detail, the widget sends the values
|
|
1006
|
+
* below verbatim.
|
|
1007
|
+
*/
|
|
1008
|
+
type RetentionOutcome = 'NONE' | 'DECLINED_OFFERS' | 'CANCELLED_ANYWAY' | 'NO_OFFER_PRESENTED' | 'ACCEPTED_OFFER';
|
|
1009
|
+
/** Request body for {@code POST /cancel}. Idempotency-Key REQUIRED on header. */
|
|
1010
|
+
interface CancelWithFeedbackRequest {
|
|
1011
|
+
applyAt: ApplyAt;
|
|
1012
|
+
reason: CancelReason;
|
|
1013
|
+
/** Free-form additional context, capped at 1000 chars client-side; backend caps at 64. */
|
|
1014
|
+
reasonDetail?: string;
|
|
1015
|
+
retentionOfferShown: RetentionOfferShown;
|
|
1016
|
+
retentionOutcome: RetentionOutcome;
|
|
1017
|
+
}
|
|
1018
|
+
/**
|
|
1019
|
+
* Response body for {@code POST /cancel}. Mutation path — throws
|
|
1020
|
+
* {@link BridgeClientError} on failure.
|
|
1021
|
+
*
|
|
1022
|
+
* - {@code status=CANCELLED} + {@code cancelledAt} set when IMMEDIATE succeeded.
|
|
1023
|
+
* - {@code status=ACTIVE_PENDING_CANCEL} + {@code effectiveAt} ≈ current period end when PERIOD_END.
|
|
1024
|
+
* - {@code creditNoteId} present on IMMEDIATE cancellations with prorated refund.
|
|
1025
|
+
*/
|
|
1026
|
+
interface CancelResponse {
|
|
1027
|
+
subscriptionId: string;
|
|
1028
|
+
status: 'CANCELLED' | 'ACTIVE_PENDING_CANCEL';
|
|
1029
|
+
/** ISO-8601 timestamp when IMMEDIATE cancel applied, else null. */
|
|
1030
|
+
cancelledAt: string | null;
|
|
1031
|
+
/** ISO-8601 timestamp the cancellation takes/took effect (today for IMMEDIATE, period end for PERIOD_END). */
|
|
1032
|
+
effectiveAt: string;
|
|
1033
|
+
/** V50 feedback row id — useful for audit cross-reference. */
|
|
1034
|
+
feedbackId: string;
|
|
1035
|
+
/** Credit note produced by an IMMEDIATE cancel with prorated refund. */
|
|
1036
|
+
creditNoteId: string | null;
|
|
1037
|
+
}
|
|
1038
|
+
/** Convenience union emitted as the {@code onCompleted} callback argument. */
|
|
1039
|
+
interface UpgradeCancelResultPayload {
|
|
1040
|
+
mode: 'upgrade' | 'cancel';
|
|
1041
|
+
upgrade?: UpgradeResponse;
|
|
1042
|
+
cancel?: CancelResponse;
|
|
1043
|
+
}
|
|
1044
|
+
/** Payload for {@code aforo.upgrade-cancel.step_changed}. Non-PII. */
|
|
1045
|
+
interface UpgradeCancelStepChangedPayload {
|
|
1046
|
+
from: number;
|
|
1047
|
+
to: number;
|
|
1048
|
+
mode: 'upgrade' | 'cancel';
|
|
1049
|
+
}
|
|
1050
|
+
/**
|
|
1051
|
+
* Payload for {@code aforo.upgrade-cancel.preview_fetched}. Non-PII —
|
|
1052
|
+
* carries the change shape but never the actual amounts (parent pages
|
|
1053
|
+
* shouldn't observe per-customer pricing from event traffic).
|
|
1054
|
+
*/
|
|
1055
|
+
interface UpgradeCancelPreviewFetchedPayload {
|
|
1056
|
+
changeType: SubscriptionChangeType;
|
|
1057
|
+
applyAt: ApplyAt;
|
|
1058
|
+
/** True when the preview returned a non-zero net charge. */
|
|
1059
|
+
hasNet: boolean;
|
|
1060
|
+
}
|
|
1061
|
+
/** Payload for {@code aforo.upgrade-cancel.deflection_offer_shown}. */
|
|
1062
|
+
interface UpgradeCancelDeflectionOfferShownPayload {
|
|
1063
|
+
reason: CancelReason;
|
|
1064
|
+
offer: RetentionOfferShown;
|
|
1065
|
+
}
|
|
1066
|
+
/** Payload for {@code aforo.upgrade-cancel.deflection_offer_accepted}. */
|
|
1067
|
+
interface UpgradeCancelDeflectionOfferAcceptedPayload {
|
|
1068
|
+
reason: CancelReason;
|
|
1069
|
+
offer: RetentionOfferShown;
|
|
1070
|
+
}
|
|
1071
|
+
/**
|
|
1072
|
+
* Payload for {@code aforo.upgrade-cancel.completed}. Non-PII — carries
|
|
1073
|
+
* mode + status + presence flags only, never invoice/credit-note ids.
|
|
1074
|
+
*
|
|
1075
|
+
* FR-SEC-23 reminder: this event is INFORMATIONAL. The customer backend
|
|
1076
|
+
* MUST receive the {@code subscription.status_changed} webhook as the
|
|
1077
|
+
* authoritative signal. postMessage events are advisory only.
|
|
1078
|
+
*/
|
|
1079
|
+
interface UpgradeCancelCompletedPayload {
|
|
1080
|
+
mode: 'upgrade' | 'cancel';
|
|
1081
|
+
status: string;
|
|
1082
|
+
hasInvoice: boolean;
|
|
1083
|
+
hasCreditNote: boolean;
|
|
1084
|
+
}
|
|
1085
|
+
/** Payload for {@code aforo.upgrade-cancel.abandoned}. */
|
|
1086
|
+
interface UpgradeCancelAbandonedPayload {
|
|
1087
|
+
step: number;
|
|
1088
|
+
mode: 'upgrade' | 'cancel';
|
|
1089
|
+
}
|
|
1090
|
+
/**
|
|
1091
|
+
* Interface every public method of the real BridgeClient mirrors by name.
|
|
1092
|
+
* The sandbox package implements this against client-side mock fixtures;
|
|
1093
|
+
* tests can install a recording stub. All methods MUST be async.
|
|
1094
|
+
*
|
|
1095
|
+
* Read methods that today return null on miss preserve that contract
|
|
1096
|
+
* (Pattern #18). Write methods that today throw on failure also throw
|
|
1097
|
+
* here when the demo provider chooses to surface an error.
|
|
1098
|
+
*/
|
|
1099
|
+
interface DemoModeBridgeProvider {
|
|
1100
|
+
exchange(bridgeToken: string): Promise<{
|
|
1101
|
+
sessionJwt: string;
|
|
1102
|
+
expiresAt: number;
|
|
1103
|
+
customerId: string | null;
|
|
1104
|
+
}>;
|
|
1105
|
+
getTenantBrandKit(slug?: string): Promise<TenantBrandKit | null>;
|
|
1106
|
+
fetchHeadlessConfig(slug?: string): Promise<HeadlessConfigResponse | null>;
|
|
1107
|
+
sendTelemetry(batch: ReadonlyArray<Record<string, unknown>>): Promise<boolean>;
|
|
1108
|
+
fetchSubscriptionForOffering(offeringId: string, sessionJwt: string): Promise<SubscriptionForOfferingResponse | null>;
|
|
1109
|
+
initiateCheckout(request: InitiateCheckoutRequest, sessionJwt: string): Promise<InitiateCheckoutResponse>;
|
|
1110
|
+
fetchInvoices(filter: InvoiceListFilter, sessionJwt: string): Promise<InvoicePageResponse>;
|
|
1111
|
+
fetchInvoicePdfUrl(invoiceId: string, sessionJwt: string): Promise<InvoicePdfUrlResponse | null>;
|
|
1112
|
+
initiateInvoicePayment(invoiceId: string, request: InitiateInvoicePaymentRequest, idempotencyKey: string, sessionJwt: string): Promise<InitiateInvoicePaymentResponse>;
|
|
1113
|
+
createCart(request: CreateCartRequest, idempotencyKey: string, sessionJwt: string): Promise<CheckoutCartResponse>;
|
|
1114
|
+
getCart(cartId: string, sessionJwt: string): Promise<CheckoutCartResponse | null>;
|
|
1115
|
+
updateCustomerDetails(cartId: string, request: UpdateCustomerDetailsRequest, idempotencyKey: string, sessionJwt: string): Promise<CheckoutCartResponse>;
|
|
1116
|
+
initiatePayment(cartId: string, request: InitiateCartPaymentRequest, idempotencyKey: string, sessionJwt: string): Promise<CheckoutCartResponse>;
|
|
1117
|
+
confirmCart(cartId: string, idempotencyKey: string, sessionJwt: string): Promise<ConfirmCartResponse>;
|
|
1118
|
+
cancelCart(cartId: string, sessionJwt: string): Promise<void>;
|
|
1119
|
+
health(): Promise<{
|
|
1120
|
+
status: string;
|
|
1121
|
+
} | null>;
|
|
1122
|
+
requestMagicLink(req: RequestMagicLinkRequest, idempotencyKey?: string): Promise<RequestMagicLinkResponse>;
|
|
1123
|
+
verifyMagicLink(token: string, externalId?: string): Promise<VerifyMagicLinkResponse>;
|
|
1124
|
+
fetchSubscriptions(filter: SubscriptionListFilter, sessionJwt: string): Promise<SubscriptionPageResponse>;
|
|
1125
|
+
fetchUsage(subscriptionId: string, sessionJwt: string): Promise<UsageResponse | null>;
|
|
1126
|
+
previewSubscriptionChange(subscriptionId: string, request: PreviewChangeRequest, sessionJwt: string): Promise<PreviewChangeResponse | null>;
|
|
1127
|
+
upgradeSubscription(subscriptionId: string, request: UpgradeRequest, idempotencyKey: string, sessionJwt: string): Promise<UpgradeResponse>;
|
|
1128
|
+
cancelSubscription(subscriptionId: string, request: CancelWithFeedbackRequest, idempotencyKey: string, sessionJwt: string): Promise<CancelResponse>;
|
|
1129
|
+
fetchPaymentMethods(sessionJwt: string): Promise<PaymentMethodListResponse>;
|
|
1130
|
+
createPaymentMethodSetupIntent(sessionJwt: string, idempotencyKey: string): Promise<SetupIntentResponse | null>;
|
|
1131
|
+
setDefaultPaymentMethod(methodId: string, sessionJwt: string): Promise<void>;
|
|
1132
|
+
deletePaymentMethod(methodId: string, sessionJwt: string): Promise<void>;
|
|
1133
|
+
}
|
|
1134
|
+
/** Brand identifier — common card networks + ACH/SEPA shapes. Open string
|
|
1135
|
+
* so future providers (e.g. UPI / iDEAL) extend without an SDK bump. */
|
|
1136
|
+
type PaymentMethodBrand = "visa" | "mastercard" | "amex" | "discover" | "diners" | "jcb" | "unionpay" | "unknown" | string;
|
|
1137
|
+
/** Type — `card` is v1 baseline. `us_bank_account` / `sepa_debit` flow
|
|
1138
|
+
* through the same provider-iframe pattern in Phase 1. */
|
|
1139
|
+
type PaymentMethodType = "card" | "us_bank_account" | "sepa_debit" | string;
|
|
1140
|
+
interface PaymentMethodPayload {
|
|
1141
|
+
id: string;
|
|
1142
|
+
type: PaymentMethodType;
|
|
1143
|
+
brand: PaymentMethodBrand;
|
|
1144
|
+
last4: string;
|
|
1145
|
+
expiryMonth?: number;
|
|
1146
|
+
expiryYear?: number;
|
|
1147
|
+
holderName?: string;
|
|
1148
|
+
isDefault: boolean;
|
|
1149
|
+
/** ISO-8601 timestamp the method was created in billing-service. */
|
|
1150
|
+
createdAt?: string;
|
|
1151
|
+
}
|
|
1152
|
+
interface PaymentMethodListResponse {
|
|
1153
|
+
methods: PaymentMethodPayload[];
|
|
1154
|
+
defaultMethodId: string | null;
|
|
1155
|
+
}
|
|
1156
|
+
/** Returned by POST /payment-methods/setup-intent — the widget forwards
|
|
1157
|
+
* `clientSecret` to the parent page via the update_requested event. */
|
|
1158
|
+
interface SetupIntentResponse {
|
|
1159
|
+
clientSecret: string;
|
|
1160
|
+
provider: "stripe" | "razorpay" | "paypal" | string;
|
|
1161
|
+
/** Provider-specific opaque payload (e.g. Razorpay order id, PayPal
|
|
1162
|
+
* billing-token). Not introspected by the SDK. */
|
|
1163
|
+
providerData?: Record<string, unknown>;
|
|
1164
|
+
}
|
|
1165
|
+
declare global {
|
|
1166
|
+
interface Window {
|
|
1167
|
+
/**
|
|
1168
|
+
* The loader's shared `AforoEmbed` instance, attached at bootstrap
|
|
1169
|
+
* (see `loader/loader.ts`). Per-widget bundles (`loader/*.entry.ts`)
|
|
1170
|
+
* MUST register against this, not a locally-imported copy of
|
|
1171
|
+
* `src/core/AforoEmbed` — each widget bundle compiles to a standalone
|
|
1172
|
+
* IIFE, and importing `AforoEmbed` directly inlines a private copy of
|
|
1173
|
+
* the module (its own private `widgetRegistry`), disconnected from
|
|
1174
|
+
* the loader's copy. Registering into the private copy while the
|
|
1175
|
+
* loader's `mount()` reads from its own copy means the registration
|
|
1176
|
+
* silently never reaches the loader — "No mount handler registered"
|
|
1177
|
+
* even though the bundle loaded successfully.
|
|
1178
|
+
*/
|
|
1179
|
+
aforoEmbed?: {
|
|
1180
|
+
_registerWidget: (id: WidgetId, mountFn: (element: HTMLElement, config: WidgetMountConfig) => () => void) => void;
|
|
1181
|
+
};
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1185
|
+
export type { CheckoutCustomerDetailsSubmittedPayload as $, ApplyAt as A, PreviewChangeResponse as B, CheckoutCartType as C, DemoModeBridgeProvider as D, UpgradeRequest as E, UpgradeResponse as F, CancelWithFeedbackRequest as G, HeadlessConfigResponse as H, InvoiceStatusFilter as I, CancelResponse as J, SetupIntentResponse as K, SessionConfig as L, SessionState as M, AforoEventPayload as N, OfferingPayload as O, PaymentMethodListResponse as P, AforoEventType as Q, RequestMagicLinkRequest as R, SubscriptionPayload as S, ThemeTokenOverrides as T, UsageMetricPayload as U, VerifyMagicLinkResponse as V, WidgetErrorPayload as W, CancelReason as X, CheckoutBillingAddress as Y, CheckoutCartErrorResponse as Z, CheckoutCartStatus as _, SubscriptionForOfferingResponse as a, CheckoutExpiredPayload as a0, CheckoutFlowPhase as a1, CheckoutPaymentCompletedPayload as a2, CheckoutPaymentInitiatedPayload as a3, CheckoutPaymentProvider as a4, CheckoutStepChangedPayload as a5, ConsentState as a6, FeaturePayload as a7, HeadlessBrandingPayload as a8, InvoiceDownloadedPayload as a9, UsageMetricClickedPayload as aA, UsageThresholdReachedPayload as aB, WidgetId as aC, WidgetMountConfig as aD, InvoiceExpandedPayload as aa, InvoiceFilterChangedPayload as ab, InvoicePaidPayload as ac, InvoicePayRequestedPayload as ad, InvoiceSearchChangedPayload as ae, InvoiceStatus as af, InvoiceTypeKind as ag, LiveSubscriptionStatus as ah, MagicLinkFailureCode as ai, OfferingSummary as aj, RatePlanPayload as ak, RetentionOfferShown as al, RetentionOutcome as am, SubscriptionCancelRequestedPayload as an, SubscriptionChangeType as ao, SubscriptionClickedPayload as ap, SubscriptionFilterChangedPayload as aq, SubscriptionStatus as ar, SubscriptionUpgradeRequestedPayload as as, ThemeTokens as at, UpgradeCancelAbandonedPayload as au, UpgradeCancelCompletedPayload as av, UpgradeCancelDeflectionOfferAcceptedPayload as aw, UpgradeCancelDeflectionOfferShownPayload as ax, UpgradeCancelPreviewFetchedPayload as ay, UpgradeCancelStepChangedPayload as az, CheckoutConfirmedPayload as b, CheckoutCancelledPayload as c, CheckoutCartResponse as d, SubscriptionStatusFilter as e, SubscriptionPayloadV2 as f, SubscriptionPageResponse as g, InvoicePayload as h, InvoicePageResponse as i, UsageThreshold as j, UsageResponse as k, UpgradeCancelResultPayload as l, TenantBrandKit as m, InitiateCheckoutRequest as n, InitiateCheckoutResponse as o, InvoiceListFilter as p, InvoicePdfUrlResponse as q, InitiateInvoicePaymentRequest as r, InitiateInvoicePaymentResponse as s, CreateCartRequest as t, UpdateCustomerDetailsRequest as u, InitiateCartPaymentRequest as v, ConfirmCartResponse as w, RequestMagicLinkResponse as x, SubscriptionListFilter as y, PreviewChangeRequest as z };
|