feat: resume activation handoff after payments

This commit is contained in:
3252a8
2026-05-24 22:45:54 +03:00
parent bafc5f4709
commit 9674c674a2
3 changed files with 367 additions and 10 deletions
@@ -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,
};
}
+17 -1
View File
@@ -6,6 +6,7 @@ export function createBillingStore({
t,
showToast,
openExternalLink,
onSubscriptionActivationPending = null,
onSubscriptionActivated = null,
tg,
}) {
@@ -68,7 +69,21 @@ export function createBillingStore({
successContext.initialSubscriptionPayment &&
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;
showToast(t("wa_payment_created"));
const successContext = paymentSuccessContext(s, response);
rememberSubscriptionActivationPending(successContext);
if (response.action === "open_invoice") {
if (!response.payment_url) throw response;
openTelegramInvoice(response.payment_url, successContext);