@growth-operations/ui-shared 1.5.6 → 1.5.8
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/package.json +1 -1
- package/src/billing/BillingTab.jsx +46 -43
package/package.json
CHANGED
|
@@ -140,9 +140,10 @@ function StatusPanel({ entitlement }) {
|
|
|
140
140
|
);
|
|
141
141
|
}
|
|
142
142
|
|
|
143
|
-
// Pre-creates a Stripe
|
|
144
|
-
// { portalUrl, loading, error }.
|
|
145
|
-
// the
|
|
143
|
+
// Pre-creates a Stripe Customer Portal session on mount (in-iframe POST) and
|
|
144
|
+
// returns { portalUrl, loading, error, notReady }. Used by the TRIAL arm only —
|
|
145
|
+
// the credits arm builds a direct /v1/billing/portal/start link instead (no
|
|
146
|
+
// in-iframe fetch, avoiding the billing service's cold-start timeout).
|
|
146
147
|
function usePortalSession({ context, state, appKey }) {
|
|
147
148
|
const [loading, setLoading] = useState(true);
|
|
148
149
|
const [error, setError] = useState(null);
|
|
@@ -169,8 +170,8 @@ function usePortalSession({ context, state, appKey }) {
|
|
|
169
170
|
resetOnUnmount();
|
|
170
171
|
return;
|
|
171
172
|
}
|
|
172
|
-
// No Stripe customer yet → don't even try to open a portal.
|
|
173
|
-
// that hasn't been provisioned (no entitlement, or status null/
|
|
173
|
+
// No Stripe customer yet → don't even try to open a portal. A trial
|
|
174
|
+
// install that hasn't been provisioned (no entitlement, or status null/
|
|
174
175
|
// not_installed) has nothing to manage; show the calm "not ready" state.
|
|
175
176
|
const status = state?.entitlement?.status;
|
|
176
177
|
if (!state.entitlement || status == null || status === "not_installed") {
|
|
@@ -286,16 +287,28 @@ function TrialSubscriptionBilling({ context, state, appKey }) {
|
|
|
286
287
|
}
|
|
287
288
|
|
|
288
289
|
function CreditsBilling({ context, state, appKey }) {
|
|
289
|
-
// The Stripe portal session lets an EXISTING paid customer manage/switch/cancel
|
|
290
|
-
// their plan, with proration handled by Stripe.
|
|
291
|
-
const { portalUrl, loading, error, notReady } = usePortalSession({
|
|
292
|
-
context,
|
|
293
|
-
state,
|
|
294
|
-
appKey,
|
|
295
|
-
});
|
|
296
|
-
|
|
297
290
|
const onPaidPlan = !!state?.entitlement?.plan;
|
|
298
291
|
|
|
292
|
+
// Direct external link to the billing service's GET /v1/billing/portal/start,
|
|
293
|
+
// which resolves the customer + creates the Customer Portal session server-side
|
|
294
|
+
// and 303s to Stripe — IN THE OPENED TAB. No in-iframe portal-session fetch
|
|
295
|
+
// (that round trip hit the billing service's cold-start/CPU stall and surfaced
|
|
296
|
+
// as the iframe's 15s "Gateway took too long" error). Same pattern as
|
|
297
|
+
// PlanGrid's /checkout/start "Choose" button. null (button disabled) until
|
|
298
|
+
// billing_base_url is known.
|
|
299
|
+
const base = state?.billing_base_url ?? null;
|
|
300
|
+
const portalId = context?.portal?.id;
|
|
301
|
+
const returnUrl = state?.app_id
|
|
302
|
+
? `https://app.hubspot.com/app/${portalId}/${state.app_id}/billing`
|
|
303
|
+
: "https://app.hubspot.com/";
|
|
304
|
+
const portalStartUrl =
|
|
305
|
+
base && appKey && portalId
|
|
306
|
+
? `${base}/v1/billing/portal/start` +
|
|
307
|
+
`?app_key=${encodeURIComponent(appKey)}` +
|
|
308
|
+
`&portal_id=${encodeURIComponent(String(portalId))}` +
|
|
309
|
+
`&return_url=${encodeURIComponent(returnUrl)}`
|
|
310
|
+
: null;
|
|
311
|
+
|
|
299
312
|
// PAID: do NOT show the plan picker. The picker's "Choose" starts a NEW Stripe
|
|
300
313
|
// Checkout subscription — clicking another tier would create a SECOND
|
|
301
314
|
// subscription (double-bill), not switch in place. Plan changes (upgrade/
|
|
@@ -309,21 +322,15 @@ function CreditsBilling({ context, state, appKey }) {
|
|
|
309
322
|
entitlement={state?.entitlement}
|
|
310
323
|
creditMeter={state?.credit_meter}
|
|
311
324
|
/>
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
disabled={!portalUrl}
|
|
322
|
-
variant="primary"
|
|
323
|
-
>
|
|
324
|
-
{portalUrl ? "Manage subscription" : "Preparing billing…"}
|
|
325
|
-
</LoadingButton>
|
|
326
|
-
))}
|
|
325
|
+
<LoadingButton
|
|
326
|
+
href={
|
|
327
|
+
portalStartUrl ? { url: portalStartUrl, external: true } : undefined
|
|
328
|
+
}
|
|
329
|
+
disabled={!portalStartUrl}
|
|
330
|
+
variant="primary"
|
|
331
|
+
>
|
|
332
|
+
{portalStartUrl ? "Manage subscription" : "Preparing billing…"}
|
|
333
|
+
</LoadingButton>
|
|
327
334
|
<Text format={{ fontStyle: "italic" }}>
|
|
328
335
|
Change or cancel your plan in Stripe — billing is managed across all
|
|
329
336
|
Growth Operations apps.
|
|
@@ -343,21 +350,17 @@ function CreditsBilling({ context, state, appKey }) {
|
|
|
343
350
|
creditMeter={state?.credit_meter}
|
|
344
351
|
/>
|
|
345
352
|
<PlanGrid context={context} state={state} appKey={appKey} />
|
|
346
|
-
{!
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
>
|
|
358
|
-
{portalUrl ? "Buy credits in Stripe" : "Preparing billing…"}
|
|
359
|
-
</LoadingButton>
|
|
360
|
-
))}
|
|
353
|
+
{!hasPlans && (
|
|
354
|
+
<LoadingButton
|
|
355
|
+
href={
|
|
356
|
+
portalStartUrl ? { url: portalStartUrl, external: true } : undefined
|
|
357
|
+
}
|
|
358
|
+
disabled={!portalStartUrl}
|
|
359
|
+
variant="primary"
|
|
360
|
+
>
|
|
361
|
+
{portalStartUrl ? "Buy credits in Stripe" : "Preparing billing…"}
|
|
362
|
+
</LoadingButton>
|
|
363
|
+
)}
|
|
361
364
|
{!hasPlans && (
|
|
362
365
|
<Text format={{ fontStyle: "italic" }}>
|
|
363
366
|
Billing is managed in Stripe across all Growth Operations apps.
|