feat: resume activation handoff after payments
This commit is contained in:
+199
-9
@@ -67,6 +67,15 @@
|
|||||||
|
|
||||||
/** Used-traffic percent from which top-up modals and CTAs unlock in the web app home screen */
|
/** Used-traffic percent from which top-up modals and CTAs unlock in the web app home screen */
|
||||||
const TRAFFIC_TOPUP_UNLOCK_PERCENT = 80;
|
const TRAFFIC_TOPUP_UNLOCK_PERCENT = 80;
|
||||||
|
const ACTIVATION_HANDOFF_STORAGE_KEY = "rw_webapp_activation_handoff_v1";
|
||||||
|
const ACTIVATION_HANDOFF_TTL_MS = 48 * 60 * 60 * 1000;
|
||||||
|
const ACTIVATION_PENDING_WATCH_INTERVAL_MS = 2000;
|
||||||
|
const ACTIVATION_PENDING_WATCH_MAX_ATTEMPTS = 45;
|
||||||
|
const ACTIVATION_RESUME_CHECK_COOLDOWN_MS = 1500;
|
||||||
|
import {
|
||||||
|
activationPaymentFailed,
|
||||||
|
createActivationHandoff,
|
||||||
|
} from "./lib/webapp/activationHandoff.js";
|
||||||
import { buildGravatarUrl } from "./lib/webapp/gravatar.js";
|
import { buildGravatarUrl } from "./lib/webapp/gravatar.js";
|
||||||
import { createBillingActions } from "./lib/webapp/billingActions.js";
|
import { createBillingActions } from "./lib/webapp/billingActions.js";
|
||||||
import { invalidateWebappTariffOptionCaches } from "./lib/webapp/billingOptionCache.js";
|
import { invalidateWebappTariffOptionCaches } from "./lib/webapp/billingOptionCache.js";
|
||||||
@@ -126,6 +135,11 @@
|
|||||||
let trialActivationError = "";
|
let trialActivationError = "";
|
||||||
let activationSuccessDialogOpen = false;
|
let activationSuccessDialogOpen = false;
|
||||||
let activationSuccessUseInstallGuides = false;
|
let activationSuccessUseInstallGuides = false;
|
||||||
|
let activationPendingWatchTimer = null;
|
||||||
|
let activationPendingWatchAttempts = 0;
|
||||||
|
let activationPendingWatchBusy = false;
|
||||||
|
let activationResumeRefreshBusy = false;
|
||||||
|
let activationResumeLastCheckAt = 0;
|
||||||
let promoCode = "";
|
let promoCode = "";
|
||||||
let promoBusy = false;
|
let promoBusy = false;
|
||||||
let promoStatus = "";
|
let promoStatus = "";
|
||||||
@@ -187,6 +201,10 @@
|
|||||||
api: (path, options) => apiClient.api(path, options),
|
api: (path, options) => apiClient.api(path, options),
|
||||||
t: (...args) => t(...args),
|
t: (...args) => t(...args),
|
||||||
});
|
});
|
||||||
|
const activationHandoff = createActivationHandoff({
|
||||||
|
storageKey: ACTIVATION_HANDOFF_STORAGE_KEY,
|
||||||
|
ttlMs: ACTIVATION_HANDOFF_TTL_MS,
|
||||||
|
});
|
||||||
|
|
||||||
const authStore = createAuthStore({
|
const authStore = createAuthStore({
|
||||||
publicApi,
|
publicApi,
|
||||||
@@ -204,6 +222,7 @@
|
|||||||
t,
|
t,
|
||||||
showToast,
|
showToast,
|
||||||
openExternalLink,
|
openExternalLink,
|
||||||
|
onSubscriptionActivationPending: rememberActivationPending,
|
||||||
onSubscriptionActivated: handleSubscriptionActivated,
|
onSubscriptionActivated: handleSubscriptionActivated,
|
||||||
tg,
|
tg,
|
||||||
});
|
});
|
||||||
@@ -515,6 +534,165 @@
|
|||||||
return Boolean(enabled && sub?.active);
|
return Boolean(enabled && sub?.active);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function hasPendingActivationHandoff(payload = data) {
|
||||||
|
return activationHandoff.hasPending(payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
function rememberActivationPending(context = {}) {
|
||||||
|
activationHandoff.rememberPending(context, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearPendingActivationHandoff() {
|
||||||
|
activationHandoff.clearPending();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function maybeShowActivationSuccessDialog(context = {}) {
|
||||||
|
if (activationSuccessDialogOpen) return false;
|
||||||
|
await tick();
|
||||||
|
const payload = context.payload || data;
|
||||||
|
const subscriptionKey = activationHandoff.subscriptionKey(payload);
|
||||||
|
if (!subscriptionKey) return false;
|
||||||
|
const state = activationHandoff.read();
|
||||||
|
const pending = state.pending;
|
||||||
|
if (activationHandoff.isAcknowledged(subscriptionKey, state)) {
|
||||||
|
if (pending && activationHandoff.pendingMatchesUser(pending, payload)) {
|
||||||
|
activationHandoff.write({ ...state, pending: null });
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
!context.force &&
|
||||||
|
(!pending ||
|
||||||
|
!activationHandoff.isPendingFresh(pending) ||
|
||||||
|
!activationHandoff.pendingMatchesUser(pending, payload))
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
activationHandoff.acknowledge(subscriptionKey, context, payload, state);
|
||||||
|
stopPendingActivationWatch();
|
||||||
|
navigateToActivationTarget({ replace: true });
|
||||||
|
activationSuccessDialogOpen = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function stopPendingActivationWatch() {
|
||||||
|
if (activationPendingWatchTimer) {
|
||||||
|
window.clearTimeout(activationPendingWatchTimer);
|
||||||
|
activationPendingWatchTimer = null;
|
||||||
|
}
|
||||||
|
activationPendingWatchAttempts = 0;
|
||||||
|
activationPendingWatchBusy = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function schedulePendingActivationWatch() {
|
||||||
|
if (activationPendingWatchTimer || !hasPendingActivationHandoff()) return;
|
||||||
|
activationPendingWatchTimer = window.setTimeout(() => {
|
||||||
|
activationPendingWatchTimer = null;
|
||||||
|
void checkPendingActivationWatch();
|
||||||
|
}, ACTIVATION_PENDING_WATCH_INTERVAL_MS);
|
||||||
|
}
|
||||||
|
|
||||||
|
function startPendingActivationWatch() {
|
||||||
|
if (
|
||||||
|
mode !== "app" ||
|
||||||
|
!hasPendingActivationHandoff() ||
|
||||||
|
activationSuccessDialogOpen ||
|
||||||
|
screen === "admin"
|
||||||
|
) {
|
||||||
|
stopPendingActivationWatch();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (activationPendingWatchTimer || activationPendingWatchBusy) return;
|
||||||
|
schedulePendingActivationWatch();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function checkPendingActivationWatch() {
|
||||||
|
if (activationPendingWatchBusy) return;
|
||||||
|
if (
|
||||||
|
mode !== "app" ||
|
||||||
|
!hasPendingActivationHandoff() ||
|
||||||
|
activationSuccessDialogOpen ||
|
||||||
|
screen === "admin"
|
||||||
|
) {
|
||||||
|
stopPendingActivationWatch();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (activationPendingWatchAttempts >= ACTIVATION_PENDING_WATCH_MAX_ATTEMPTS) {
|
||||||
|
stopPendingActivationWatch();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const state = activationHandoff.read();
|
||||||
|
const pending = state.pending;
|
||||||
|
activationPendingWatchAttempts += 1;
|
||||||
|
activationPendingWatchBusy = true;
|
||||||
|
try {
|
||||||
|
let shouldRefreshProfile = !pending?.paymentId;
|
||||||
|
if (pending?.paymentId && billing.fetchPaymentStatus) {
|
||||||
|
const paymentStatus = await billing.fetchPaymentStatus(pending.paymentId);
|
||||||
|
if (paymentStatus?.paid || paymentStatus?.status === "succeeded") {
|
||||||
|
shouldRefreshProfile = true;
|
||||||
|
} else if (activationPaymentFailed(paymentStatus)) {
|
||||||
|
clearPendingActivationHandoff();
|
||||||
|
stopPendingActivationWatch();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (shouldRefreshProfile) {
|
||||||
|
await loadData();
|
||||||
|
const shown = await maybeShowActivationSuccessDialog({
|
||||||
|
source: "watch",
|
||||||
|
paymentId: pending?.paymentId,
|
||||||
|
});
|
||||||
|
if (shown || !hasPendingActivationHandoff()) {
|
||||||
|
stopPendingActivationWatch();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (_error) {
|
||||||
|
void _error;
|
||||||
|
} finally {
|
||||||
|
activationPendingWatchBusy = false;
|
||||||
|
}
|
||||||
|
schedulePendingActivationWatch();
|
||||||
|
}
|
||||||
|
|
||||||
|
function canRefreshPendingActivationOnResume() {
|
||||||
|
return Boolean(
|
||||||
|
mode === "app" &&
|
||||||
|
screen !== "admin" &&
|
||||||
|
!activationSuccessDialogOpen &&
|
||||||
|
!paymentModalOpen &&
|
||||||
|
!topupModalOpen &&
|
||||||
|
!deviceTopupModalOpen &&
|
||||||
|
!changeModalOpen &&
|
||||||
|
!changeConfirmOpen &&
|
||||||
|
hasPendingActivationHandoff()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function refreshPendingActivationOnResume() {
|
||||||
|
if (!canRefreshPendingActivationOnResume()) return;
|
||||||
|
const now = Date.now();
|
||||||
|
if (
|
||||||
|
activationResumeRefreshBusy ||
|
||||||
|
now - activationResumeLastCheckAt < ACTIVATION_RESUME_CHECK_COOLDOWN_MS
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
activationResumeLastCheckAt = now;
|
||||||
|
activationResumeRefreshBusy = true;
|
||||||
|
try {
|
||||||
|
await loadData();
|
||||||
|
const shown = await maybeShowActivationSuccessDialog({ source: "resume" });
|
||||||
|
if (!shown) startPendingActivationWatch();
|
||||||
|
} catch (_error) {
|
||||||
|
void _error;
|
||||||
|
} finally {
|
||||||
|
activationResumeRefreshBusy = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function refreshAppLaunchTarget() {
|
function refreshAppLaunchTarget() {
|
||||||
appLaunchTarget = readExternalAppLaunchTarget();
|
appLaunchTarget = readExternalAppLaunchTarget();
|
||||||
return appLaunchTarget;
|
return appLaunchTarget;
|
||||||
@@ -534,6 +712,13 @@
|
|||||||
const onAnyPointerDown = () => {
|
const onAnyPointerDown = () => {
|
||||||
if (mode === "login") loginEmailTooltipOpen = false;
|
if (mode === "login") loginEmailTooltipOpen = false;
|
||||||
};
|
};
|
||||||
|
const onActivationResume = () => {
|
||||||
|
if (typeof document !== "undefined" && document.visibilityState === "hidden") return;
|
||||||
|
void refreshPendingActivationOnResume();
|
||||||
|
};
|
||||||
|
const onVisibilityChange = () => {
|
||||||
|
if (document.visibilityState !== "hidden") onActivationResume();
|
||||||
|
};
|
||||||
const onPopState = () => {
|
const onPopState = () => {
|
||||||
const shareToken = publicInstallTokenFromPath(window.location.pathname);
|
const shareToken = publicInstallTokenFromPath(window.location.pathname);
|
||||||
if (shareToken) {
|
if (shareToken) {
|
||||||
@@ -585,15 +770,22 @@
|
|||||||
};
|
};
|
||||||
window.addEventListener("popstate", onPopState);
|
window.addEventListener("popstate", onPopState);
|
||||||
window.addEventListener("pointerdown", onAnyPointerDown);
|
window.addEventListener("pointerdown", onAnyPointerDown);
|
||||||
|
window.addEventListener("focus", onActivationResume);
|
||||||
|
window.addEventListener("pageshow", onActivationResume);
|
||||||
|
document.addEventListener("visibilitychange", onVisibilityChange);
|
||||||
boot();
|
boot();
|
||||||
return () => {
|
return () => {
|
||||||
window.removeEventListener("popstate", onPopState);
|
window.removeEventListener("popstate", onPopState);
|
||||||
window.removeEventListener("pointerdown", onAnyPointerDown);
|
window.removeEventListener("pointerdown", onAnyPointerDown);
|
||||||
|
window.removeEventListener("focus", onActivationResume);
|
||||||
|
window.removeEventListener("pageshow", onActivationResume);
|
||||||
|
document.removeEventListener("visibilitychange", onVisibilityChange);
|
||||||
authStore.stopTelegramLoginWatchdog();
|
authStore.stopTelegramLoginWatchdog();
|
||||||
authStore.clearCooldownTimer();
|
authStore.clearCooldownTimer();
|
||||||
accountStore.clearLinkEmailResendTimer();
|
accountStore.clearLinkEmailResendTimer();
|
||||||
accountStore.clearSetPasswordResendTimer();
|
accountStore.clearSetPasswordResendTimer();
|
||||||
supportStore.closePolling();
|
supportStore.closePolling();
|
||||||
|
stopPendingActivationWatch();
|
||||||
clearLanguageClickGuard();
|
clearLanguageClickGuard();
|
||||||
syncBodyScrollLock(false);
|
syncBodyScrollLock(false);
|
||||||
destroyAdminMount();
|
destroyAdminMount();
|
||||||
@@ -877,6 +1069,10 @@
|
|||||||
getToken: () => token,
|
getToken: () => token,
|
||||||
getCsrfToken: () => csrfToken,
|
getCsrfToken: () => csrfToken,
|
||||||
});
|
});
|
||||||
|
if (mode === "app" && screen !== "admin") {
|
||||||
|
const shown = await maybeShowActivationSuccessDialog({ source: "boot" });
|
||||||
|
if (!shown) startPendingActivationWatch();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function stripTopupQueryFromUrl() {
|
function stripTopupQueryFromUrl() {
|
||||||
@@ -1206,16 +1402,10 @@
|
|||||||
syncSectionPath("home", replace);
|
syncSectionPath("home", replace);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function showActivationSuccessDialog() {
|
async function handleSubscriptionActivated(context = {}) {
|
||||||
await tick();
|
|
||||||
navigateToActivationTarget({ replace: true });
|
|
||||||
activationSuccessDialogOpen = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handleSubscriptionActivated() {
|
|
||||||
await tick();
|
await tick();
|
||||||
if (!subscription?.active) return;
|
if (!subscription?.active) return;
|
||||||
await showActivationSuccessDialog();
|
await maybeShowActivationSuccessDialog({ ...context, force: true, source: "payment" });
|
||||||
}
|
}
|
||||||
|
|
||||||
function closeActivationSuccessDialog() {
|
function closeActivationSuccessDialog() {
|
||||||
@@ -1290,7 +1480,7 @@
|
|||||||
trialActivationResult = response;
|
trialActivationResult = response;
|
||||||
showToast(t("wa_trial_activated"));
|
showToast(t("wa_trial_activated"));
|
||||||
await loadData();
|
await loadData();
|
||||||
await showActivationSuccessDialog();
|
await maybeShowActivationSuccessDialog({ source: "trial", force: true });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const message = error?.message || t("wa_trial_activation_failed");
|
const message = error?.message || t("wa_trial_activation_failed");
|
||||||
trialActivationError = message;
|
trialActivationError = message;
|
||||||
|
|||||||
@@ -0,0 +1,151 @@
|
|||||||
|
export function activationPaymentFailed(status) {
|
||||||
|
const normalized = String(status?.status || "").toLowerCase();
|
||||||
|
return (
|
||||||
|
normalized === "failed" ||
|
||||||
|
normalized === "canceled" ||
|
||||||
|
normalized === "cancelled" ||
|
||||||
|
normalized.startsWith("failed_")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createActivationHandoff({ storageKey, ttlMs, now = () => Date.now() } = {}) {
|
||||||
|
let fallbackState = null;
|
||||||
|
|
||||||
|
function normalizeState(value) {
|
||||||
|
return value && typeof value === "object"
|
||||||
|
? {
|
||||||
|
pending: value.pending && typeof value.pending === "object" ? value.pending : null,
|
||||||
|
acknowledged:
|
||||||
|
value.acknowledged && typeof value.acknowledged === "object"
|
||||||
|
? value.acknowledged
|
||||||
|
: null,
|
||||||
|
}
|
||||||
|
: { pending: null, acknowledged: null };
|
||||||
|
}
|
||||||
|
|
||||||
|
function isPendingFresh(pending) {
|
||||||
|
const startedAt = Number(pending?.startedAt || 0);
|
||||||
|
return Boolean(startedAt && now() - startedAt <= ttlMs);
|
||||||
|
}
|
||||||
|
|
||||||
|
function write(state) {
|
||||||
|
const normalized = normalizeState(state);
|
||||||
|
fallbackState = normalized;
|
||||||
|
if (!storageKey) return;
|
||||||
|
try {
|
||||||
|
localStorage.setItem(storageKey, JSON.stringify(normalized));
|
||||||
|
} catch (_error) {
|
||||||
|
void _error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function read() {
|
||||||
|
let state = fallbackState || { pending: null, acknowledged: null };
|
||||||
|
if (storageKey) {
|
||||||
|
try {
|
||||||
|
const raw = localStorage.getItem(storageKey);
|
||||||
|
if (raw) state = JSON.parse(raw);
|
||||||
|
} catch (_error) {
|
||||||
|
void _error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
state = normalizeState(state);
|
||||||
|
if (state.pending && !isPendingFresh(state.pending)) {
|
||||||
|
state = { ...state, pending: null };
|
||||||
|
write(state);
|
||||||
|
}
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
function userKey(payload = {}) {
|
||||||
|
const payloadUser = payload?.user || {};
|
||||||
|
return String(payloadUser.user_id ?? payloadUser.id ?? payloadUser.telegram_id ?? "").trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
function subscriptionKey(payload = {}) {
|
||||||
|
const payloadSubscription = payload?.subscription || {};
|
||||||
|
if (!payloadSubscription?.active) return "";
|
||||||
|
return [
|
||||||
|
userKey(payload) || "anonymous",
|
||||||
|
payloadSubscription.panel_short_uuid ||
|
||||||
|
payloadSubscription.panel_uuid ||
|
||||||
|
payloadSubscription.uuid ||
|
||||||
|
payloadSubscription.subscription_id ||
|
||||||
|
payloadSubscription.config_link ||
|
||||||
|
payloadSubscription.connect_url ||
|
||||||
|
"active",
|
||||||
|
payloadSubscription.end_date || payloadSubscription.end_date_text || "",
|
||||||
|
payloadSubscription.tariff_key || payloadSubscription.tariff_name || "",
|
||||||
|
payloadSubscription.status || "",
|
||||||
|
]
|
||||||
|
.map((part) => String(part || "").trim())
|
||||||
|
.join("|");
|
||||||
|
}
|
||||||
|
|
||||||
|
function pendingMatchesUser(pending, payload = {}) {
|
||||||
|
if (!pending) return false;
|
||||||
|
const pendingUserKey = String(pending.userKey || "").trim();
|
||||||
|
const currentUserKey = userKey(payload);
|
||||||
|
return !pendingUserKey || !currentUserKey || pendingUserKey === currentUserKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasPending(payload = {}) {
|
||||||
|
const pending = read().pending;
|
||||||
|
return Boolean(pending && pendingMatchesUser(pending, payload));
|
||||||
|
}
|
||||||
|
|
||||||
|
function rememberPending(context = {}, payload = {}) {
|
||||||
|
if (context.initialSubscriptionPayment === false) return;
|
||||||
|
const state = read();
|
||||||
|
write({
|
||||||
|
...state,
|
||||||
|
pending: {
|
||||||
|
kind: "initial_subscription",
|
||||||
|
source: String(context.source || "payment"),
|
||||||
|
paymentId: String(context.paymentId || ""),
|
||||||
|
userKey: userKey(payload),
|
||||||
|
startedAt: now(),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearPending() {
|
||||||
|
const state = read();
|
||||||
|
if (!state.pending) return;
|
||||||
|
write({ ...state, pending: null });
|
||||||
|
}
|
||||||
|
|
||||||
|
function isAcknowledged(nextSubscriptionKey, state = read()) {
|
||||||
|
return Boolean(
|
||||||
|
nextSubscriptionKey && state.acknowledged?.subscriptionKey === nextSubscriptionKey
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function acknowledge(nextSubscriptionKey, context = {}, payload = {}, state = read()) {
|
||||||
|
const pending = state.pending || {};
|
||||||
|
write({
|
||||||
|
...state,
|
||||||
|
pending: null,
|
||||||
|
acknowledged: {
|
||||||
|
subscriptionKey: nextSubscriptionKey,
|
||||||
|
source: String(context.source || pending.source || "payment"),
|
||||||
|
paymentId: String(context.paymentId || pending.paymentId || ""),
|
||||||
|
userKey: userKey(payload),
|
||||||
|
acknowledgedAt: now(),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
acknowledge,
|
||||||
|
clearPending,
|
||||||
|
hasPending,
|
||||||
|
isAcknowledged,
|
||||||
|
isPendingFresh,
|
||||||
|
pendingMatchesUser,
|
||||||
|
read,
|
||||||
|
rememberPending,
|
||||||
|
subscriptionKey,
|
||||||
|
write,
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@ export function createBillingStore({
|
|||||||
t,
|
t,
|
||||||
showToast,
|
showToast,
|
||||||
openExternalLink,
|
openExternalLink,
|
||||||
|
onSubscriptionActivationPending = null,
|
||||||
onSubscriptionActivated = null,
|
onSubscriptionActivated = null,
|
||||||
tg,
|
tg,
|
||||||
}) {
|
}) {
|
||||||
@@ -68,7 +69,21 @@ export function createBillingStore({
|
|||||||
successContext.initialSubscriptionPayment &&
|
successContext.initialSubscriptionPayment &&
|
||||||
typeof onSubscriptionActivated === "function"
|
typeof onSubscriptionActivated === "function"
|
||||||
) {
|
) {
|
||||||
await onSubscriptionActivated();
|
await onSubscriptionActivated({ source: "payment", ...successContext });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function rememberSubscriptionActivationPending(successContext = {}) {
|
||||||
|
if (
|
||||||
|
!successContext.initialSubscriptionPayment ||
|
||||||
|
typeof onSubscriptionActivationPending !== "function"
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
onSubscriptionActivationPending({ source: "payment", ...successContext });
|
||||||
|
} catch (_error) {
|
||||||
|
void _error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -264,6 +279,7 @@ export function createBillingStore({
|
|||||||
if (!response.ok) throw response;
|
if (!response.ok) throw response;
|
||||||
showToast(t("wa_payment_created"));
|
showToast(t("wa_payment_created"));
|
||||||
const successContext = paymentSuccessContext(s, response);
|
const successContext = paymentSuccessContext(s, response);
|
||||||
|
rememberSubscriptionActivationPending(successContext);
|
||||||
if (response.action === "open_invoice") {
|
if (response.action === "open_invoice") {
|
||||||
if (!response.payment_url) throw response;
|
if (!response.payment_url) throw response;
|
||||||
openTelegramInvoice(response.payment_url, successContext);
|
openTelegramInvoice(response.payment_url, successContext);
|
||||||
|
|||||||
Reference in New Issue
Block a user