mirror of
https://github.com/hanzoai/commerce.git
synced 2026-10-30 15:08:42 +00:00
A plan with trialPeriodDays sold through POST /v1/billing/subscribe/card to a buyer and a card that have not had its trial charges nothing today. The card is vaulted through saveCard, which validates it; the subscription opens trialing on it, Square-backed and carrying any promo; the receipt answers amountCents 0, trialEnd and trialEndCents; the operator notice says it is a trial. One trial per buyer and per card. The buyer rule reads the subject's own subscriptions: a subject that has held the plan before, in any status, is not offered the trial. The card rule spans every buyer org: each buyer's sale runs in its own org's namespace, so a taken trial is recorded with the platform's other per-card offer uses, as a couponredemption row in the platform namespace keyed by the plan's trial and the hashed card (promo.TakeTrial, under the offer's lock). A sandbox trial is its own offer. A refused trial sells exactly as a plan without one, charged now. A store that cannot answer ends the sale with nothing charged. A coupon on a sale that would open a trial is refused (400) and the trial given back: a coupon discounts a charge made today, and a trial makes none. The renewal sweep charges the trial's end. engine.IsDue counts a trialing row on a card once its TrialEnd has passed, and RenewSubscription treats the trial as what the row has been served (servedTo), so the first paid period is invoiced and collected through CollectInvoice under the per-period key. Paid moves the row active; a decline leaves it past due with the invoice open, as for any renewal. A trial canceled at its end ends with it, unbilled. A trial with no card on it is never due, so the signup on-ramp is unchanged. Co-authored-by: Hanzo Dev <[email protected]>