Polar has no quote API. immediateChargeAmount comes back as 0 even when Polar is about to invoice. If you apply the plan from that 200, you showed a price that isn't real, and you wrote entitlements before Polar did.
const pendingPayment =
ownedProvider.providerId === 'polar'
? !context.scheduleAtPeriodEnd
: quote.immediateChargeAmount > 0;
Preview still matters. I mint a signed quote with the org, subscription, target plan version, Polar product id, discounts, and money fields. Five minutes, then it dies.
const PLAN_CHANGE_QUOTE_TTL_MS = 5 * 60 * 1_000;
const quoteToken = mintPlanChangeQuote(
{
...payload,
issuedAt,
expiresAt: issuedAt + PLAN_CHANGE_QUOTE_TTL_MS,
},
this.quoteSecret(),
);
On commit I verify the token, preview again, and reject if the money moved. Then I persist a pending intent so a fast plan_changed webhook can mark the row applied instead of racing the HTTP response.
await this.planChangeRequests.upsertPending({
organizationId: context.organizationId,
provider: ownedProvider.providerId,
providerSubscriptionId: context.providerSubscriptionId,
targetPlanId: context.targetPlan.id,
targetProviderProductId: targetProductId,
});
Mock billing is the only local apply
The mock adapter has no webhooks, so immediate changes write locally. Polar does not. Production stays pending until Polar writes.
I stopped flipping entitlements from the HTTP 200. Polar writes them.
