Skip to content

Commit a9686bc

Browse files
committed
refresh hub license token via API with ALTCHA captcha verification
Instead of using the store's token directly, the billing page now refreshes the license token through the /licenses/hub/refresh API endpoint with captcha verification. On normal visits the token is displayed inline; on checkout/modification flows the user is redirected back to Hub. Also extracts captcha widget into a reusable partial and fixes Paddle passthrough JSON serialization.
1 parent c8ee277 commit a9686bc

4 files changed

Lines changed: 47 additions & 25 deletions

File tree

assets/js/hubsubscription.js

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ const CUSTOM_BILLING_URL = LEGACY_STORE_URL + '/hub/custom-billing';
55
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';
8+
const REFRESH_LICENSE_URL = API_BASE_URL + '/licenses/hub/refresh';
89

910
class HubSubscription {
1011

@@ -56,14 +57,15 @@ class HubSubscription {
5657
}
5758

5859
onLoadSubscriptionSucceeded(data) {
59-
this._subscriptionData.token = data.token;
60+
this._subscriptionData.verificationToken = data.token;
6061
this._subscriptionData.details = data.subscription;
6162
if (data.subscription.quantity) {
6263
this._subscriptionData.quantity = data.subscription.quantity;
6364
}
6465
this._subscriptionData.state = 'EXISTING_CUSTOMER';
6566
this._subscriptionData.errorMessage = '';
6667
this._subscriptionData.inProgress = false;
68+
this._subscriptionData.needsTokenRefresh = true;
6769
}
6870

6971
onLoadSubscriptionFailed(status, error) {
@@ -271,7 +273,7 @@ class HubSubscription {
271273
override: payLink,
272274
email: this._subscriptionData.email,
273275
locale: locale,
274-
passthrough: '{"hub_id": ' + this._subscriptionData.hubId + '}',
276+
passthrough: JSON.stringify({ hub_id: this._subscriptionData.hubId }),
275277
successCallback: data => this.getPaddleOrderDetails(data.checkout.id),
276278
closeCallback: () => {
277279
this._subscriptionData.inProgress = false;
@@ -311,7 +313,7 @@ class HubSubscription {
311313

312314
onPostSucceeded(data) {
313315
this._subscriptionData.state = 'EXISTING_CUSTOMER';
314-
this._subscriptionData.token = data.token;
316+
this._subscriptionData.verificationToken = data.token;
315317
this._subscriptionData.details = data.subscription;
316318
this._subscriptionData.session = data.session;
317319
var searchParams = new URLSearchParams(window.location.search)
@@ -320,7 +322,8 @@ class HubSubscription {
320322
history.pushState(null, '', newRelativePathQuery);
321323
this._subscriptionData.errorMessage = '';
322324
this._subscriptionData.inProgress = false;
323-
this.transferTokenToHub();
325+
this._subscriptionData.shouldTransferToHub = true;
326+
this._subscriptionData.needsTokenRefresh = true;
324327
}
325328

326329
onPostFailed(error) {
@@ -477,12 +480,13 @@ class HubSubscription {
477480
}
478481

479482
onPutSucceeded(data, shouldOpenReturnUrl) {
480-
this._subscriptionData.token = data.token;
483+
this._subscriptionData.verificationToken = data.token;
481484
this._subscriptionData.details = data.subscription;
482485
this._subscriptionData.errorMessage = '';
483486
this._subscriptionData.inProgress = false;
484487
if (shouldOpenReturnUrl) {
485-
this.transferTokenToHub();
488+
this._subscriptionData.shouldTransferToHub = true;
489+
this._subscriptionData.needsTokenRefresh = true;
486490
}
487491
}
488492

@@ -494,6 +498,30 @@ class HubSubscription {
494498
this._subscriptionData.inProgress = false;
495499
}
496500

501+
refreshToken() {
502+
this._subscriptionData.inProgress = true;
503+
this._subscriptionData.errorMessage = '';
504+
$.ajax({
505+
url: REFRESH_LICENSE_URL,
506+
type: 'POST',
507+
data: {
508+
token: this._subscriptionData.verificationToken,
509+
captcha: this._subscriptionData.captcha
510+
}
511+
}).done(token => {
512+
this._subscriptionData.token = token;
513+
this._subscriptionData.needsTokenRefresh = false;
514+
this._subscriptionData.errorMessage = '';
515+
this._subscriptionData.inProgress = false;
516+
if (this._subscriptionData.shouldTransferToHub) {
517+
this.transferTokenToHub();
518+
}
519+
}).fail(xhr => {
520+
this._subscriptionData.errorMessage = xhr.responseJSON?.message || 'Refreshing license failed.';
521+
this._subscriptionData.inProgress = false;
522+
});
523+
}
524+
497525
transferTokenToHub() {
498526
window.open(this._subscriptionData.returnUrl + '?token=' + this._subscriptionData.token, '_self');
499527
}

layouts/hub-billing/single.html

Lines changed: 9 additions & 1 deletion
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, 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: ''}, 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, session: 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">
@@ -179,6 +179,14 @@ <h2 class="font-h2 mb-4 mt-12">
179179
<button :disabled="subscriptionData.inProgress || !subscriptionData.token" class="btn btn-primary text-center w-full md:w-64 mt-4" @click.prevent="hubSubscription.transferTokenToHub()">
180180
{{ i18n "hub_billing_manage_license_key_transfer_action" . }}
181181
</button>
182+
<div x-show="subscriptionData.needsTokenRefresh && !subscriptionData.inProgress" class="mt-4">
183+
{{ $challengeUrl := printf "%s/licenses/hub/challenge" .Site.Params.apiBaseUrl }}
184+
{{ partial "captcha.html" (dict "challengeUrl" $challengeUrl "captchaPayload" "subscriptionData.captcha" "captchaState" "captchaState" "ref" "refreshCaptcha" "auto" "onload" "onVerified" "hubSubscription.refreshToken()") }}
185+
</div>
186+
<p x-show="subscriptionData.needsTokenRefresh && subscriptionData.inProgress" class="text-sm text-gray-600 mt-4">
187+
<i class="fa-solid fa-spinner fa-spin"></i>
188+
{{ i18n "hub_billing_loading_description" . }}
189+
</p>
182190

183191
<div x-show="subscriptionData.restartModal.open" class="relative z-10" aria-labelledby="restart-modal-title" role="dialog" aria-modal="true">
184192
<div class="fixed inset-0 bg-gray-500/75"></div>

layouts/hub-register/single.html

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -171,22 +171,8 @@ <h2 class="font-h2 mb-6">
171171

172172
<!-- Captcha (auto-starts on load, hidden when done) -->
173173
<div x-show="!feedbackData.licenseText" class="mb-4">
174-
<altcha-widget
175-
challengeurl="{{ .Site.Params.apiBaseUrl }}/licenses/hub/challenge"
176-
hidelogo
177-
hidefooter
178-
auto="onload"
179-
@statechange="captchaState = $event.detail.state; if ($event.detail.state === 'verified') { submitData.captcha = $event.detail.payload; hubCE.getHubLicense() }"
180-
:strings="JSON.stringify({
181-
label: '{{ i18n "altcha_label" }}',
182-
error: '{{ i18n "altcha_error" }}',
183-
expired: '{{ i18n "altcha_expired" }}',
184-
verified: '{{ i18n "altcha_verified" }}',
185-
verifying: '{{ i18n "altcha_verifying" }}',
186-
waitAlert: '{{ i18n "altcha_waitAlert" }}'
187-
})"
188-
x-ref="licenseCaptcha"
189-
></altcha-widget>
174+
{{ $challengeUrl := printf "%s/licenses/hub/challenge" .Site.Params.apiBaseUrl }}
175+
{{ partial "captcha.html" (dict "challengeUrl" $challengeUrl "captchaPayload" "submitData.captcha" "captchaState" "captchaState" "ref" "licenseCaptcha" "auto" "onload" "onVerified" "hubCE.getHubLicense()") }}
190176
</div>
191177

192178
<!-- Loading state -->

layouts/partials/captcha.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22
challengeurl="{{ .challengeUrl }}"
33
hidelogo
44
hidefooter
5-
floating="auto"
6-
@statechange="{{ .captchaState }} = $event.detail.state; if ($event.detail.state === 'verified') { {{ .captchaPayload }} = $event.detail.payload }"
5+
{{ with .auto }}auto="{{ . }}"{{ else }}floating="auto"{{ end }}
6+
@statechange="{{ .captchaState }} = $event.detail.state; if ($event.detail.state === 'verified') { {{ .captchaPayload }} = $event.detail.payload{{ with .onVerified }}; {{ . }}{{ end }} }"
77
:strings="JSON.stringify({
88
label: '{{ i18n "altcha_label" }}',
99
error: '{{ i18n "altcha_error" }}',

0 commit comments

Comments
 (0)