mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-10-24 04:33:17 +00:00
Merge verified provider Plan return onto current main
Compose the previously reviewed Plan-return repair with current main for exact-lock source proof. Change-source: pulse-maintainer
This commit is contained in:
@@ -3489,11 +3489,26 @@ inside its 14-day grace the panel says the subscription has not renewed and
|
||||
when clients lose the plan. Owners and admins get Buy, Manage billing and an
|
||||
immediate refresh ("Apply my purchase now", or "Apply it now" on a paid plan);
|
||||
read-only members are told who can buy. After Stripe returns to
|
||||
`?provider_msp_checkout=complete` the panel keeps refreshing the licence until
|
||||
the purchase lands. It then polls the plan until the restarted control plane
|
||||
serves the new plan, and it never reloads the page. Manage billing returns to
|
||||
`?provider_msp_checkout=complete`, the redirect is a navigation hint, not
|
||||
proof of payment. The panel keeps checking without saying payment was received
|
||||
until the licence refresh reports `active` and the running plan is paid and
|
||||
matches the returned plan version, licence ID and expiry. A changed licence
|
||||
that schedules a restart is confirmed only after the restarted control plane
|
||||
serves the matching plan; an unchanged paid licence already applied by a
|
||||
background refresh can be confirmed without another restart. If no matching
|
||||
paid plan appears, the panel gives conditional follow-up instead of claiming
|
||||
payment or activation. Manual Apply uses the same running-plan confirmation
|
||||
before updating the view. The panel never reloads the page. Manage billing returns to
|
||||
`?provider_msp_checkout=billing`, and the panel checks the licence twice so a
|
||||
plan changed in the Stripe billing portal applies without a click. Regression
|
||||
plan changed in the Stripe billing portal applies without a click. It also
|
||||
accepts a matching paid plan already applied by the background refresher
|
||||
without requiring a second restart; an unchanged visit stays silent. A stale
|
||||
panel is replaced only after the refreshed running plan is confirmed, never
|
||||
because the billing return URL says a change was made. When the return check
|
||||
finishes before the initial plan load, the panel may show that confirmed paid
|
||||
plan silently but must keep its second check for a later webhook. A cancelled
|
||||
or unknown checkout return value must not assert whether a charge occurred.
|
||||
Regression
|
||||
coverage:
|
||||
`TestLoadConfig_ProviderHostedMSPPrefersRenewedLicense` in
|
||||
`internal/cloudcp/config_test.go`,
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"source_hash": "33ddecf9ec76e0a22e0facf6971b8f0825600e56007db5e7f1f4e2cbe1dd2621",
|
||||
"source_hash": "d5c69f37d9cf32a2e8ef727af6779bd44148235ed352fcb427967ef8a6a24859",
|
||||
"build_inputs": [
|
||||
"package.json",
|
||||
"tsconfig.json",
|
||||
|
||||
+32
-12
@@ -2829,16 +2829,22 @@
|
||||
var planEpoch = 0;
|
||||
var applying = null;
|
||||
function isApplied(plan, target) {
|
||||
return !!target.plan_version && plan.plan_version === target.plan_version && (!target.license_id || plan.license_id === target.license_id) && (!target.expires_at || plan.expires_at === target.expires_at);
|
||||
return !plan.evaluation && !!target.plan_version && plan.plan_version === target.plan_version && (!target.license_id || plan.license_id === target.license_id) && (!target.expires_at || plan.expires_at === target.expires_at);
|
||||
}
|
||||
function finishApplying(plan) {
|
||||
function sameRunningPlan(left, right) {
|
||||
return left.evaluation === right.evaluation && left.plan_version === right.plan_version && left.license_id === right.license_id && left.expires_at === right.expires_at;
|
||||
}
|
||||
function acceptRunningPlan(plan, announce) {
|
||||
applying = null;
|
||||
planEpoch += 1;
|
||||
view.plan = plan;
|
||||
view.error = "";
|
||||
view.notice = "Your " + providerPlanName(plan.plan_version) + " plan is active.";
|
||||
if (announce) view.notice = "Your " + providerPlanName(plan.plan_version) + " plan is active.";
|
||||
render();
|
||||
}
|
||||
function finishApplying(plan) {
|
||||
acceptRunningPlan(plan, true);
|
||||
}
|
||||
function account() {
|
||||
var bootstrap = deps.store.getBootstrap();
|
||||
if (bootstrap.provider_hosted_mode !== true || !bootstrap.authenticated) return null;
|
||||
@@ -2909,7 +2915,7 @@
|
||||
waitForAppliedPlan(target, 0);
|
||||
}, RESTART_SETTLE_MS);
|
||||
}
|
||||
async function refresh(manual) {
|
||||
async function refresh(manual, confirmCurrent = false, billingReturn = false) {
|
||||
var current = account();
|
||||
if (!current) return false;
|
||||
view.busy = "refresh";
|
||||
@@ -2920,8 +2926,22 @@
|
||||
applyRestart(result);
|
||||
return true;
|
||||
}
|
||||
if (confirmCurrent && result.status === "active" && result.plan_version) {
|
||||
var plan = await deps.api.fetchPlan(current.id);
|
||||
if (isApplied(plan, result)) {
|
||||
if (billingReturn) {
|
||||
if (!view.plan) {
|
||||
acceptRunningPlan(plan, false);
|
||||
return false;
|
||||
}
|
||||
if (sameRunningPlan(view.plan, plan)) return false;
|
||||
}
|
||||
finishApplying(plan);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (manual) {
|
||||
deps.showToast(result.status === "active" ? "Your plan is already up to date." : "No payment has reached this platform yet. It can take a minute after checkout.");
|
||||
deps.showToast(result.status === "active" ? "The updated plan could not be confirmed yet. Try again shortly." : "No payment has reached this platform yet. It can take a minute after checkout.");
|
||||
}
|
||||
return false;
|
||||
} catch (error) {
|
||||
@@ -2933,10 +2953,10 @@
|
||||
}
|
||||
}
|
||||
function pollAfterCheckout(attempt) {
|
||||
void refresh(false).then(function(applied) {
|
||||
void refresh(false, true).then(function(applied) {
|
||||
if (applied) return;
|
||||
if (attempt + 1 >= APPLY_POLL_ATTEMPTS) {
|
||||
view.notice = "Payment received. If your plan has not changed in a few minutes, use Apply my purchase now.";
|
||||
view.notice = "A paid plan could not be confirmed yet. If you completed checkout, wait a minute and use Apply my purchase now.";
|
||||
render();
|
||||
return;
|
||||
}
|
||||
@@ -2946,7 +2966,7 @@
|
||||
});
|
||||
}
|
||||
function checkAfterBillingReturn(attempt) {
|
||||
void refresh(false).then(function(applied) {
|
||||
void refresh(false, true, true).then(function(applied) {
|
||||
if (applied || attempt + 1 >= BILLING_RETURN_ATTEMPTS) return;
|
||||
later(function() {
|
||||
checkAfterBillingReturn(attempt + 1);
|
||||
@@ -2999,7 +3019,7 @@
|
||||
});
|
||||
return;
|
||||
case "refresh":
|
||||
void refresh(true);
|
||||
void refresh(true, true);
|
||||
return;
|
||||
}
|
||||
});
|
||||
@@ -3019,12 +3039,12 @@
|
||||
}
|
||||
deps.store.setActiveShellSection("billing");
|
||||
if (returned === "complete") {
|
||||
view.notice = "Payment received. Applying your plan\u2026";
|
||||
view.notice = "Checking for your paid plan\u2026";
|
||||
pollAfterCheckout(0);
|
||||
} else if (returned === "billing") {
|
||||
checkAfterBillingReturn(0);
|
||||
} else {
|
||||
view.notice = "Checkout was cancelled. Nothing was charged.";
|
||||
} else if (returned === "cancelled") {
|
||||
view.notice = "Checkout was not completed here. Check your current plan below.";
|
||||
}
|
||||
}
|
||||
return {
|
||||
|
||||
@@ -197,6 +197,24 @@ describe('installProviderPlan', () => {
|
||||
expect(navigate).toHaveBeenCalledWith('https://billing.stripe.com/p/session/test');
|
||||
});
|
||||
|
||||
it('updates the manual apply view when a paid licence is already running', async () => {
|
||||
const paid = evaluationPlan({ plan_version: 'msp_solo', evaluation: false, license_id: 'lic_msp_paid', workspace_limit: 3 });
|
||||
api.fetchPlan = vi.fn()
|
||||
.mockResolvedValueOnce(evaluationPlan())
|
||||
.mockResolvedValueOnce(paid);
|
||||
api.refreshLicense = vi.fn().mockResolvedValue({
|
||||
status: 'active', changed: false, restart_scheduled: false,
|
||||
plan_version: paid.plan_version, license_id: paid.license_id, expires_at: paid.expires_at,
|
||||
});
|
||||
const { controller } = install(providerBootstrap());
|
||||
await controller.load();
|
||||
click('[data-provider-plan-action="refresh"]');
|
||||
await flush();
|
||||
expect(controller.view().notice).toBe('Your Solo plan is active.');
|
||||
expect(controller.view().plan).toEqual(paid);
|
||||
expect(toast).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('applies a purchase after checkout returns and shows the new plan once the platform restarts', async () => {
|
||||
api.refreshLicense = vi.fn()
|
||||
.mockResolvedValueOnce({ status: 'no_paid_subscription' })
|
||||
@@ -204,7 +222,7 @@ describe('installProviderPlan', () => {
|
||||
const { store, controller } = install(providerBootstrap(), '?provider_msp_checkout=complete');
|
||||
await controller.load();
|
||||
expect(store.getShellState().activeSection).toBe('billing');
|
||||
expect(controller.view().notice).toContain('Payment received');
|
||||
expect(controller.view().notice).toBe('Checking for your paid plan…');
|
||||
await flush();
|
||||
expect(api.refreshLicense).toHaveBeenCalledTimes(1);
|
||||
timers.shift()!();
|
||||
@@ -225,6 +243,61 @@ describe('installProviderPlan', () => {
|
||||
expect(document.getElementById(PROVIDER_PLAN_ROOT_ID)!.innerHTML).toContain('Manage billing');
|
||||
});
|
||||
|
||||
it('does not claim payment from the checkout return when no paid subscription appears', async () => {
|
||||
const { controller } = install(providerBootstrap(), '?provider_msp_checkout=complete');
|
||||
await controller.load();
|
||||
await flush();
|
||||
for (let attempt = 1; attempt < 12; attempt += 1) {
|
||||
expect(timers).toHaveLength(1);
|
||||
timers.shift()!();
|
||||
await flush();
|
||||
}
|
||||
expect(api.refreshLicense).toHaveBeenCalledTimes(12);
|
||||
expect(timers).toHaveLength(0);
|
||||
expect(controller.view().notice).toContain('could not be confirmed yet');
|
||||
expect(controller.view().notice).not.toContain('Payment received');
|
||||
expect(controller.view().plan?.evaluation).toBe(true);
|
||||
});
|
||||
|
||||
it('confirms an already-applied paid licence without requiring another restart', async () => {
|
||||
const paid = evaluationPlan({
|
||||
plan_version: 'msp_solo', evaluation: false, license_id: 'lic_msp_paid', workspace_limit: 3,
|
||||
});
|
||||
api.fetchPlan = vi.fn().mockResolvedValue(paid);
|
||||
api.refreshLicense = vi.fn().mockResolvedValue({
|
||||
status: 'active', changed: false, restart_scheduled: false,
|
||||
plan_version: paid.plan_version, license_id: paid.license_id, expires_at: paid.expires_at,
|
||||
});
|
||||
const { controller } = install(providerBootstrap(), '?provider_msp_checkout=complete');
|
||||
await controller.load();
|
||||
await flush();
|
||||
expect(controller.view().notice).toBe('Your Solo plan is active.');
|
||||
expect(controller.view().plan).toEqual(paid);
|
||||
expect(timers).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('waits for the matching running plan even when refresh reports an active licence', async () => {
|
||||
const paid = evaluationPlan({
|
||||
plan_version: 'msp_solo', evaluation: false, license_id: 'lic_msp_paid', workspace_limit: 3,
|
||||
});
|
||||
api.fetchPlan = vi.fn()
|
||||
.mockResolvedValueOnce(evaluationPlan())
|
||||
.mockResolvedValueOnce(evaluationPlan())
|
||||
.mockResolvedValueOnce(paid);
|
||||
api.refreshLicense = vi.fn().mockResolvedValue({
|
||||
status: 'active', changed: false, restart_scheduled: false,
|
||||
plan_version: paid.plan_version, license_id: paid.license_id, expires_at: paid.expires_at,
|
||||
});
|
||||
const { controller } = install(providerBootstrap(), '?provider_msp_checkout=complete');
|
||||
await controller.load();
|
||||
await flush();
|
||||
expect(controller.view().notice).toBe('Checking for your paid plan…');
|
||||
expect(timers).toHaveLength(1);
|
||||
timers.shift()!();
|
||||
await flush();
|
||||
expect(controller.view().notice).toBe('Your Solo plan is active.');
|
||||
});
|
||||
|
||||
it('does not announce the old plan when refresh beats the initial plan request', async () => {
|
||||
let resolveInitial!: (plan: ProviderPlanState) => void;
|
||||
api.fetchPlan = vi.fn()
|
||||
@@ -292,6 +365,114 @@ describe('installProviderPlan', () => {
|
||||
expect(toast).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('confirms a paid plan already applied by the background refresh on a billing return', async () => {
|
||||
const oldPlan = evaluationPlan({
|
||||
plan_version: 'msp_solo', evaluation: false, license_id: 'lic_old',
|
||||
workspace_limit: 3, expires_at: '2026-10-10T00:00:00Z',
|
||||
});
|
||||
const newPlan = evaluationPlan({
|
||||
plan_version: 'msp_starter', evaluation: false, license_id: 'lic_new',
|
||||
workspace_limit: 5, expires_at: '2026-11-10T00:00:00Z',
|
||||
});
|
||||
let resolveRefresh!: (result: Awaited<ReturnType<ProviderPlanAPI['refreshLicense']>>) => void;
|
||||
api.refreshLicense = vi.fn().mockImplementation(() => new Promise((resolve) => { resolveRefresh = resolve; }));
|
||||
api.fetchPlan = vi.fn().mockResolvedValueOnce(oldPlan).mockResolvedValueOnce(newPlan);
|
||||
const { controller } = install(providerBootstrap(), '?provider_msp_checkout=billing');
|
||||
await controller.load();
|
||||
expect(controller.view().plan).toEqual(oldPlan);
|
||||
expect(controller.view().notice).toBe('');
|
||||
|
||||
resolveRefresh({
|
||||
status: 'active', changed: false, restart_scheduled: false,
|
||||
plan_version: newPlan.plan_version, license_id: newPlan.license_id, expires_at: newPlan.expires_at,
|
||||
});
|
||||
await flush();
|
||||
expect(controller.view().plan).toEqual(newPlan);
|
||||
expect(controller.view().notice).toBe('Your Starter plan is active.');
|
||||
expect(timers).toHaveLength(0);
|
||||
expect(toast).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('does not announce an unchanged paid plan from a Manage billing visit', async () => {
|
||||
const paid = evaluationPlan({
|
||||
plan_version: 'msp_solo', evaluation: false, license_id: 'lic_paid', workspace_limit: 3,
|
||||
});
|
||||
api.fetchPlan = vi.fn().mockResolvedValue(paid);
|
||||
api.refreshLicense = vi.fn().mockResolvedValue({
|
||||
status: 'active', changed: false, restart_scheduled: false,
|
||||
plan_version: paid.plan_version, license_id: paid.license_id, expires_at: paid.expires_at,
|
||||
});
|
||||
const { controller } = install(providerBootstrap(), '?provider_msp_checkout=billing');
|
||||
await controller.load();
|
||||
await flush();
|
||||
expect(controller.view().notice).toBe('');
|
||||
expect(timers).toHaveLength(1);
|
||||
timers.shift()!();
|
||||
await flush();
|
||||
expect(controller.view().notice).toBe('');
|
||||
expect(controller.view().plan).toEqual(paid);
|
||||
expect(timers).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('confirms a renewed paid period but not an unmatched refresh on a billing return', async () => {
|
||||
const oldPlan = evaluationPlan({
|
||||
plan_version: 'msp_solo', evaluation: false, license_id: 'lic_paid',
|
||||
workspace_limit: 3, expires_at: '2026-10-10T00:00:00Z',
|
||||
});
|
||||
const renewed = { ...oldPlan, expires_at: '2026-11-10T00:00:00Z' };
|
||||
let served = oldPlan;
|
||||
api.fetchPlan = vi.fn().mockImplementation(() => Promise.resolve(served));
|
||||
let resolveRefresh!: (result: Awaited<ReturnType<ProviderPlanAPI['refreshLicense']>>) => void;
|
||||
api.refreshLicense = vi.fn().mockImplementationOnce(() => new Promise((resolve) => { resolveRefresh = resolve; }))
|
||||
.mockResolvedValue({
|
||||
status: 'active', changed: false, restart_scheduled: false,
|
||||
plan_version: renewed.plan_version, license_id: renewed.license_id, expires_at: renewed.expires_at,
|
||||
});
|
||||
const { controller } = install(providerBootstrap(), '?provider_msp_checkout=billing');
|
||||
await controller.load();
|
||||
resolveRefresh({
|
||||
status: 'active', changed: false, restart_scheduled: false,
|
||||
plan_version: renewed.plan_version, license_id: renewed.license_id, expires_at: renewed.expires_at,
|
||||
});
|
||||
await flush();
|
||||
expect(controller.view().plan).toEqual(oldPlan);
|
||||
expect(controller.view().notice).toBe('');
|
||||
expect(timers).toHaveLength(1);
|
||||
served = renewed;
|
||||
timers.shift()!();
|
||||
await flush();
|
||||
expect(controller.view().plan).toEqual(renewed);
|
||||
expect(controller.view().notice).toBe('Your Solo plan is active.');
|
||||
expect(timers).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('keeps the second billing check when refresh finishes before the first plan load', async () => {
|
||||
const oldPlan = evaluationPlan({
|
||||
plan_version: 'msp_solo', evaluation: false, license_id: 'lic_old', workspace_limit: 3,
|
||||
});
|
||||
const newPlan = evaluationPlan({
|
||||
plan_version: 'msp_starter', evaluation: false, license_id: 'lic_new', workspace_limit: 5,
|
||||
});
|
||||
let served = oldPlan;
|
||||
api.fetchPlan = vi.fn().mockImplementation(() => Promise.resolve(served));
|
||||
api.refreshLicense = vi.fn().mockImplementation(() => Promise.resolve({
|
||||
status: 'active', changed: false, restart_scheduled: false,
|
||||
plan_version: served.plan_version, license_id: served.license_id, expires_at: served.expires_at,
|
||||
}));
|
||||
const { controller } = install(providerBootstrap(), '?provider_msp_checkout=billing');
|
||||
await flush();
|
||||
expect(controller.view().plan).toEqual(oldPlan);
|
||||
expect(controller.view().notice).toBe('');
|
||||
expect(timers).toHaveLength(1);
|
||||
|
||||
served = newPlan;
|
||||
timers.shift()!();
|
||||
await flush();
|
||||
expect(controller.view().plan).toEqual(newPlan);
|
||||
expect(controller.view().notice).toBe('Your Starter plan is active.');
|
||||
expect(timers).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('checks a Manage billing return only twice when nothing changed', async () => {
|
||||
api.refreshLicense = vi.fn().mockResolvedValue({ status: 'active', changed: false });
|
||||
const { controller } = install(providerBootstrap(), '?provider_msp_checkout=billing');
|
||||
@@ -304,9 +485,15 @@ describe('installProviderPlan', () => {
|
||||
expect(controller.view().notice).toBe('');
|
||||
});
|
||||
|
||||
it('says nothing was charged when checkout is cancelled', () => {
|
||||
it('does not infer a charge outcome from a cancelled checkout return', () => {
|
||||
const { controller } = install(providerBootstrap(), '?provider_msp_checkout=cancelled');
|
||||
expect(controller.view().notice).toBe('Checkout was cancelled. Nothing was charged.');
|
||||
expect(controller.view().notice).toBe('Checkout was not completed here. Check your current plan below.');
|
||||
expect(api.refreshLicense).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('ignores an unrecognised checkout return value', () => {
|
||||
const { controller } = install(providerBootstrap(), '?provider_msp_checkout=unknown');
|
||||
expect(controller.view().notice).toBe('');
|
||||
expect(api.refreshLicense).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
|
||||
@@ -254,20 +254,29 @@ export function installProviderPlan(deps: ProviderPlanDeps): ProviderPlanControl
|
||||
var applying: ProviderPlanRefreshResult | null = null;
|
||||
|
||||
function isApplied(plan: ProviderPlanState, target: ProviderPlanRefreshResult): boolean {
|
||||
return !!target.plan_version && plan.plan_version === target.plan_version &&
|
||||
return !plan.evaluation && !!target.plan_version && plan.plan_version === target.plan_version &&
|
||||
(!target.license_id || plan.license_id === target.license_id) &&
|
||||
(!target.expires_at || plan.expires_at === target.expires_at);
|
||||
}
|
||||
|
||||
function finishApplying(plan: ProviderPlanState) {
|
||||
function sameRunningPlan(left: ProviderPlanState, right: ProviderPlanState): boolean {
|
||||
return left.evaluation === right.evaluation && left.plan_version === right.plan_version &&
|
||||
left.license_id === right.license_id && left.expires_at === right.expires_at;
|
||||
}
|
||||
|
||||
function acceptRunningPlan(plan: ProviderPlanState, announce: boolean) {
|
||||
applying = null;
|
||||
planEpoch += 1; // Ignore plan requests started before the restart completed.
|
||||
planEpoch += 1; // Ignore requests started before this confirmed plan was served.
|
||||
view.plan = plan;
|
||||
view.error = '';
|
||||
view.notice = 'Your ' + providerPlanName(plan.plan_version) + ' plan is active.';
|
||||
if (announce) view.notice = 'Your ' + providerPlanName(plan.plan_version) + ' plan is active.';
|
||||
render();
|
||||
}
|
||||
|
||||
function finishApplying(plan: ProviderPlanState) {
|
||||
acceptRunningPlan(plan, true);
|
||||
}
|
||||
|
||||
function account(): PortalAccountSummary | null {
|
||||
var bootstrap = deps.store.getBootstrap();
|
||||
if (bootstrap.provider_hosted_mode !== true || !bootstrap.authenticated) return null;
|
||||
@@ -339,7 +348,7 @@ export function installProviderPlan(deps: ProviderPlanDeps): ProviderPlanControl
|
||||
later(function() { waitForAppliedPlan(target, 0); }, RESTART_SETTLE_MS);
|
||||
}
|
||||
|
||||
async function refresh(manual: boolean): Promise<boolean> {
|
||||
async function refresh(manual: boolean, confirmCurrent = false, billingReturn = false): Promise<boolean> {
|
||||
var current = account();
|
||||
if (!current) return false;
|
||||
view.busy = 'refresh';
|
||||
@@ -350,8 +359,32 @@ export function installProviderPlan(deps: ProviderPlanDeps): ProviderPlanControl
|
||||
applyRestart(result);
|
||||
return true;
|
||||
}
|
||||
// The background refresher may have restarted the platform before the
|
||||
// browser returns from checkout. Confirm the running paid plan rather
|
||||
// than waiting for another restart that will never be scheduled.
|
||||
if (confirmCurrent && result.status === 'active' && result.plan_version) {
|
||||
var plan = await deps.api.fetchPlan(current.id);
|
||||
if (isApplied(plan, result)) {
|
||||
// A visit to Manage billing is not itself a change. If the
|
||||
// background refresher applied a new licence before this check,
|
||||
// replace the older panel; otherwise keep the visit silent.
|
||||
if (billingReturn) {
|
||||
if (!view.plan) {
|
||||
acceptRunningPlan(plan, false);
|
||||
// Without an earlier view there is no evidence of a change;
|
||||
// keep the second check for a webhook trailing the redirect.
|
||||
return false;
|
||||
}
|
||||
if (sameRunningPlan(view.plan, plan)) return false;
|
||||
}
|
||||
finishApplying(plan);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (manual) {
|
||||
deps.showToast(result.status === 'active' ? 'Your plan is already up to date.' : 'No payment has reached this platform yet. It can take a minute after checkout.');
|
||||
deps.showToast(result.status === 'active'
|
||||
? 'The updated plan could not be confirmed yet. Try again shortly.'
|
||||
: 'No payment has reached this platform yet. It can take a minute after checkout.');
|
||||
}
|
||||
return false;
|
||||
} catch (error) {
|
||||
@@ -364,10 +397,10 @@ export function installProviderPlan(deps: ProviderPlanDeps): ProviderPlanControl
|
||||
}
|
||||
|
||||
function pollAfterCheckout(attempt: number) {
|
||||
void refresh(false).then(function(applied) {
|
||||
void refresh(false, true).then(function(applied) {
|
||||
if (applied) return;
|
||||
if (attempt + 1 >= APPLY_POLL_ATTEMPTS) {
|
||||
view.notice = 'Payment received. If your plan has not changed in a few minutes, use Apply my purchase now.';
|
||||
view.notice = 'A paid plan could not be confirmed yet. If you completed checkout, wait a minute and use Apply my purchase now.';
|
||||
render();
|
||||
return;
|
||||
}
|
||||
@@ -376,7 +409,7 @@ export function installProviderPlan(deps: ProviderPlanDeps): ProviderPlanControl
|
||||
}
|
||||
|
||||
function checkAfterBillingReturn(attempt: number) {
|
||||
void refresh(false).then(function(applied) {
|
||||
void refresh(false, true, true).then(function(applied) {
|
||||
if (applied || attempt + 1 >= BILLING_RETURN_ATTEMPTS) return;
|
||||
later(function() { checkAfterBillingReturn(attempt + 1); }, APPLY_POLL_MS);
|
||||
});
|
||||
@@ -428,7 +461,7 @@ export function installProviderPlan(deps: ProviderPlanDeps): ProviderPlanControl
|
||||
});
|
||||
return;
|
||||
case 'refresh':
|
||||
void refresh(true);
|
||||
void refresh(true, true);
|
||||
return;
|
||||
}
|
||||
});
|
||||
@@ -451,12 +484,12 @@ export function installProviderPlan(deps: ProviderPlanDeps): ProviderPlanControl
|
||||
}
|
||||
deps.store.setActiveShellSection('billing');
|
||||
if (returned === 'complete') {
|
||||
view.notice = 'Payment received. Applying your plan…';
|
||||
view.notice = 'Checking for your paid plan…';
|
||||
pollAfterCheckout(0);
|
||||
} else if (returned === 'billing') {
|
||||
checkAfterBillingReturn(0);
|
||||
} else {
|
||||
view.notice = 'Checkout was cancelled. Nothing was charged.';
|
||||
} else if (returned === 'cancelled') {
|
||||
view.notice = 'Checkout was not completed here. Check your current plan below.';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user