From 14e059784cdde4212d94f92154f7b304b78c7e39 Mon Sep 17 00:00:00 2001 From: Mauricio Siu Date: Fri, 18 Sep 2026 02:41:28 -0600 Subject: [PATCH] fix(billing): stop the reminder claim from rewriting stale metadata Stripe merges metadata on update, so spreading the snapshot read during the list call re-asserted stale values over anything written since. Send only the key being claimed. The claim is not exclusive and the comment no longer says it is: two replicas can still read the same unset marker. Marking before the send only keeps a crash mid-send from resending on the next run. --- apps/dokploy/server/utils/trial-notifications.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/apps/dokploy/server/utils/trial-notifications.ts b/apps/dokploy/server/utils/trial-notifications.ts index 2a7b9c90e..7e2fc20e7 100644 --- a/apps/dokploy/server/utils/trial-notifications.ts +++ b/apps/dokploy/server/utils/trial-notifications.ts @@ -94,9 +94,11 @@ const processSubscription = async ( ); const plan = planFromPriceIds(priceIds); - // Claim the reminder before sending so concurrent replicas don't double-send. + // Marked before sending so a crash mid-send cannot resend on the next run. + // Stripe merges metadata, so only the claimed key travels; spreading the + // snapshot read at list time would rewrite concurrent changes. await stripe.subscriptions.update(subscription.id, { - metadata: { ...subscription.metadata, [threshold.metadataKey]: "sent" }, + metadata: { [threshold.metadataKey]: "sent" }, }); try { @@ -109,7 +111,7 @@ const processSubscription = async ( }); } catch (error) { await stripe.subscriptions.update(subscription.id, { - metadata: { ...subscription.metadata, [threshold.metadataKey]: "" }, + metadata: { [threshold.metadataKey]: "" }, }); throw error; }