package main // ๐Ÿค– Lifetime access levels (docs/managed-payments.md ยง Lifetime): pay once, // in forever. These pin the four load-bearing properties: the grant has no // lifecycle (no sweep, no repair pass may ever touch it), a refund/chargeback // un-buys it immediately, both providers mint payment-mode checkouts with the // commission applied as a fixed amount, and the renewal-shaped member // endpoints (cancel/resume/change/portal) answer honestly instead of // confusing Stripe with a payment-intent id. import ( "context" "encoding/json" "fmt" "net/http" "strconv" "strings" "testing" "time" ) // countingPay is a configured provider that counts SubState fetches โ€” the // repair-pass skip is the property under test, so the count is the assert. type countingPay struct { noPay subStateCalls int } func (c *countingPay) Configured() bool { return true } func (c *countingPay) SubState(context.Context, string) (SubEvent, bool, error) { c.subStateCalls++ return SubEvent{}, false, nil } // lifetimeGrant is the event a completed payment-mode checkout produces. func lifetimeGrant(uid, tid string, now int64) SubEvent { return SubEvent{ ExtSub: "pi_life_1", ExtCustomer: "cus_1", UserRef: uid, TierRef: tid, Status: "active", Created: now, AllowCreate: true, Lifetime: true, } } func TestLifetimeGrantNeverSwept(t *testing.T) { _, srv := newTestServer(t) tid, uid := seedTierAndMember(t, srv, "Founder", "bea") now := int64(3_000_000) if err := srv.applyWebhookEvent(context.Background(), lifetimeGrant(uid, tid, now), now); err != nil { t.Fatal(err) } if !srv.db.tierMember(tid, uid) { t.Fatal("member not assigned after lifetime purchase") } e, ok := srv.db.memberEntitlement(tid, uid) if !ok || !e.Lifetime || e.Status != "active" || e.ExtSub != "pi_life_1" { t.Fatalf("entitlement = %+v ok=%v, want active lifetime on pi_life_1", e, ok) } // A decade of sweeps: the grace clause can't match (no deadline), the // silent-lapse backstop can't match (no paid_until), and the repair pass // must SKIP the row โ€” there is no subscription to re-fetch, and a // not-found answer would revoke a valid purchase. fake := &countingPay{} srv.pay = fake tenYears := now + 10*365*24*3600 if n := srv.runBillingSweep(tenYears); n != 0 { t.Fatalf("sweep revoked %d membership(s) โ€” a lifetime purchase expired", n) } if !srv.db.tierMember(tid, uid) { t.Fatal("lifetime membership gone after sweeps") } if fake.subStateCalls != 0 { t.Fatalf("repair pass fetched provider state %d time(s) for a lifetime row", fake.subStateCalls) } // Redelivery of the same grant is a no-op, not a duplicate. if err := srv.applyWebhookEvent(context.Background(), lifetimeGrant(uid, tid, now), now); err != nil { t.Fatal(err) } if !srv.db.tierMember(tid, uid) { t.Fatal("redelivered grant broke the membership") } } func TestLifetimeRefundRevokes(t *testing.T) { _, srv := newTestServer(t) tid, uid := seedTierAndMember(t, srv, "Founder", "bea") now := int64(3_000_000) if err := srv.applyWebhookEvent(context.Background(), lifetimeGrant(uid, tid, now), now); err != nil { t.Fatal(err) } // The refund/chargeback event: revoke is immediate โ€” grace windows exist // to survive a failing card, and nothing here is failing. refund := SubEvent{ ExtSub: "pi_life_1", UserRef: uid, TierRef: tid, Status: "canceled", Created: now + 100, Deleted: true, Lifetime: true, } if err := srv.applyWebhookEvent(context.Background(), refund, now+100); err != nil { t.Fatal(err) } if srv.db.tierMember(tid, uid) { t.Fatal("membership survived a refund") } // And the no-resurrect property holds: a redelivered grant older than the // refund must not re-buy the level. if err := srv.applyWebhookEvent(context.Background(), lifetimeGrant(uid, tid, now), now+200); err != nil { t.Fatal(err) } if srv.db.tierMember(tid, uid) { t.Fatal("stale redelivered grant resurrected a refunded purchase") } } // connectTestProvider is a stripeConnect against a captured fake Stripe. func connectTestProvider(t *testing.T, srv *Server) (*stripeConnect, *map[string][]string) { t.Helper() stripe, captured, _ := fakePlatformStripe(t) if err := srv.db.setConfig("stripe_connect_account", "acct_admin"); err != nil { t.Fatal(err) } p := &stripeConnect{db: srv.db, key: "sk", webhookSecret: "whsec_connect", apiBase: stripe.URL, client: http.DefaultClient} return p, (*map[string][]string)(captured) } func TestLifetimeCheckoutForms(t *testing.T) { _, srv := newTestServer(t) // BYO: mode=payment, the fee as a fixed amount (5.5% of the price), the // routing metadata mirrored onto the payment intent for refund routing, // and nothing subscription-shaped. pc, capturedC := connectTestProvider(t, srv) if _, err := pc.Checkout(context.Background(), CheckoutReq{ TierID: "t1", PriceRef: "price_once", UserRef: "u1", Once: true, AmountMinor: 11000, SuccessURL: "https://s/ok", CancelURL: "https://s/no", }); err != nil { t.Fatal(err) } fc := *capturedC if got := fc["mode"]; len(got) != 1 || got[0] != "payment" { t.Errorf("BYO mode = %v", got) } if got := fc["payment_intent_data[application_fee_amount]"]; len(got) != 1 || got[0] != "605" { t.Errorf("BYO fee = %v, want 605 (5.5%% of 11000)", got) } if got := fc["payment_intent_data[metadata][tier_ref]"]; len(got) != 1 || got[0] != "t1" { t.Errorf("BYO intent metadata = %v", fc) } for k := range fc { if strings.Contains(k, "subscription_data") { t.Errorf("BYO once checkout sent %s", k) } } // Managed: mode=payment, the admin's share as a fixed transfer computed // from the chosen commission (8% of 11000 โ†’ 10120 transfers). if err := srv.db.setConfig("stripe_express_account", "acct_express"); err != nil { t.Fatal(err) } if err := srv.db.setConfig("managed_commission_pct", "8"); err != nil { t.Fatal(err) } stripe, capturedP, _ := fakePlatformStripe(t) pp := &stripePlatform{db: srv.db, key: "sk", webhookSecret: "whsec_platform", apiBase: stripe.URL, client: http.DefaultClient} if _, err := pp.Checkout(context.Background(), CheckoutReq{ TierID: "t1", PriceRef: "price_once", UserRef: "u1", Once: true, AmountMinor: 11000, SuccessURL: "https://s/ok", CancelURL: "https://s/no", }); err != nil { t.Fatal(err) } fp := *capturedP if got := fp.Get("payment_intent_data[transfer_data][amount]"); got != "10120" { t.Errorf("managed transfer = %q, want 10120 (92%% of 11000)", got) } if got := fp.Get("payment_intent_data[transfer_data][destination]"); got != "acct_express" { t.Errorf("managed destination = %q", got) } for k := range fp { if strings.Contains(k, "subscription_data") || strings.Contains(k, "application_fee") { t.Errorf("managed once checkout sent %s", k) } } } func TestLifetimeWebhookParsing(t *testing.T) { _, srv := newTestServer(t) if err := srv.db.setConfig("stripe_express_account", "acct_ours"); err != nil { t.Fatal(err) } p := &stripePlatform{db: srv.db, key: "sk", webhookSecret: "whsec_platform", apiBase: "http://unused", client: http.DefaultClient} // A payment-mode session with our binding โ†’ a lifetime grant on the // payment intent id. session := map[string]any{ "mode": "payment", "customer": "cus1", "payment_intent": "pi_1", "metadata": map[string]any{"user_ref": "u1", "tier_ref": "t1", "space_ref": "acct_ours"}, } body, sig := platformEvent(t, "checkout.session.completed", session) evs, err := p.ParseWebhook(sig, body) if err != nil || len(evs) != 1 { t.Fatalf("grant: %+v, %v", evs, err) } if e := evs[0]; !e.Lifetime || !e.AllowCreate || e.Checkout || e.ExtSub != "pi_1" { t.Errorf("grant = %+v, want lifetime AllowCreate on pi_1, never a receipt", e) } // A donate/enterprise-shaped payment session (no binding): dropped. stray := map[string]any{"mode": "payment", "payment_intent": "pi_x", "metadata": map[string]any{"donation": "1"}} body, sig = platformEvent(t, "checkout.session.completed", stray) if evs, err := p.ParseWebhook(sig, body); err != nil || evs != nil { t.Fatalf("stray payment session must drop: %+v, %v", evs, err) } // charge.refunded with our stamped metadata โ†’ the un-buy event. refund := map[string]any{ "payment_intent": "pi_1", "metadata": map[string]any{"user_ref": "u1", "tier_ref": "t1", "space_ref": "acct_ours"}, } body, sig = platformEvent(t, "charge.refunded", refund) evs, err = p.ParseWebhook(sig, body) if err != nil || len(evs) != 1 || !evs[0].Deleted || !evs[0].Lifetime || evs[0].ExtSub != "pi_1" { t.Fatalf("refund: %+v, %v", evs, err) } // A refund on some other space's charge: dropped. foreign := map[string]any{ "payment_intent": "pi_2", "metadata": map[string]any{"tier_ref": "t9", "space_ref": "acct_theirs"}, } body, sig = platformEvent(t, "charge.refunded", foreign) if evs, err := p.ParseWebhook(sig, body); err != nil || evs != nil { t.Fatalf("foreign refund must drop: %+v, %v", evs, err) } // The BYO provider: same shapes, bound by connected account instead. if err := srv.db.setConfig("stripe_connect_account", "acct_admin"); err != nil { t.Fatal(err) } pc := &stripeConnect{db: srv.db, key: "sk", webhookSecret: "whsec_connect", apiBase: "http://unused", client: http.DefaultClient} connectEvent := func(evType string, object map[string]any) ([]byte, string) { raw, _ := json.Marshal(map[string]any{ "type": evType, "account": "acct_admin", "created": 1000, "data": map[string]any{"object": object}, }) return raw, stripeSign(raw, "whsec_connect", time.Now()) } sess := map[string]any{ "mode": "payment", "customer": "cus1", "payment_intent": "pi_c1", "metadata": map[string]any{"user_ref": "u1", "tier_ref": "t1"}, } body, sig = connectEvent("checkout.session.completed", sess) evs, err = pc.ParseWebhook(sig, body) if err != nil || len(evs) != 1 || !evs[0].Lifetime || evs[0].ExtSub != "pi_c1" { t.Fatalf("BYO grant: %+v, %v", evs, err) } body, sig = connectEvent("charge.refunded", map[string]any{ "payment_intent": "pi_c1", "metadata": map[string]any{"tier_ref": "t1", "user_ref": "u1"}, }) evs, err = pc.ParseWebhook(sig, body) if err != nil || len(evs) != 1 || !evs[0].Deleted || !evs[0].Lifetime { t.Fatalf("BYO refund: %+v, %v", evs, err) } } func TestLifetimePriceFloorAndMemberGuards(t *testing.T) { ts, srv := newTestServer(t) srv.pay = &fakePay{checkoutURL: "https://pay.example/checkout"} mkUser := func(label string) (string, string) { u, err := srv.db.createUser(label) if err != nil { t.Fatal(err) } if err := srv.db.createKeyInvite("inv-"+label, u, "wrap", testClaimHash, 0); err != nil { t.Fatal(err) } tk, ok, err := srv.db.claimKeyInvite("inv-"+label, label, "", testClaimSecret) if err != nil || !ok { t.Fatalf("claim: ok=%v err=%v", ok, err) } if err := srv.db.setUserPublicKey(u, "pub-"+label, ""); err != nil { t.Fatal(err) } return tk, u } admin, uidAdmin := mkUser("Root") if err := srv.db.setUserRole(uidAdmin, "admin"); err != nil { t.Fatal(err) } tok, uid := mkUser("Bea") tid, err := srv.db.createTier("Founder") if err != nil { t.Fatal(err) } post := func(path string, body map[string]any) int { raw, _ := json.Marshal(body) resp, err := http.Post(ts.URL+path, "application/json", strings.NewReader(string(raw))) if err != nil { t.Fatal(err) } resp.Body.Close() return resp.StatusCode } // The โ‚ฌ11 floor on lifetime pricing; a valid once price sticks. if code := post(fmt.Sprintf("/api/tiers/%s/price", tid), map[string]any{ "token": admin, "cents": 500, "currency": "eur", "interval": "once"}); code != http.StatusBadRequest { t.Errorf("โ‚ฌ5 lifetime accepted: %d", code) } if code := post(fmt.Sprintf("/api/tiers/%s/price", tid), map[string]any{ "token": admin, "cents": 11000, "currency": "eur", "interval": "once", "provider_ref": "price_once"}); code != http.StatusOK { t.Errorf("โ‚ฌ110 lifetime rejected: %d", code) } // A lifetime holder gets honest answers from the renewal-shaped verbs. now := time.Now().Unix() if err := srv.applyWebhookEvent(context.Background(), lifetimeGrant(uid, tid, now), now); err != nil { t.Fatal(err) } for _, path := range []string{"cancel-subscription", "resume", "portal"} { if code := post(fmt.Sprintf("/api/tiers/%s/%s", tid, path), map[string]any{"token": tok}); code != http.StatusBadRequest { t.Errorf("%s on a lifetime purchase = %d, want 400", path, code) } } } // TestOnceFeeMatchesPercent pins feePermille to applicationFeePercent โ€” the // string feeds Stripe's subscription form, the integer feeds payment-mode // money math, and they must describe the same rate. func TestOnceFeeMatchesPercent(t *testing.T) { pct, err := strconv.ParseFloat(applicationFeePercent, 64) if err != nil { t.Fatalf("applicationFeePercent %q: %v", applicationFeePercent, err) } if got := float64(feePermille) / 10; got != pct { t.Errorf("feePermille %d = %.2f%%, applicationFeePercent = %s", feePermille, got, applicationFeePercent) } // Floor rounding favours the admin: 5.5% of 999 is 54.945 โ†’ 54. if got := onceFeeMinor(999); got != 54 { t.Errorf("onceFeeMinor(999) = %d, want 54", got) } }