mirror of
https://github.com/chillpadclub/bedolaga-cabinet.git
synced 2026-07-29 18:13:47 +00:00
fix(subscription): forward subscription_id to purchaseTariff to fix renew race
Companion to bedolaga-bot a527df23. Bot's purchase-tariff endpoint now accepts optional subscription_id to resolve the EXACT target row by ID instead of doing a (user_id, tariff_id) re-lookup that races with concurrent panel webhooks (produces 'Тариф уже активен' + refund + no extension). Frontend changes: * subscriptionApi.purchaseTariff() — new optional subscriptionId arg appended after trafficGb. Forwarded as subscription_id in POST body. * SubscriptionPurchase.tsx — forwards URL searchParam subscriptionId (set when user lands here from 'Renew this subscription'). * SubscriptionCardExpired.tsx — passes subscription.id when renewing an expired daily tariff (the .purchaseTariff(tariff_id, 1) call).
This commit is contained in:
@@ -398,6 +398,14 @@ export const subscriptionApi = {
|
|||||||
tariffId: number,
|
tariffId: number,
|
||||||
periodDays: number,
|
periodDays: number,
|
||||||
trafficGb?: number,
|
trafficGb?: number,
|
||||||
|
/**
|
||||||
|
* Subscription ID being renewed. Pass this when the user clicked
|
||||||
|
* "Renew" on an existing subscription so the backend can resolve
|
||||||
|
* the target row by ID instead of doing a (user_id, tariff_id)
|
||||||
|
* re-lookup that races with concurrent panel webhooks. Omit when
|
||||||
|
* this is a fresh purchase from the catalog.
|
||||||
|
*/
|
||||||
|
subscriptionId?: number,
|
||||||
): Promise<{
|
): Promise<{
|
||||||
success: boolean;
|
success: boolean;
|
||||||
message: string;
|
message: string;
|
||||||
@@ -411,6 +419,7 @@ export const subscriptionApi = {
|
|||||||
tariff_id: tariffId,
|
tariff_id: tariffId,
|
||||||
period_days: periodDays,
|
period_days: periodDays,
|
||||||
traffic_gb: trafficGb,
|
traffic_gb: trafficGb,
|
||||||
|
subscription_id: subscriptionId,
|
||||||
});
|
});
|
||||||
return response.data;
|
return response.data;
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -59,8 +59,11 @@ export default function SubscriptionCardExpired({
|
|||||||
// Resume daily subscription via toggle pause endpoint
|
// Resume daily subscription via toggle pause endpoint
|
||||||
await subscriptionApi.togglePause(subscription.id);
|
await subscriptionApi.togglePause(subscription.id);
|
||||||
} else if (isDaily && subscription.tariff_id) {
|
} else if (isDaily && subscription.tariff_id) {
|
||||||
// Expired daily tariff — purchase for 1 day
|
// Expired daily tariff — purchase for 1 day. Pass subscription.id
|
||||||
await subscriptionApi.purchaseTariff(subscription.tariff_id, 1);
|
// so the backend resolves the EXACT row instead of doing a
|
||||||
|
// (user_id, tariff_id) re-lookup that races with concurrent
|
||||||
|
// panel webhooks (would surface as "Тариф уже активен" + refund).
|
||||||
|
await subscriptionApi.purchaseTariff(subscription.tariff_id, 1, undefined, subscription.id);
|
||||||
} else {
|
} else {
|
||||||
await subscriptionApi.renewSubscription(30, subscription.id);
|
await subscriptionApi.renewSubscription(30, subscription.id);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -305,7 +305,17 @@ export default function SubscriptionPurchase() {
|
|||||||
: selectedTariffPeriod?.days || 30;
|
: selectedTariffPeriod?.days || 30;
|
||||||
const trafficGb =
|
const trafficGb =
|
||||||
useCustomTraffic && selectedTariff.custom_traffic_enabled ? customTrafficGb : undefined;
|
useCustomTraffic && selectedTariff.custom_traffic_enabled ? customTrafficGb : undefined;
|
||||||
return subscriptionApi.purchaseTariff(selectedTariff.id, days, trafficGb);
|
// Forward the subscription_id when the user landed here via the
|
||||||
|
// "Renew this subscription" flow (?subscriptionId=N). The backend
|
||||||
|
// uses it to resolve the exact target row by ID, avoiding the
|
||||||
|
// race with concurrent panel webhooks that would otherwise hit
|
||||||
|
// the partial UNIQUE on uq_subscriptions_user_tariff_active.
|
||||||
|
return subscriptionApi.purchaseTariff(
|
||||||
|
selectedTariff.id,
|
||||||
|
days,
|
||||||
|
trafficGb,
|
||||||
|
subscriptionId ?? undefined,
|
||||||
|
);
|
||||||
},
|
},
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
queryClient.invalidateQueries({ queryKey: ['subscription'] });
|
queryClient.invalidateQueries({ queryKey: ['subscription'] });
|
||||||
|
|||||||
Reference in New Issue
Block a user