fix: bind HWID top-ups to subscription periods

This commit is contained in:
3252a8
2026-05-25 19:33:50 +03:00
parent 59fa301344
commit c68cb97964
49 changed files with 2027 additions and 119 deletions
+1
View File
@@ -1240,6 +1240,7 @@
stripTopupQueryFromUrl();
}
}
return payload;
}
async function loadPublicInstall(shareToken) {
+16 -4
View File
@@ -49,6 +49,8 @@ export function rowsFromPackages(packageSet, currency, valueKey) {
return (packageSet?.[currency] || []).map((pkg) => ({
[valueKey]: pkg[valueKey],
price: pkg.price,
prices: pkg.prices ? structuredCloneSafe(pkg.prices) : undefined,
min_price: pkg.min_price ?? "",
}));
}
@@ -115,10 +117,20 @@ export function compactMap(obj) {
export function packagesFromRows(rows, valueKey) {
return (rows || [])
.map((row) => ({
[valueKey]: parseNumber(row[valueKey]),
price: parseNumber(row.price),
}))
.map((row) => {
const pkg = {
[valueKey]: parseNumber(row[valueKey]),
price: parseNumber(row.price),
};
if (row.prices && typeof row.prices === "object") {
pkg.prices = structuredCloneSafe(row.prices);
}
const minPrice = parseNumber(row.min_price);
if (minPrice !== null) {
pkg.min_price = minPrice;
}
return pkg;
})
.filter((row) => row[valueKey] > 0 && row.price !== null && row.price >= 0);
}
+1 -1
View File
@@ -53,7 +53,7 @@ export function createBillingActions({ api }) {
months: plan.device_count || plan.months,
device_count: plan.device_count || plan.months,
tariff_key: plan.tariff_key || fallbackTariffKey,
sale_mode: "hwid_devices",
sale_mode: plan.sale_mode || "hwid_devices",
method,
};
}
+6
View File
@@ -669,6 +669,10 @@ export function applyPreviewMock(kind) {
tariff_key: "standard",
tariff_name: "Стандарт",
current_limit: 5,
extra_hwid_devices: 2,
extra_hwid_devices_valid_until_text: "01.06.2026 12:00",
renewal_available: false,
renewal_recommended_count: 0,
plans: [
{
id: "standard:hwid:1",
@@ -702,6 +706,8 @@ export function applyPreviewMock(kind) {
...DEV_MOCK.data.subscription,
active: true,
max_devices: 5,
extra_hwid_devices: 2,
extra_hwid_devices_valid_until_text: "01.06.2026 12:00",
};
} else if (mode === "trial") {
DEV_MOCK.data.settings.traffic_mode = false;
+21 -4
View File
@@ -43,9 +43,14 @@ export function createBillingStore({
function isSubscriptionSale(plan) {
const saleMode = String(plan?.sale_mode || "subscription").toLowerCase();
return !["traffic", "traffic_package", "topup", "premium_topup", "hwid_devices"].includes(
saleMode
);
return ![
"traffic",
"traffic_package",
"topup",
"premium_topup",
"hwid_devices",
"hwid_devices_renewal",
].includes(saleMode);
}
function paymentSuccessContext(s, response = {}) {
@@ -53,6 +58,8 @@ export function createBillingStore({
paymentId: response.payment_id || "",
initialSubscriptionPayment:
!s.paymentStartedWithActiveSubscription && isSubscriptionSale(s.selectedPlan),
renewalSubscriptionPayment:
s.paymentStartedWithActiveSubscription && isSubscriptionSale(s.selectedPlan),
};
}
@@ -64,7 +71,15 @@ export function createBillingStore({
paymentPollToken += 1;
}
showToast(t("wa_payment_success", {}, "Payment successful"));
await loadData({ fresh: true });
const payload = await loadData({ fresh: true });
if (
successContext.renewalSubscriptionPayment &&
payload?.subscription?.device_topup_renewal_available &&
payload?.subscription?.can_topup_devices
) {
showToast(t("wa_hwid_devices_renewal_prompt"));
openDeviceTopupModal(payload.payment_methods?.[0]?.id || "");
}
if (
successContext.initialSubscriptionPayment &&
typeof onSubscriptionActivated === "function"
@@ -190,6 +205,8 @@ export function createBillingStore({
state.update((s) => ({
...s,
deviceTopupModalOpen: true,
deviceTopupOptions: null,
selectedDeviceTopupPlan: null,
selectedMethod: s.selectedMethod || defaultMethod,
}));
loadDeviceTopupOptions();
+7
View File
@@ -591,6 +591,13 @@ a {
margin-top: 0;
}
.devices-topup-validity {
margin: 0;
color: var(--muted);
font-size: 11px;
line-height: 1.35;
}
.devices-list {
display: grid;
gap: 8px;
+20
View File
@@ -197,6 +197,16 @@
<p>{subscriptionPurchaseDescription}</p>
</div>
{/if}
{#if subscription?.active && Number(subscription?.extra_hwid_devices || 0) > 0}
<div class="subscription-purchase-description">
<p>
{t("wa_hwid_devices_renewal_notice", {
count: Number(subscription.extra_hwid_devices || 0),
date: subscription.extra_hwid_devices_valid_until_text || "",
})}
</p>
</div>
{/if}
<div class="period-grid period-grid-two-columns">
{#each selectedTariffPlans as plan}
<button
@@ -251,6 +261,16 @@
<p>{subscriptionPurchaseDescription}</p>
</div>
{/if}
{#if subscription?.active && Number(subscription?.extra_hwid_devices || 0) > 0}
<div class="subscription-purchase-description">
<p>
{t("wa_hwid_devices_renewal_notice", {
count: Number(subscription.extra_hwid_devices || 0),
date: subscription.extra_hwid_devices_valid_until_text || "",
})}
</p>
</div>
{/if}
<div class="period-grid period-grid-two-columns">
{#each plans as plan}
<button
+19
View File
@@ -369,6 +369,25 @@
{#if !deviceTopupOptions}
<DialogOptionsSkeleton label={t("wa_tariff_options_loading")} rows={3} />
{:else if deviceTopupOptions?.plans?.length}
{#if deviceTopupOptions?.renewal_available}
<div class="topup-carryover-note">
<p>
{t("wa_hwid_devices_renewal_offer", {
count: Number(deviceTopupOptions.renewal_recommended_count || 0),
date: deviceTopupOptions.extra_hwid_devices_valid_until_text || "",
})}
</p>
</div>
{:else if Number(deviceTopupOptions?.extra_hwid_devices || 0) > 0 && deviceTopupOptions?.extra_hwid_devices_valid_until_text}
<div class="topup-carryover-note">
<p>
{t("wa_hwid_devices_valid_until", {
count: Number(deviceTopupOptions.extra_hwid_devices || 0),
date: deviceTopupOptions.extra_hwid_devices_valid_until_text,
})}
</p>
</div>
{/if}
<div class="option-list">
{#each deviceTopupOptions.plans as plan}
<button
@@ -46,6 +46,14 @@
value={devicesPercent(devicesData)}
label={t("wa_devices_title")}
/>
{#if Number(subscription?.extra_hwid_devices || 0) > 0 && subscription?.extra_hwid_devices_valid_until_text}
<p class="devices-topup-validity">
{t("wa_hwid_devices_valid_until", {
count: Number(subscription.extra_hwid_devices || 0),
date: subscription.extra_hwid_devices_valid_until_text,
})}
</p>
{/if}
{#if subscription?.active && subscription?.max_devices !== 0 && subscription?.can_topup_devices}
<Button variant="secondary" class="wide" onclick={openDeviceTopupModal}>
<Plus size={17} />