Skip to content

Commit 0c9c5a6

Browse files
committed
Return Hub license via URL fragment and stash billing params to avoid long-token URL breakage
1 parent f0dd70a commit 0c9c5a6

3 files changed

Lines changed: 49 additions & 8 deletions

File tree

assets/js/hubsubscription.js

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,15 @@ const GENERATE_PAY_LINK_URL = LEGACY_STORE_URL + '/hub/generate-pay-link';
66
const MANAGE_SUBSCRIPTION_URL = LEGACY_STORE_URL + '/hub/manage-subscription';
77
const UPDATE_PAYMENT_METHOD_URL = LEGACY_STORE_URL + '/hub/update-payment-method';
88
const REFRESH_LICENSE_URL = API_BASE_URL + '/licenses/hub/refresh';
9+
const FRAGMENT_PARAMS_STORAGE_KEY = 'hubSubscriptionParams';
910

1011
class HubSubscription {
1112

1213
constructor(form, subscriptionData, searchParams) {
1314
this._form = form;
1415
this._subscriptionData = subscriptionData;
15-
let fragmentParams = new URLSearchParams(location.hash.substring(1));
16-
this._subscriptionData.oldLicense = fragmentParams.get('oldLicense');
16+
let restoredParams = this.resolveParams();
17+
this._subscriptionData.oldLicense = restoredParams.oldLicense;
1718
if (this._subscriptionData.oldLicense) {
1819
try {
1920
let base64 = this._subscriptionData.oldLicense.split('.')[1].replace(/-/g, '+').replace(/_/g, '/');
@@ -24,9 +25,10 @@ class HubSubscription {
2425
}
2526
}
2627
this._subscriptionData.hubId = this._subscriptionData.hubId ?? searchParams.get('hub_id');
27-
let returnUrl = fragmentParams.get('returnUrl') ?? searchParams.get('return_url');
28+
let returnUrl = restoredParams.returnUrl ?? searchParams.get('return_url');
2829
if (returnUrl) {
2930
this._subscriptionData.returnUrl = returnUrl;
31+
this._subscriptionData.returnUrlFromFragment = restoredParams.returnUrlFromFragment;
3032
}
3133
this._subscriptionData.session = searchParams.get('session');
3234
if (this._subscriptionData.hubId && this._subscriptionData.hubId.length > 0 && this._subscriptionData.returnUrl && this._subscriptionData.returnUrl.length > 0) {
@@ -46,6 +48,39 @@ class HubSubscription {
4648
});
4749
}
4850

51+
resolveParams() {
52+
let fragmentParams = new URLSearchParams(location.hash.substring(1));
53+
let oldLicense = fragmentParams.get('oldLicense');
54+
let returnUrl = fragmentParams.get('returnUrl');
55+
if (oldLicense || returnUrl) {
56+
let params = { oldLicense: oldLicense, returnUrl: returnUrl, returnUrlFromFragment: returnUrl != null };
57+
try {
58+
sessionStorage.setItem(FRAGMENT_PARAMS_STORAGE_KEY, JSON.stringify(params));
59+
} catch (e) {
60+
console.error('Failed to persist hub billing parameters:', e);
61+
}
62+
// The oldLicense token is long enough that leaving it in the page URL breaks Paddle checkout, so we drop the fragment regardless of whether the stash succeeded.
63+
history.replaceState(null, '', location.pathname + location.search);
64+
return params;
65+
}
66+
let searchParams = new URLSearchParams(location.search);
67+
let emptyParams = { oldLicense: null, returnUrl: null, returnUrlFromFragment: false };
68+
try {
69+
if (searchParams.has('hub_id') || searchParams.has('return_url')) {
70+
// A fresh flow carries its own query params, so any stashed fragment from an earlier, abandoned flow is stale and must not override it.
71+
sessionStorage.removeItem(FRAGMENT_PARAMS_STORAGE_KEY);
72+
return emptyParams;
73+
}
74+
let stored = sessionStorage.getItem(FRAGMENT_PARAMS_STORAGE_KEY);
75+
if (stored) {
76+
return JSON.parse(stored);
77+
}
78+
} catch (e) {
79+
console.error('Failed to restore hub billing parameters:', e);
80+
}
81+
return emptyParams;
82+
}
83+
4984
loadSubscription() {
5085
this.loadCustomBilling(() => {
5186
this.loadPrice(() => {
@@ -535,7 +570,13 @@ class HubSubscription {
535570
}
536571

537572
transferTokenToHub() {
538-
window.open(this._subscriptionData.returnUrl + '?token=' + this._subscriptionData.token, '_self');
573+
try {
574+
sessionStorage.removeItem(FRAGMENT_PARAMS_STORAGE_KEY);
575+
} catch (e) {
576+
console.error('Failed to clear hub billing parameters:', e);
577+
}
578+
let separator = this._subscriptionData.returnUrlFromFragment ? '#' : '?';
579+
location.href = this._subscriptionData.returnUrl + separator + 'token=' + this._subscriptionData.token;
539580
}
540581

541582
}

layouts/hub-billing/single.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
{{ end }}
44
{{ define "main" }}
55
<div class="container pt-12 pb-24">
6-
<form x-data="{subscriptionData: {state: 'MISSING_PARAMS', captcha: null, hubId: null, returnUrl: null, session: null, oldLicense: null, customBilling: null, billingInterval: 'yearly', savingsPercent: null, errorMessage: '', inProgress: false, restartModal: {open: false, nextPayment: null}, changeSeatsModal: {open: false, confirmation: false, immediatePayment: null}, token: null, details: null, quantity: 5, email: '', needsTokenRefresh: false, shouldTransferToHub: false}, acceptTerms: false, hubSubscription: null, captchaState: null}" x-init="hubSubscription = new HubSubscription($refs.form, subscriptionData, new URLSearchParams(location.search))" x-ref="form" @submit.prevent="hubSubscription.createSession(); $refs.captcha.reset()">
6+
<form x-data="{subscriptionData: {state: 'MISSING_PARAMS', captcha: null, hubId: null, returnUrl: null, returnUrlFromFragment: false, session: null, oldLicense: null, customBilling: null, billingInterval: 'yearly', savingsPercent: null, errorMessage: '', inProgress: false, restartModal: {open: false, nextPayment: null}, changeSeatsModal: {open: false, confirmation: false, immediatePayment: null}, token: null, details: null, quantity: 5, email: '', needsTokenRefresh: false, shouldTransferToHub: false}, acceptTerms: false, hubSubscription: null, captchaState: null}" x-init="hubSubscription = new HubSubscription($refs.form, subscriptionData, new URLSearchParams(location.search))" x-ref="form" @submit.prevent="hubSubscription.createSession(); $refs.captcha.reset()">
77
<template x-if="subscriptionData.state == 'MISSING_PARAMS'">
88
<div class="text-center max-w-xl mx-auto">
99
<h3 class="font-headline text-xl md:text-2xl leading-relaxed mb-4">
@@ -354,7 +354,7 @@ <h3 class="font-h3 mb-4 md:mb-0">
354354
{{ i18n "hub_billing_checkout_community_cta" . }}
355355
</p>
356356
</div>
357-
<a :href="`{{ "hub/register" | relLangURL }}#oldLicense=${encodeURIComponent(subscriptionData.oldLicense)}&returnUrl=${encodeURIComponent(subscriptionData.returnUrl)}`" role="button" class="btn btn-primary w-full">
357+
<a :href="`{{ "hub/register" | relLangURL }}#oldLicense=${encodeURIComponent(subscriptionData.oldLicense)}&returnUrl=${encodeURIComponent(subscriptionData.returnUrl)}&returnUrlFromFragment=${subscriptionData.returnUrlFromFragment}`" role="button" class="btn btn-primary w-full">
358358
<i class="fa-solid fa-user-plus"></i>
359359
{{ i18n "hub_billing_checkout_community_action" . }}
360360
</a>

layouts/hub-register/single.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
{{ partial "altcha-css.html" . }}
33
{{ end }}
44
{{ define "main" }}
5-
<section x-data="{steps: ['{{ i18n "hub_ce_registration_step_1_nav_title" }}', '{{ i18n "hub_ce_registration_step_2_confirmation_nav_title" }}', '{{ i18n "hub_ce_registration_step_3_license_nav_title" }}'], feedbackData: {currentStep: 0, success: false, inProgress: false, errorMessage: '', licenseText: null, emailSent: false, emailVerified: false}, submitData: {captcha: null, oldLicense: '', email: '', acceptNewsletter: false}, acceptTerms: false, hubCE: null, captchaState: null, returnUrl: null}" x-init="const params = new URLSearchParams(location.hash.substring(1)); returnUrl = params.get('returnUrl'); hubCE = new HubCE($refs.form, feedbackData, submitData, params)" class="container py-12">
5+
<section x-data="{steps: ['{{ i18n "hub_ce_registration_step_1_nav_title" }}', '{{ i18n "hub_ce_registration_step_2_confirmation_nav_title" }}', '{{ i18n "hub_ce_registration_step_3_license_nav_title" }}'], feedbackData: {currentStep: 0, success: false, inProgress: false, errorMessage: '', licenseText: null, emailSent: false, emailVerified: false}, submitData: {captcha: null, oldLicense: '', email: '', acceptNewsletter: false}, acceptTerms: false, hubCE: null, captchaState: null, returnUrl: null, returnUrlFromFragment: false}" x-init="const params = new URLSearchParams(location.hash.substring(1)); returnUrl = params.get('returnUrl'); returnUrlFromFragment = params.get('returnUrlFromFragment') === 'true'; hubCE = new HubCE($refs.form, feedbackData, submitData, params)" class="container py-12">
66
<header class="mb-6">
77
<h1 class="font-h1 mb-8">{{ .Title }}</h1>
88
<p class="lead">{{ i18n "hub_ce_registration_description" }}</p>
@@ -188,7 +188,7 @@ <h2 class="font-h2 mb-6">
188188
<p class="font-p mb-4">{{ i18n "hub_ce_registration_step_3_success" }}</p>
189189
<textarea class="block input-box w-full h-32 md:h-48 mb-8" x-text="feedbackData.licenseText" readonly></textarea>
190190
<div class="mt-auto">
191-
<a :href="returnUrl + '?token=' + encodeURIComponent(feedbackData.licenseText)" class="btn btn-primary w-full md:w-64" data-umami-event="hub-ce-registration-return-to-hub">
191+
<a :href="returnUrl + (returnUrlFromFragment ? '#' : '?') + 'token=' + encodeURIComponent(feedbackData.licenseText)" class="btn btn-primary w-full md:w-64" data-umami-event="hub-ce-registration-return-to-hub">
192192
<i class="fa-solid fa-chevron-right" aria-hidden="true"></i>
193193
Todo: Return to Hub
194194
</a>

0 commit comments

Comments
 (0)