@burdenoff/microfe-billing 2026.1027.2 → 2026.1028.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.
@@ -7,128 +7,140 @@ import { useCallback as i, useEffect as a, useRef as o, useState as s } from "re
7
7
  import { AlertCircle as c, CheckCircle2 as l, CreditCard as u, Loader2 as d, X as f } from "lucide-react";
8
8
  import { Fragment as p, jsx as m, jsxs as h } from "react/jsx-runtime";
9
9
  import { useI18n as g } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
10
+ import { createPortal as _ } from "react-dom";
11
+ import { ASSISTANT_BOTTOM_CHROME_CLEARANCE as v } from "@burdenoff/fe-libs/shared/assistant";
10
12
  //#region src/billing/modules/dashboard/components/AddPaymentMethodModal.tsx
11
- var _ = (e, t) => new Promise((n, r) => {
13
+ var y = (e, t) => new Promise((n, r) => {
12
14
  if (document.getElementById(t)) {
13
15
  n();
14
16
  return;
15
17
  }
16
18
  let i = document.createElement("script");
17
19
  i.id = t, i.src = e, i.async = !0, i.onload = () => n(), i.onerror = () => r(/* @__PURE__ */ Error(`Failed to load ${e}`)), document.head.appendChild(i);
18
- }), v = ({ isOpen: v, onClose: y, billingAccountId: b, onSuccess: x }) => {
19
- let { t: S } = g(), C = (e, t) => {
20
- let n = S(e);
20
+ }), b = ({ isOpen: b, onClose: x, billingAccountId: S, onSuccess: C }) => {
21
+ let { t: w } = g(), T = (e, t) => {
22
+ let n = w(e);
21
23
  return n === e ? t : n;
22
- }, w = o(null), T = o(null), E = o(null), D = o(null), { stripeEnabled: O, stripePublishableKey: k, isLoading: A } = r(!v), { isAddingPaymentMethod: j, verifyPaymentMethod: M } = n(), N = O, [P, F] = s("select-provider"), [I, L] = s(null), [R, z] = s(null), [B, V] = s(!1), [H, U] = s(!1), [W, G] = s(""), [K, q] = s(!0), [J, Y] = s(!1), X = i((e) => {
23
- L(e), F("add-card"), z(null);
24
+ }, E = o(null), D = o(null), O = o(null), k = o(null), { stripeEnabled: A, stripePublishableKey: j, isLoading: M } = r(!b), { isAddingPaymentMethod: N, verifyPaymentMethod: P } = n(), F = A, [I, L] = s("select-provider"), [R, z] = s(null), [B, V] = s(null), [H, U] = s(!1), [W, G] = s(!1), [K, q] = s(""), [J, Y] = s(!0), [X, Z] = s(!1), Q = i((e) => {
25
+ z(e), L("add-card"), V(null);
24
26
  }, []);
25
27
  a(() => {
26
- v ? (F("select-provider"), L(null), z(null), G(""), q(!0), Y(!1)) : E.current &&= (E.current.destroy(), null);
27
- }, [v]), a(() => {
28
- P !== "select-provider" || A || N && X("stripe");
28
+ b ? (L("select-provider"), z(null), V(null), q(""), Y(!0), Z(!1)) : O.current &&= (O.current.destroy(), null);
29
+ }, [b]), a(() => {
30
+ I !== "select-provider" || M || F && Q("stripe");
29
31
  }, [
30
- P,
31
- A,
32
- N,
33
- X
32
+ I,
33
+ M,
34
+ F,
35
+ Q
34
36
  ]), a(() => {
35
- if (!(P !== "add-card" || I !== "stripe")) {
36
- if (!k && !A) {
37
- z("Stripe is not configured. Please contact support.");
37
+ if (!(I !== "add-card" || R !== "stripe")) {
38
+ if (!j && !M) {
39
+ V("Stripe is not configured. Please contact support.");
38
40
  return;
39
41
  }
40
- return k ? ((async () => {
41
- U(!0);
42
+ return j ? ((async () => {
43
+ G(!0);
42
44
  try {
43
- if (await _("https://js.stripe.com/v3/", "stripe-js"), !window.Stripe) throw Error("Stripe SDK failed to load");
44
- let e = window.Stripe(k);
45
- D.current = e;
45
+ if (await y("https://js.stripe.com/v3/", "stripe-js"), !window.Stripe) throw Error("Stripe SDK failed to load");
46
+ let e = window.Stripe(j);
47
+ k.current = e;
46
48
  let n = 0;
47
- for (; !T.current && n < 50;) await new Promise((e) => setTimeout(e, 100)), n++;
48
- if (!T.current) throw Error("Card element container not found after 5 seconds");
49
- if (E.current) return;
49
+ for (; !D.current && n < 50;) await new Promise((e) => setTimeout(e, 100)), n++;
50
+ if (!D.current) throw Error("Card element container not found after 5 seconds");
51
+ if (O.current) return;
50
52
  let r = e.elements().create("card", { style: t("system-ui, -apple-system, sans-serif") });
51
- r.mount(T.current), E.current = r, r.on("change", (e) => {
52
- Y(e.complete), e.error ? z(e.error.message) : z(null);
53
+ r.mount(D.current), O.current = r, r.on("change", (e) => {
54
+ Z(e.complete), e.error ? V(e.error.message) : V(null);
53
55
  });
54
56
  } catch (e) {
55
- z(e instanceof Error ? e.message : "Failed to load Stripe");
57
+ V(e instanceof Error ? e.message : "Failed to load Stripe");
56
58
  } finally {
57
- U(!1);
59
+ G(!1);
58
60
  }
59
61
  })(), () => {
60
- if (E.current) try {
61
- E.current.destroy(), E.current = null;
62
+ if (O.current) try {
63
+ O.current.destroy(), O.current = null;
62
64
  } catch {}
63
65
  }) : void 0;
64
66
  }
65
67
  }, [
66
- P,
67
68
  I,
68
- k,
69
- A
69
+ R,
70
+ j,
71
+ M
70
72
  ]), a(() => {
71
73
  let e = (e) => {
72
- e.key === "Escape" && v && !B && y();
74
+ e.key === "Escape" && b && !H && x();
73
75
  };
74
76
  return document.addEventListener("keydown", e), () => document.removeEventListener("keydown", e);
75
77
  }, [
76
- v,
77
- B,
78
- y
79
- ]);
80
- let Z = i(async () => {
81
- if (!D.current || !E.current) {
82
- z("Stripe not initialized");
78
+ b,
79
+ H,
80
+ x
81
+ ]), a(() => {
82
+ if (!b) return;
83
+ let e = document.documentElement, t = document.body, n = e.style.overflow, r = t.style.overflow;
84
+ return e.style.overflow = "hidden", t.style.overflow = "hidden", () => {
85
+ e.style.overflow = n, t.style.overflow = r;
86
+ };
87
+ }, [b]);
88
+ let $ = i(async () => {
89
+ if (!k.current || !O.current) {
90
+ V("Stripe not initialized");
83
91
  return;
84
92
  }
85
- if (!W.trim()) {
86
- z("Please enter the cardholder name");
93
+ if (!K.trim()) {
94
+ V("Please enter the cardholder name");
87
95
  return;
88
96
  }
89
- V(!0), z(null);
97
+ U(!0), V(null);
90
98
  try {
91
- let t = await M({
92
- billingAccountId: b,
99
+ let t = await P({
100
+ billingAccountId: S,
93
101
  currency: "USD",
94
102
  provider: e.STRIPE,
95
- setAsDefault: K
103
+ setAsDefault: J
96
104
  });
97
105
  if (!t.clientSecret) throw Error("Failed to initiate card verification");
98
- let { setupIntent: n, error: r } = await D.current.confirmCardSetup(t.clientSecret, { payment_method: {
99
- card: E.current,
100
- billing_details: { name: W }
106
+ let { setupIntent: n, error: r } = await k.current.confirmCardSetup(t.clientSecret, { payment_method: {
107
+ card: O.current,
108
+ billing_details: { name: K }
101
109
  } });
102
110
  if (r) throw Error(r.message);
103
111
  if (n?.status !== "succeeded") throw Error("Card verification failed");
104
- F("success"), await new Promise((e) => setTimeout(e, 2e3)), x?.(), setTimeout(() => {
105
- y();
112
+ L("success"), await new Promise((e) => setTimeout(e, 2e3)), C?.(), setTimeout(() => {
113
+ x();
106
114
  }, 500);
107
115
  } catch (e) {
108
- z(e instanceof Error ? e.message : "Failed to add payment method"), V(!1);
116
+ V(e instanceof Error ? e.message : "Failed to add payment method"), U(!1);
109
117
  }
110
118
  }, [
111
- W,
112
119
  K,
113
- b,
114
- M,
115
- x,
116
- y
117
- ]), Q = i(() => {
118
- I === "stripe" && Z();
119
- }, [I, Z]);
120
- return v ? /* @__PURE__ */ h("div", {
120
+ J,
121
+ S,
122
+ P,
123
+ C,
124
+ x
125
+ ]), ee = i(() => {
126
+ R === "stripe" && $();
127
+ }, [R, $]);
128
+ return b ? _(/* @__PURE__ */ h("div", {
121
129
  className: "fixed inset-0 z-50 flex items-center justify-center",
130
+ style: {
131
+ paddingTop: "max(1rem, var(--safe-top, 0px))",
132
+ paddingBottom: `max(1rem, ${v})`
133
+ },
122
134
  role: "dialog",
123
135
  "aria-modal": "true",
124
136
  "aria-labelledby": "modal-title",
125
137
  children: [/* @__PURE__ */ m("div", {
126
138
  className: "absolute inset-0 bg-bg-overlay backdrop-blur-sm",
127
- onClick: B ? void 0 : y,
139
+ onClick: H ? void 0 : x,
128
140
  "aria-hidden": "true"
129
141
  }), /* @__PURE__ */ h("div", {
130
- ref: w,
131
- className: "relative w-full max-w-md max-h-[90vh] mx-4 bg-bg-surface border border-border-seam rounded-card shadow-[var(--shadow-elevation-4)] flex flex-col",
142
+ ref: E,
143
+ className: "relative w-full max-w-md max-h-full mx-4 bg-bg-surface border border-border-seam rounded-card shadow-[var(--shadow-elevation-4)] flex flex-col",
132
144
  children: [
133
145
  /* @__PURE__ */ h("header", {
134
146
  className: "flex items-center justify-between p-4 border-b border-border-subtle flex-shrink-0",
@@ -140,20 +152,20 @@ var _ = (e, t) => new Promise((n, r) => {
140
152
  }), /* @__PURE__ */ h("div", { children: [/* @__PURE__ */ m("h2", {
141
153
  id: "modal-title",
142
154
  className: "text-lg font-semibold text-text-primary",
143
- children: P === "success" ? "Payment Method Added" : "Add Payment Method"
155
+ children: I === "success" ? "Payment Method Added" : "Add Payment Method"
144
156
  }), /* @__PURE__ */ h("p", {
145
157
  className: "text-sm text-text-secondary",
146
158
  children: [
147
- P === "select-provider" && (N ? "Add card via Stripe" : "Standalone card save unavailable"),
148
- P === "add-card" && "Add card via Stripe",
149
- P === "processing" && "Processing...",
150
- P === "success" && "Your payment method has been saved"
159
+ I === "select-provider" && (F ? "Add card via Stripe" : "Standalone card save unavailable"),
160
+ I === "add-card" && "Add card via Stripe",
161
+ I === "processing" && "Processing...",
162
+ I === "success" && "Your payment method has been saved"
151
163
  ]
152
164
  })] })]
153
165
  }), /* @__PURE__ */ m("button", {
154
166
  type: "button",
155
- onClick: y,
156
- disabled: B,
167
+ onClick: x,
168
+ disabled: H,
157
169
  className: "p-2 rounded-button hover:bg-bg-sunken text-text-secondary hover:text-text-primary transition-colors disabled:opacity-50",
158
170
  "aria-label": "Close modal",
159
171
  children: /* @__PURE__ */ m(f, { className: "size-5" })
@@ -161,12 +173,12 @@ var _ = (e, t) => new Promise((n, r) => {
161
173
  }),
162
174
  /* @__PURE__ */ m("div", {
163
175
  className: "p-4 overflow-y-auto flex-1",
164
- children: A ? /* @__PURE__ */ m("div", {
176
+ children: M ? /* @__PURE__ */ m("div", {
165
177
  className: "flex items-center justify-center py-8",
166
178
  children: /* @__PURE__ */ m(d, { className: "size-6 animate-spin text-text-secondary" })
167
- }) : P === "select-provider" ? /* @__PURE__ */ m("div", {
179
+ }) : I === "select-provider" ? /* @__PURE__ */ m("div", {
168
180
  className: "space-y-3",
169
- children: N ? /* @__PURE__ */ m("div", {
181
+ children: F ? /* @__PURE__ */ m("div", {
170
182
  className: "flex items-center justify-center py-8",
171
183
  children: /* @__PURE__ */ m(d, { className: "size-6 animate-spin text-text-secondary" })
172
184
  }) : /* @__PURE__ */ h("div", {
@@ -175,7 +187,7 @@ var _ = (e, t) => new Promise((n, r) => {
175
187
  /* @__PURE__ */ m(c, { className: "size-12 text-status-warning-text mb-3" }),
176
188
  /* @__PURE__ */ m("p", {
177
189
  className: "font-medium text-text-primary",
178
- children: C("billing.overview.noPaymentProviders", "No Payment Providers Available")
190
+ children: T("billing.overview.noPaymentProviders", "No Payment Providers Available")
179
191
  }),
180
192
  /* @__PURE__ */ m("p", {
181
193
  className: "text-sm text-text-secondary mt-1",
@@ -183,17 +195,17 @@ var _ = (e, t) => new Promise((n, r) => {
183
195
  })
184
196
  ]
185
197
  })
186
- }) : P === "add-card" ? /* @__PURE__ */ h("div", {
198
+ }) : I === "add-card" ? /* @__PURE__ */ h("div", {
187
199
  className: "space-y-4",
188
200
  children: [
189
- R && /* @__PURE__ */ h("div", {
201
+ B && /* @__PURE__ */ h("div", {
190
202
  className: "flex items-center gap-2 p-3 rounded-lg bg-status-error-bg-subtle border border-status-error-border text-status-error-text",
191
203
  children: [/* @__PURE__ */ m(c, { className: "size-4 flex-shrink-0" }), /* @__PURE__ */ m("p", {
192
204
  className: "text-sm",
193
- children: R
205
+ children: B
194
206
  })]
195
207
  }),
196
- I === "stripe" && /* @__PURE__ */ h("div", {
208
+ R === "stripe" && /* @__PURE__ */ h("div", {
197
209
  className: "space-y-1.5",
198
210
  children: [/* @__PURE__ */ m("label", {
199
211
  htmlFor: "cardholderName",
@@ -202,24 +214,24 @@ var _ = (e, t) => new Promise((n, r) => {
202
214
  }), /* @__PURE__ */ m("input", {
203
215
  type: "text",
204
216
  id: "cardholderName",
205
- value: W,
206
- onChange: (e) => G(e.target.value),
217
+ value: K,
218
+ onChange: (e) => q(e.target.value),
207
219
  className: "w-full px-3 py-2 border border-border-default rounded-input bg-bg-surface text-text-primary placeholder:text-text-placeholder focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] focus:border-border-strong",
208
220
  placeholder: "John Doe",
209
- disabled: B
221
+ disabled: H
210
222
  })]
211
223
  }),
212
- I === "stripe" && /* @__PURE__ */ h("div", {
224
+ R === "stripe" && /* @__PURE__ */ h("div", {
213
225
  className: "space-y-1.5",
214
226
  children: [/* @__PURE__ */ m("label", {
215
227
  className: "block text-sm font-medium text-text-primary",
216
- children: C("billing.overview.cardDetails", "Card Details")
228
+ children: T("billing.overview.cardDetails", "Card Details")
217
229
  }), /* @__PURE__ */ h("div", {
218
230
  className: "relative",
219
231
  children: [/* @__PURE__ */ m("div", {
220
- ref: T,
232
+ ref: D,
221
233
  className: "p-3 border border-border-default rounded-lg bg-bg-surface min-h-[56px]"
222
- }), H && /* @__PURE__ */ h("div", {
234
+ }), W && /* @__PURE__ */ h("div", {
223
235
  className: "absolute inset-0 flex items-center justify-center bg-bg-overlay rounded-lg",
224
236
  children: [/* @__PURE__ */ m(d, { className: "size-5 animate-spin text-text-secondary" }), /* @__PURE__ */ m("span", {
225
237
  className: "ml-2 text-sm text-text-secondary",
@@ -232,13 +244,13 @@ var _ = (e, t) => new Promise((n, r) => {
232
244
  className: "flex items-center gap-2 cursor-pointer",
233
245
  children: [/* @__PURE__ */ m("input", {
234
246
  type: "checkbox",
235
- checked: K,
236
- onChange: (e) => q(e.target.checked),
247
+ checked: J,
248
+ onChange: (e) => Y(e.target.checked),
237
249
  className: "size-4 rounded border-border-default text-text-link focus:ring-[var(--color-focus-ring)]",
238
- disabled: B
250
+ disabled: H
239
251
  }), /* @__PURE__ */ m("span", {
240
252
  className: "text-sm text-text-primary",
241
- children: C("billing.overview.setAsDefault", "Set as default payment method")
253
+ children: T("billing.overview.setAsDefault", "Set as default payment method")
242
254
  })]
243
255
  }),
244
256
  /* @__PURE__ */ h("div", {
@@ -261,7 +273,7 @@ var _ = (e, t) => new Promise((n, r) => {
261
273
  }),
262
274
  /* @__PURE__ */ m("p", {
263
275
  className: "font-medium text-text-primary",
264
- children: C("billing.overview.paymentMethodAdded", "Payment method added successfully!")
276
+ children: T("billing.overview.paymentMethodAdded", "Payment method added successfully!")
265
277
  }),
266
278
  /* @__PURE__ */ m("p", {
267
279
  className: "text-sm text-text-secondary mt-1",
@@ -270,30 +282,30 @@ var _ = (e, t) => new Promise((n, r) => {
270
282
  ]
271
283
  })
272
284
  }),
273
- P !== "success" && /* @__PURE__ */ h("footer", {
285
+ I !== "success" && /* @__PURE__ */ h("footer", {
274
286
  className: "flex items-center justify-between gap-3 p-4 border-t border-border-subtle bg-bg-sunken rounded-b-lg flex-shrink-0",
275
287
  children: [/* @__PURE__ */ m("div", {}), /* @__PURE__ */ h("div", {
276
288
  className: "flex items-center gap-3",
277
289
  children: [/* @__PURE__ */ m("button", {
278
290
  type: "button",
279
- onClick: y,
280
- disabled: B,
291
+ onClick: x,
292
+ disabled: H,
281
293
  className: "px-4 py-2 text-sm font-medium text-text-primary border border-border-subtle rounded-lg hover:bg-bg-sunken transition-colors disabled:opacity-50",
282
294
  children: "Cancel"
283
- }), P === "add-card" && /* @__PURE__ */ m("button", {
295
+ }), I === "add-card" && /* @__PURE__ */ m("button", {
284
296
  type: "button",
285
- onClick: Q,
286
- disabled: B || j || H || !J,
297
+ onClick: ee,
298
+ disabled: H || N || W || !X,
287
299
  className: "inline-flex items-center gap-2 px-4 py-2 text-sm font-medium text-action-primary-text bg-action-primary-bg rounded-lg hover:bg-action-primary-bgHover transition-colors disabled:opacity-50",
288
- children: B || j ? /* @__PURE__ */ h(p, { children: [/* @__PURE__ */ m(d, { className: "size-4 animate-spin" }), "Saving..."] }) : H ? /* @__PURE__ */ h(p, { children: [/* @__PURE__ */ m(d, { className: "size-4 animate-spin" }), "Loading…"] }) : "Add Card"
300
+ children: H || N ? /* @__PURE__ */ h(p, { children: [/* @__PURE__ */ m(d, { className: "size-4 animate-spin" }), "Saving..."] }) : W ? /* @__PURE__ */ h(p, { children: [/* @__PURE__ */ m(d, { className: "size-4 animate-spin" }), "Loading…"] }) : "Add Card"
289
301
  })]
290
302
  })]
291
303
  })
292
304
  ]
293
305
  })]
294
- }) : null;
306
+ }), document.body) : null;
295
307
  };
296
308
  //#endregion
297
- export { v as AddPaymentMethodModal };
309
+ export { b as AddPaymentMethodModal };
298
310
 
299
311
  //# sourceMappingURL=AddPaymentMethodModal.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"AddPaymentMethodModal.js","names":[],"sources":["../../../../../src/billing/modules/dashboard/components/AddPaymentMethodModal.tsx"],"sourcesContent":["/**\n * Add Payment Method Modal Component\n * Modal for adding a new payment method via Stripe or Razorpay SDKs\n */\n\nimport { type FC, useState, useCallback, useEffect, useRef } from 'react';\nimport { X, CreditCard, Loader2, AlertCircle, CheckCircle2 } from 'lucide-react';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { usePaymentProviderConfig, useDashboardMutations } from '../hooks';\nimport { PaymentProviderPreference } from '../../../shared/types';\nimport { getStripeCardElementStyle } from '../../../shared/utils';\n\n// Stripe types (loaded dynamically)\ndeclare global {\n interface Window {\n Stripe?: (key: string) => StripeInstance;\n }\n}\n\ninterface StripeInstance {\n elements: () => StripeElements;\n createPaymentMethod: (options: {\n type: 'card';\n card: StripeCardElement;\n billing_details?: { name?: string };\n }) => Promise<{\n paymentMethod?: {\n id: string;\n card?: { brand: string; last4: string; exp_month: number; exp_year: number };\n };\n error?: { message: string };\n }>;\n confirmCardSetup: (\n clientSecret: string,\n options?: {\n payment_method?: {\n card: StripeCardElement;\n billing_details?: { name?: string };\n };\n }\n ) => Promise<{\n setupIntent?: {\n id: string;\n status: string;\n payment_method?: string;\n };\n error?: { message: string };\n }>;\n}\n\ninterface StripeElements {\n create: (type: 'card', options?: object) => StripeCardElement;\n}\n\ninterface StripeCardElement {\n mount: (element: HTMLElement | string) => void;\n unmount: () => void;\n on: (\n event: string,\n handler: (event: { complete: boolean; error?: { message: string } }) => void\n ) => void;\n destroy: () => void;\n}\n\ninterface AddPaymentMethodModalProps {\n isOpen: boolean;\n onClose: () => void;\n billingAccountId: string;\n currency: string; // e.g., 'INR', 'USD'\n country?: string; // e.g., 'IN', 'US' - from billing address\n onSuccess?: () => void;\n}\n\ntype ModalStep = 'select-provider' | 'add-card' | 'processing' | 'success';\n\n// Load external script dynamically\nconst loadScript = (src: string, id: string): Promise<void> => {\n return new Promise((resolve, reject) => {\n if (document.getElementById(id)) {\n resolve();\n return;\n }\n const script = document.createElement('script');\n script.id = id;\n script.src = src;\n script.async = true;\n script.onload = () => resolve();\n script.onerror = () => reject(new Error(`Failed to load ${src}`));\n document.head.appendChild(script);\n });\n};\n\nexport const AddPaymentMethodModal: FC<AddPaymentMethodModalProps> = ({\n isOpen,\n onClose,\n billingAccountId,\n onSuccess,\n}) => {\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const translated = t(key);\n return translated === key ? fallback : translated;\n };\n const modalRef = useRef<HTMLDivElement>(null);\n const cardElementRef = useRef<HTMLDivElement>(null);\n const stripeCardElementRef = useRef<StripeCardElement | null>(null);\n const stripeRef = useRef<StripeInstance | null>(null);\n\n // Only fetch payment provider config when modal is open\n const {\n stripeEnabled,\n stripePublishableKey,\n isLoading: isLoadingConfig,\n } = usePaymentProviderConfig(!isOpen);\n const { isAddingPaymentMethod, verifyPaymentMethod } = useDashboardMutations();\n\n // Standalone \"add a card without purchasing anything\" is only offered via\n // Stripe's SetupIntent flow (a genuine $0 card verification). Razorpay has\n // no equivalent zero-charge save: their Checkout.js widget only remembers a\n // card as a side effect of a real payment (customer_id + remember_customer),\n // and our backend's Razorpay \"attach payment method\" call is a documented\n // no-op stub. Offering it here previously meant charging (and refunding) a\n // real ₹1 for a save that didn't actually do anything durable — Razorpay's\n // own remember-card behavior already works during real checkout regardless.\n // Stripe is offered to every account regardless of country/currency, same\n // as checkout itself does (PaymentProviderSelector gates Stripe purely on\n // `stripeEnabled`, never on billing country) — see BOFF-7192 follow-up for\n // a real Razorpay saved-card implementation (Tokens API + Recurring\n // Payments) to close the gap for accounts that only pay via Razorpay.\n const showStripe = stripeEnabled;\n\n const [step, setStep] = useState<ModalStep>('select-provider');\n const [selectedProvider, setSelectedProvider] = useState<'stripe' | null>(null);\n const [error, setError] = useState<string | null>(null);\n const [isProcessing, setIsProcessing] = useState(false);\n const [isLoadingSdk, setIsLoadingSdk] = useState(false);\n const [cardholderName, setCardholderName] = useState('');\n const [setAsDefault, setSetAsDefault] = useState(true);\n const [isCardComplete, setIsCardComplete] = useState(false);\n\n // Must be declared before the effects that reference it\n const handleSelectProvider = useCallback((provider: 'stripe') => {\n setSelectedProvider(provider);\n setStep('add-card');\n setError(null);\n }, []);\n\n // Reset state when modal opens/closes\n useEffect(() => {\n if (isOpen) {\n setStep('select-provider');\n setSelectedProvider(null);\n setError(null);\n setCardholderName('');\n setSetAsDefault(true);\n setIsCardComplete(false);\n } else {\n // Cleanup Stripe element when modal closes\n if (stripeCardElementRef.current) {\n stripeCardElementRef.current.destroy();\n stripeCardElementRef.current = null;\n }\n }\n }, [isOpen]);\n\n // Stripe is the only provider this modal ever offers, so skip straight to\n // the card form when it's available (no provider-selection screen needed).\n useEffect(() => {\n if (step !== 'select-provider' || isLoadingConfig) return;\n if (showStripe) {\n handleSelectProvider('stripe');\n }\n }, [step, isLoadingConfig, showStripe, handleSelectProvider]);\n\n // Initialize Stripe when provider is selected\n useEffect(() => {\n if (step !== 'add-card' || selectedProvider !== 'stripe') return;\n\n // Show error if config loaded but key is missing\n if (!stripePublishableKey && !isLoadingConfig) {\n setError('Stripe is not configured. Please contact support.');\n return;\n }\n\n if (!stripePublishableKey) return;\n\n const initStripe = async () => {\n setIsLoadingSdk(true);\n try {\n await loadScript('https://js.stripe.com/v3/', 'stripe-js');\n\n if (!window.Stripe) {\n throw new Error('Stripe SDK failed to load');\n }\n\n const stripe = window.Stripe(stripePublishableKey);\n stripeRef.current = stripe;\n\n // Wait for the card element container to be mounted in the DOM\n let attempts = 0;\n while (!cardElementRef.current && attempts < 50) {\n await new Promise((resolve) => setTimeout(resolve, 100));\n attempts++;\n }\n\n if (!cardElementRef.current) {\n throw new Error('Card element container not found after 5 seconds');\n }\n\n if (stripeCardElementRef.current) {\n return;\n }\n\n const elements = stripe.elements();\n const cardElement = elements.create('card', {\n style: getStripeCardElementStyle('system-ui, -apple-system, sans-serif'),\n });\n\n cardElement.mount(cardElementRef.current);\n stripeCardElementRef.current = cardElement;\n\n cardElement.on('change', (event) => {\n setIsCardComplete(event.complete);\n if (event.error) {\n setError(event.error.message);\n } else {\n setError(null);\n }\n });\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to load Stripe');\n } finally {\n setIsLoadingSdk(false);\n }\n };\n\n initStripe();\n\n return () => {\n if (stripeCardElementRef.current) {\n try {\n stripeCardElementRef.current.destroy();\n stripeCardElementRef.current = null;\n } catch {\n // Stripe element may already be detached during modal teardown.\n }\n }\n };\n }, [step, selectedProvider, stripePublishableKey, isLoadingConfig]);\n\n // Handle escape key\n useEffect(() => {\n const handleKeyDown = (e: KeyboardEvent) => {\n if (e.key === 'Escape' && isOpen && !isProcessing) {\n onClose();\n }\n };\n document.addEventListener('keydown', handleKeyDown);\n return () => document.removeEventListener('keydown', handleKeyDown);\n }, [isOpen, isProcessing, onClose]);\n\n const handleStripeSubmit = useCallback(async () => {\n if (!stripeRef.current || !stripeCardElementRef.current) {\n setError('Stripe not initialized');\n return;\n }\n\n if (!cardholderName.trim()) {\n setError('Please enter the cardholder name');\n return;\n }\n\n setIsProcessing(true);\n setError(null);\n\n try {\n // Step 1: Call backend to create SetupIntent and get client secret\n const verificationResult = await verifyPaymentMethod({\n billingAccountId,\n currency: 'USD',\n provider: PaymentProviderPreference.STRIPE,\n setAsDefault,\n });\n\n if (!verificationResult.clientSecret) {\n throw new Error('Failed to initiate card verification');\n }\n\n // Step 2: Confirm card setup using the client secret (no charge)\n const { setupIntent, error: stripeError } = await stripeRef.current.confirmCardSetup(\n verificationResult.clientSecret,\n {\n payment_method: {\n card: stripeCardElementRef.current,\n billing_details: { name: cardholderName },\n },\n }\n );\n\n if (stripeError) {\n throw new Error(stripeError.message);\n }\n\n if (setupIntent?.status !== 'succeeded') {\n throw new Error('Card verification failed');\n }\n\n // Step 3: Show success (webhook will save the payment method)\n setStep('success');\n\n // Wait for webhook to process (2 seconds) before refetching\n await new Promise((resolve) => setTimeout(resolve, 2000));\n onSuccess?.();\n\n // Show success message briefly before closing\n setTimeout(() => {\n onClose();\n }, 500);\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to add payment method');\n setIsProcessing(false);\n }\n }, [cardholderName, setAsDefault, billingAccountId, verifyPaymentMethod, onSuccess, onClose]);\n\n const handleSubmit = useCallback(() => {\n if (selectedProvider === 'stripe') {\n handleStripeSubmit();\n }\n }, [selectedProvider, handleStripeSubmit]);\n\n if (!isOpen) return null;\n\n return (\n <div\n className=\"fixed inset-0 z-50 flex items-center justify-center\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby=\"modal-title\"\n >\n {/* Backdrop */}\n <div\n className=\"absolute inset-0 bg-bg-overlay backdrop-blur-sm\"\n onClick={!isProcessing ? onClose : undefined}\n aria-hidden=\"true\"\n />\n\n {/* Modal */}\n <div\n ref={modalRef}\n className=\"relative w-full max-w-md max-h-[90vh] mx-4 bg-bg-surface border border-border-seam rounded-card shadow-[var(--shadow-elevation-4)] flex flex-col\"\n >\n {/* Header */}\n <header className=\"flex items-center justify-between p-4 border-b border-border-subtle flex-shrink-0\">\n <div className=\"flex items-center gap-3\">\n <div className=\"size-10 rounded-lg bg-[var(--color-accent-soft)] flex items-center justify-center\">\n <CreditCard className=\"size-5 text-text-link\" />\n </div>\n <div>\n <h2 id=\"modal-title\" className=\"text-lg font-semibold text-text-primary\">\n {step === 'success' ? 'Payment Method Added' : 'Add Payment Method'}\n </h2>\n <p className=\"text-sm text-text-secondary\">\n {step === 'select-provider' &&\n (showStripe ? 'Add card via Stripe' : 'Standalone card save unavailable')}\n {step === 'add-card' && 'Add card via Stripe'}\n {step === 'processing' && 'Processing...'}\n {step === 'success' && 'Your payment method has been saved'}\n </p>\n </div>\n </div>\n <button\n type=\"button\"\n onClick={onClose}\n disabled={isProcessing}\n className=\"p-2 rounded-button hover:bg-bg-sunken text-text-secondary hover:text-text-primary transition-colors disabled:opacity-50\"\n aria-label=\"Close modal\"\n >\n <X className=\"size-5\" />\n </button>\n </header>\n\n {/* Content */}\n <div className=\"p-4 overflow-y-auto flex-1\">\n {isLoadingConfig ? (\n <div className=\"flex items-center justify-center py-8\">\n <Loader2 className=\"size-6 animate-spin text-text-secondary\" />\n </div>\n ) : step === 'select-provider' ? (\n <div className=\"space-y-3\">\n {showStripe ? (\n <div className=\"flex items-center justify-center py-8\">\n <Loader2 className=\"size-6 animate-spin text-text-secondary\" />\n </div>\n ) : (\n <div className=\"flex flex-col items-center py-8 text-center\">\n <AlertCircle className=\"size-12 text-status-warning-text mb-3\" />\n <p className=\"font-medium text-text-primary\">\n {tr('billing.overview.noPaymentProviders', 'No Payment Providers Available')}\n </p>\n <p className=\"text-sm text-text-secondary mt-1\">\n Card saving isn&apos;t configured for this account yet. Please contact support.\n </p>\n </div>\n )}\n </div>\n ) : step === 'add-card' ? (\n <div className=\"space-y-4\">\n {/* Error Message */}\n {error && (\n <div className=\"flex items-center gap-2 p-3 rounded-lg bg-status-error-bg-subtle border border-status-error-border text-status-error-text\">\n <AlertCircle className=\"size-4 flex-shrink-0\" />\n <p className=\"text-sm\">{error}</p>\n </div>\n )}\n\n {/* Cardholder Name - only for Stripe */}\n {selectedProvider === 'stripe' && (\n <div className=\"space-y-1.5\">\n <label\n htmlFor=\"cardholderName\"\n className=\"block text-sm font-medium text-text-primary\"\n >\n Cardholder Name\n </label>\n <input\n type=\"text\"\n id=\"cardholderName\"\n value={cardholderName}\n onChange={(e) => setCardholderName(e.target.value)}\n className=\"w-full px-3 py-2 border border-border-default rounded-input bg-bg-surface text-text-primary placeholder:text-text-placeholder focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] focus:border-border-strong\"\n placeholder=\"John Doe\"\n disabled={isProcessing}\n />\n </div>\n )}\n\n {/* Stripe Card Element */}\n {selectedProvider === 'stripe' && (\n <div className=\"space-y-1.5\">\n <label className=\"block text-sm font-medium text-text-primary\">\n {tr('billing.overview.cardDetails', 'Card Details')}\n </label>\n <div className=\"relative\">\n <div\n ref={cardElementRef}\n className=\"p-3 border border-border-default rounded-lg bg-bg-surface min-h-[56px]\"\n />\n {isLoadingSdk && (\n <div className=\"absolute inset-0 flex items-center justify-center bg-bg-overlay rounded-lg\">\n <Loader2 className=\"size-5 animate-spin text-text-secondary\" />\n <span className=\"ml-2 text-sm text-text-secondary\">Loading Stripe…</span>\n </div>\n )}\n </div>\n </div>\n )}\n\n {/* Set as default */}\n <label className=\"flex items-center gap-2 cursor-pointer\">\n <input\n type=\"checkbox\"\n checked={setAsDefault}\n onChange={(e) => setSetAsDefault(e.target.checked)}\n className=\"size-4 rounded border-border-default text-text-link focus:ring-[var(--color-focus-ring)]\"\n disabled={isProcessing}\n />\n <span className=\"text-sm text-text-primary\">\n {tr('billing.overview.setAsDefault', 'Set as default payment method')}\n </span>\n </label>\n\n {/* Provider badge */}\n <div className=\"flex items-center gap-2 pt-2 border-t border-border-subtle\">\n <span className=\"text-xs text-text-secondary\">Secured by</span>\n <span className=\"inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-accent-violet-subtle text-accent-violet\">\n Stripe\n </span>\n </div>\n </div>\n ) : (\n /* Success state */\n <div className=\"flex flex-col items-center py-8 text-center\">\n <div className=\"size-16 rounded-full bg-status-success-bg-subtle flex items-center justify-center mb-4\">\n <CheckCircle2 className=\"size-8 text-status-success-text\" />\n </div>\n <p className=\"font-medium text-text-primary\">\n {tr('billing.overview.paymentMethodAdded', 'Payment method added successfully!')}\n </p>\n <p className=\"text-sm text-text-secondary mt-1\">\n Your payment method has been securely saved.\n </p>\n </div>\n )}\n </div>\n\n {/* Footer */}\n {step !== 'success' && (\n <footer className=\"flex items-center justify-between gap-3 p-4 border-t border-border-subtle bg-bg-sunken rounded-b-lg flex-shrink-0\">\n <div />\n <div className=\"flex items-center gap-3\">\n <button\n type=\"button\"\n onClick={onClose}\n disabled={isProcessing}\n className=\"px-4 py-2 text-sm font-medium text-text-primary border border-border-subtle rounded-lg hover:bg-bg-sunken transition-colors disabled:opacity-50\"\n >\n Cancel\n </button>\n {step === 'add-card' && (\n <button\n type=\"button\"\n onClick={handleSubmit}\n disabled={\n isProcessing || isAddingPaymentMethod || isLoadingSdk || !isCardComplete\n }\n className=\"inline-flex items-center gap-2 px-4 py-2 text-sm font-medium text-action-primary-text bg-action-primary-bg rounded-lg hover:bg-action-primary-bgHover transition-colors disabled:opacity-50\"\n >\n {isProcessing || isAddingPaymentMethod ? (\n <>\n <Loader2 className=\"size-4 animate-spin\" />\n Saving...\n </>\n ) : isLoadingSdk ? (\n <>\n <Loader2 className=\"size-4 animate-spin\" />\n Loading…\n </>\n ) : (\n 'Add Card'\n )}\n </button>\n )}\n </div>\n </footer>\n )}\n </div>\n </div>\n );\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"AddPaymentMethodModal.js","names":[],"sources":["../../../../../src/billing/modules/dashboard/components/AddPaymentMethodModal.tsx"],"sourcesContent":["/**\n * Add Payment Method Modal Component\n * Modal for adding a new payment method via Stripe or Razorpay SDKs\n */\n\nimport { type FC, useState, useCallback, useEffect, useRef } from 'react';\nimport { createPortal } from 'react-dom';\nimport { X, CreditCard, Loader2, AlertCircle, CheckCircle2 } from 'lucide-react';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { ASSISTANT_BOTTOM_CHROME_CLEARANCE } from '@burdenoff/fe-libs/shared/assistant';\nimport { usePaymentProviderConfig, useDashboardMutations } from '../hooks';\nimport { PaymentProviderPreference } from '../../../shared/types';\nimport { getStripeCardElementStyle } from '../../../shared/utils';\n\n// Stripe types (loaded dynamically)\ndeclare global {\n interface Window {\n Stripe?: (key: string) => StripeInstance;\n }\n}\n\ninterface StripeInstance {\n elements: () => StripeElements;\n createPaymentMethod: (options: {\n type: 'card';\n card: StripeCardElement;\n billing_details?: { name?: string };\n }) => Promise<{\n paymentMethod?: {\n id: string;\n card?: { brand: string; last4: string; exp_month: number; exp_year: number };\n };\n error?: { message: string };\n }>;\n confirmCardSetup: (\n clientSecret: string,\n options?: {\n payment_method?: {\n card: StripeCardElement;\n billing_details?: { name?: string };\n };\n }\n ) => Promise<{\n setupIntent?: {\n id: string;\n status: string;\n payment_method?: string;\n };\n error?: { message: string };\n }>;\n}\n\ninterface StripeElements {\n create: (type: 'card', options?: object) => StripeCardElement;\n}\n\ninterface StripeCardElement {\n mount: (element: HTMLElement | string) => void;\n unmount: () => void;\n on: (\n event: string,\n handler: (event: { complete: boolean; error?: { message: string } }) => void\n ) => void;\n destroy: () => void;\n}\n\ninterface AddPaymentMethodModalProps {\n isOpen: boolean;\n onClose: () => void;\n billingAccountId: string;\n currency: string; // e.g., 'INR', 'USD'\n country?: string; // e.g., 'IN', 'US' - from billing address\n onSuccess?: () => void;\n}\n\ntype ModalStep = 'select-provider' | 'add-card' | 'processing' | 'success';\n\n// Load external script dynamically\nconst loadScript = (src: string, id: string): Promise<void> => {\n return new Promise((resolve, reject) => {\n if (document.getElementById(id)) {\n resolve();\n return;\n }\n const script = document.createElement('script');\n script.id = id;\n script.src = src;\n script.async = true;\n script.onload = () => resolve();\n script.onerror = () => reject(new Error(`Failed to load ${src}`));\n document.head.appendChild(script);\n });\n};\n\nexport const AddPaymentMethodModal: FC<AddPaymentMethodModalProps> = ({\n isOpen,\n onClose,\n billingAccountId,\n onSuccess,\n}) => {\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const translated = t(key);\n return translated === key ? fallback : translated;\n };\n const modalRef = useRef<HTMLDivElement>(null);\n const cardElementRef = useRef<HTMLDivElement>(null);\n const stripeCardElementRef = useRef<StripeCardElement | null>(null);\n const stripeRef = useRef<StripeInstance | null>(null);\n\n // Only fetch payment provider config when modal is open\n const {\n stripeEnabled,\n stripePublishableKey,\n isLoading: isLoadingConfig,\n } = usePaymentProviderConfig(!isOpen);\n const { isAddingPaymentMethod, verifyPaymentMethod } = useDashboardMutations();\n\n // Standalone \"add a card without purchasing anything\" is only offered via\n // Stripe's SetupIntent flow (a genuine $0 card verification). Razorpay has\n // no equivalent zero-charge save: their Checkout.js widget only remembers a\n // card as a side effect of a real payment (customer_id + remember_customer),\n // and our backend's Razorpay \"attach payment method\" call is a documented\n // no-op stub. Offering it here previously meant charging (and refunding) a\n // real ₹1 for a save that didn't actually do anything durable — Razorpay's\n // own remember-card behavior already works during real checkout regardless.\n // Stripe is offered to every account regardless of country/currency, same\n // as checkout itself does (PaymentProviderSelector gates Stripe purely on\n // `stripeEnabled`, never on billing country) — see BOFF-7192 follow-up for\n // a real Razorpay saved-card implementation (Tokens API + Recurring\n // Payments) to close the gap for accounts that only pay via Razorpay.\n const showStripe = stripeEnabled;\n\n const [step, setStep] = useState<ModalStep>('select-provider');\n const [selectedProvider, setSelectedProvider] = useState<'stripe' | null>(null);\n const [error, setError] = useState<string | null>(null);\n const [isProcessing, setIsProcessing] = useState(false);\n const [isLoadingSdk, setIsLoadingSdk] = useState(false);\n const [cardholderName, setCardholderName] = useState('');\n const [setAsDefault, setSetAsDefault] = useState(true);\n const [isCardComplete, setIsCardComplete] = useState(false);\n\n // Must be declared before the effects that reference it\n const handleSelectProvider = useCallback((provider: 'stripe') => {\n setSelectedProvider(provider);\n setStep('add-card');\n setError(null);\n }, []);\n\n // Reset state when modal opens/closes\n useEffect(() => {\n if (isOpen) {\n setStep('select-provider');\n setSelectedProvider(null);\n setError(null);\n setCardholderName('');\n setSetAsDefault(true);\n setIsCardComplete(false);\n } else {\n // Cleanup Stripe element when modal closes\n if (stripeCardElementRef.current) {\n stripeCardElementRef.current.destroy();\n stripeCardElementRef.current = null;\n }\n }\n }, [isOpen]);\n\n // Stripe is the only provider this modal ever offers, so skip straight to\n // the card form when it's available (no provider-selection screen needed).\n useEffect(() => {\n if (step !== 'select-provider' || isLoadingConfig) return;\n if (showStripe) {\n handleSelectProvider('stripe');\n }\n }, [step, isLoadingConfig, showStripe, handleSelectProvider]);\n\n // Initialize Stripe when provider is selected\n useEffect(() => {\n if (step !== 'add-card' || selectedProvider !== 'stripe') return;\n\n // Show error if config loaded but key is missing\n if (!stripePublishableKey && !isLoadingConfig) {\n setError('Stripe is not configured. Please contact support.');\n return;\n }\n\n if (!stripePublishableKey) return;\n\n const initStripe = async () => {\n setIsLoadingSdk(true);\n try {\n await loadScript('https://js.stripe.com/v3/', 'stripe-js');\n\n if (!window.Stripe) {\n throw new Error('Stripe SDK failed to load');\n }\n\n const stripe = window.Stripe(stripePublishableKey);\n stripeRef.current = stripe;\n\n // Wait for the card element container to be mounted in the DOM\n let attempts = 0;\n while (!cardElementRef.current && attempts < 50) {\n await new Promise((resolve) => setTimeout(resolve, 100));\n attempts++;\n }\n\n if (!cardElementRef.current) {\n throw new Error('Card element container not found after 5 seconds');\n }\n\n if (stripeCardElementRef.current) {\n return;\n }\n\n const elements = stripe.elements();\n const cardElement = elements.create('card', {\n style: getStripeCardElementStyle('system-ui, -apple-system, sans-serif'),\n });\n\n cardElement.mount(cardElementRef.current);\n stripeCardElementRef.current = cardElement;\n\n cardElement.on('change', (event) => {\n setIsCardComplete(event.complete);\n if (event.error) {\n setError(event.error.message);\n } else {\n setError(null);\n }\n });\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to load Stripe');\n } finally {\n setIsLoadingSdk(false);\n }\n };\n\n initStripe();\n\n return () => {\n if (stripeCardElementRef.current) {\n try {\n stripeCardElementRef.current.destroy();\n stripeCardElementRef.current = null;\n } catch {\n // Stripe element may already be detached during modal teardown.\n }\n }\n };\n }, [step, selectedProvider, stripePublishableKey, isLoadingConfig]);\n\n // Handle escape key\n useEffect(() => {\n const handleKeyDown = (e: KeyboardEvent) => {\n if (e.key === 'Escape' && isOpen && !isProcessing) {\n onClose();\n }\n };\n document.addEventListener('keydown', handleKeyDown);\n return () => document.removeEventListener('keydown', handleKeyDown);\n }, [isOpen, isProcessing, onClose]);\n\n // Lock background scroll while open. Without this, swiping up on the page\n // behind the modal scrolls it (and, on the installed app, carries the\n // modal along too — see the portal below), so the form content moves out\n // of view. Locking both html and body covers iOS Safari/WebView, which\n // can rubber-band-scroll one but not the other depending on which has the\n // overflow.\n useEffect(() => {\n if (!isOpen) return;\n const htmlEl = document.documentElement;\n const bodyEl = document.body;\n const prevHtmlOverflow = htmlEl.style.overflow;\n const prevBodyOverflow = bodyEl.style.overflow;\n htmlEl.style.overflow = 'hidden';\n bodyEl.style.overflow = 'hidden';\n return () => {\n htmlEl.style.overflow = prevHtmlOverflow;\n bodyEl.style.overflow = prevBodyOverflow;\n };\n }, [isOpen]);\n\n const handleStripeSubmit = useCallback(async () => {\n if (!stripeRef.current || !stripeCardElementRef.current) {\n setError('Stripe not initialized');\n return;\n }\n\n if (!cardholderName.trim()) {\n setError('Please enter the cardholder name');\n return;\n }\n\n setIsProcessing(true);\n setError(null);\n\n try {\n // Step 1: Call backend to create SetupIntent and get client secret\n const verificationResult = await verifyPaymentMethod({\n billingAccountId,\n currency: 'USD',\n provider: PaymentProviderPreference.STRIPE,\n setAsDefault,\n });\n\n if (!verificationResult.clientSecret) {\n throw new Error('Failed to initiate card verification');\n }\n\n // Step 2: Confirm card setup using the client secret (no charge)\n const { setupIntent, error: stripeError } = await stripeRef.current.confirmCardSetup(\n verificationResult.clientSecret,\n {\n payment_method: {\n card: stripeCardElementRef.current,\n billing_details: { name: cardholderName },\n },\n }\n );\n\n if (stripeError) {\n throw new Error(stripeError.message);\n }\n\n if (setupIntent?.status !== 'succeeded') {\n throw new Error('Card verification failed');\n }\n\n // Step 3: Show success (webhook will save the payment method)\n setStep('success');\n\n // Wait for webhook to process (2 seconds) before refetching\n await new Promise((resolve) => setTimeout(resolve, 2000));\n onSuccess?.();\n\n // Show success message briefly before closing\n setTimeout(() => {\n onClose();\n }, 500);\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to add payment method');\n setIsProcessing(false);\n }\n }, [cardholderName, setAsDefault, billingAccountId, verifyPaymentMethod, onSuccess, onClose]);\n\n const handleSubmit = useCallback(() => {\n if (selectedProvider === 'stripe') {\n handleStripeSubmit();\n }\n }, [selectedProvider, handleStripeSubmit]);\n\n if (!isOpen) return null;\n\n // Portaled to <body>, not rendered inside the page tree: on the installed\n // app, the route wrapper (NativePageTransition) animates with a CSS\n // transform, which makes a `position: fixed` descendant fixed to THAT\n // transformed ancestor instead of the viewport — the modal scrolled with\n // the underlying page instead of staying put. Same root cause and fix as\n // the floating checkout chip (BOFF-9983/9984).\n //\n // The outer wrapper's padding keeps the dialog fully inside the safe\n // area and above the tab bar instead of only centering within 100vh.\n return createPortal(\n <div\n className=\"fixed inset-0 z-50 flex items-center justify-center\"\n style={{\n paddingTop: 'max(1rem, var(--safe-top, 0px))',\n paddingBottom: `max(1rem, ${ASSISTANT_BOTTOM_CHROME_CLEARANCE})`,\n }}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby=\"modal-title\"\n >\n {/* Backdrop */}\n <div\n className=\"absolute inset-0 bg-bg-overlay backdrop-blur-sm\"\n onClick={!isProcessing ? onClose : undefined}\n aria-hidden=\"true\"\n />\n\n {/* Modal */}\n <div\n ref={modalRef}\n className=\"relative w-full max-w-md max-h-full mx-4 bg-bg-surface border border-border-seam rounded-card shadow-[var(--shadow-elevation-4)] flex flex-col\"\n >\n {/* Header */}\n <header className=\"flex items-center justify-between p-4 border-b border-border-subtle flex-shrink-0\">\n <div className=\"flex items-center gap-3\">\n <div className=\"size-10 rounded-lg bg-[var(--color-accent-soft)] flex items-center justify-center\">\n <CreditCard className=\"size-5 text-text-link\" />\n </div>\n <div>\n <h2 id=\"modal-title\" className=\"text-lg font-semibold text-text-primary\">\n {step === 'success' ? 'Payment Method Added' : 'Add Payment Method'}\n </h2>\n <p className=\"text-sm text-text-secondary\">\n {step === 'select-provider' &&\n (showStripe ? 'Add card via Stripe' : 'Standalone card save unavailable')}\n {step === 'add-card' && 'Add card via Stripe'}\n {step === 'processing' && 'Processing...'}\n {step === 'success' && 'Your payment method has been saved'}\n </p>\n </div>\n </div>\n <button\n type=\"button\"\n onClick={onClose}\n disabled={isProcessing}\n className=\"p-2 rounded-button hover:bg-bg-sunken text-text-secondary hover:text-text-primary transition-colors disabled:opacity-50\"\n aria-label=\"Close modal\"\n >\n <X className=\"size-5\" />\n </button>\n </header>\n\n {/* Content */}\n <div className=\"p-4 overflow-y-auto flex-1\">\n {isLoadingConfig ? (\n <div className=\"flex items-center justify-center py-8\">\n <Loader2 className=\"size-6 animate-spin text-text-secondary\" />\n </div>\n ) : step === 'select-provider' ? (\n <div className=\"space-y-3\">\n {showStripe ? (\n <div className=\"flex items-center justify-center py-8\">\n <Loader2 className=\"size-6 animate-spin text-text-secondary\" />\n </div>\n ) : (\n <div className=\"flex flex-col items-center py-8 text-center\">\n <AlertCircle className=\"size-12 text-status-warning-text mb-3\" />\n <p className=\"font-medium text-text-primary\">\n {tr('billing.overview.noPaymentProviders', 'No Payment Providers Available')}\n </p>\n <p className=\"text-sm text-text-secondary mt-1\">\n Card saving isn&apos;t configured for this account yet. Please contact support.\n </p>\n </div>\n )}\n </div>\n ) : step === 'add-card' ? (\n <div className=\"space-y-4\">\n {/* Error Message */}\n {error && (\n <div className=\"flex items-center gap-2 p-3 rounded-lg bg-status-error-bg-subtle border border-status-error-border text-status-error-text\">\n <AlertCircle className=\"size-4 flex-shrink-0\" />\n <p className=\"text-sm\">{error}</p>\n </div>\n )}\n\n {/* Cardholder Name - only for Stripe */}\n {selectedProvider === 'stripe' && (\n <div className=\"space-y-1.5\">\n <label\n htmlFor=\"cardholderName\"\n className=\"block text-sm font-medium text-text-primary\"\n >\n Cardholder Name\n </label>\n <input\n type=\"text\"\n id=\"cardholderName\"\n value={cardholderName}\n onChange={(e) => setCardholderName(e.target.value)}\n className=\"w-full px-3 py-2 border border-border-default rounded-input bg-bg-surface text-text-primary placeholder:text-text-placeholder focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] focus:border-border-strong\"\n placeholder=\"John Doe\"\n disabled={isProcessing}\n />\n </div>\n )}\n\n {/* Stripe Card Element */}\n {selectedProvider === 'stripe' && (\n <div className=\"space-y-1.5\">\n <label className=\"block text-sm font-medium text-text-primary\">\n {tr('billing.overview.cardDetails', 'Card Details')}\n </label>\n <div className=\"relative\">\n <div\n ref={cardElementRef}\n className=\"p-3 border border-border-default rounded-lg bg-bg-surface min-h-[56px]\"\n />\n {isLoadingSdk && (\n <div className=\"absolute inset-0 flex items-center justify-center bg-bg-overlay rounded-lg\">\n <Loader2 className=\"size-5 animate-spin text-text-secondary\" />\n <span className=\"ml-2 text-sm text-text-secondary\">Loading Stripe…</span>\n </div>\n )}\n </div>\n </div>\n )}\n\n {/* Set as default */}\n <label className=\"flex items-center gap-2 cursor-pointer\">\n <input\n type=\"checkbox\"\n checked={setAsDefault}\n onChange={(e) => setSetAsDefault(e.target.checked)}\n className=\"size-4 rounded border-border-default text-text-link focus:ring-[var(--color-focus-ring)]\"\n disabled={isProcessing}\n />\n <span className=\"text-sm text-text-primary\">\n {tr('billing.overview.setAsDefault', 'Set as default payment method')}\n </span>\n </label>\n\n {/* Provider badge */}\n <div className=\"flex items-center gap-2 pt-2 border-t border-border-subtle\">\n <span className=\"text-xs text-text-secondary\">Secured by</span>\n <span className=\"inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-accent-violet-subtle text-accent-violet\">\n Stripe\n </span>\n </div>\n </div>\n ) : (\n /* Success state */\n <div className=\"flex flex-col items-center py-8 text-center\">\n <div className=\"size-16 rounded-full bg-status-success-bg-subtle flex items-center justify-center mb-4\">\n <CheckCircle2 className=\"size-8 text-status-success-text\" />\n </div>\n <p className=\"font-medium text-text-primary\">\n {tr('billing.overview.paymentMethodAdded', 'Payment method added successfully!')}\n </p>\n <p className=\"text-sm text-text-secondary mt-1\">\n Your payment method has been securely saved.\n </p>\n </div>\n )}\n </div>\n\n {/* Footer */}\n {step !== 'success' && (\n <footer className=\"flex items-center justify-between gap-3 p-4 border-t border-border-subtle bg-bg-sunken rounded-b-lg flex-shrink-0\">\n <div />\n <div className=\"flex items-center gap-3\">\n <button\n type=\"button\"\n onClick={onClose}\n disabled={isProcessing}\n className=\"px-4 py-2 text-sm font-medium text-text-primary border border-border-subtle rounded-lg hover:bg-bg-sunken transition-colors disabled:opacity-50\"\n >\n Cancel\n </button>\n {step === 'add-card' && (\n <button\n type=\"button\"\n onClick={handleSubmit}\n disabled={\n isProcessing || isAddingPaymentMethod || isLoadingSdk || !isCardComplete\n }\n className=\"inline-flex items-center gap-2 px-4 py-2 text-sm font-medium text-action-primary-text bg-action-primary-bg rounded-lg hover:bg-action-primary-bgHover transition-colors disabled:opacity-50\"\n >\n {isProcessing || isAddingPaymentMethod ? (\n <>\n <Loader2 className=\"size-4 animate-spin\" />\n Saving...\n </>\n ) : isLoadingSdk ? (\n <>\n <Loader2 className=\"size-4 animate-spin\" />\n Loading…\n </>\n ) : (\n 'Add Card'\n )}\n </button>\n )}\n </div>\n </footer>\n )}\n </div>\n </div>,\n document.body\n );\n};\n"],"mappings":"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"}