Creem's official packages¶
Context and Problem Statement¶
The Creem module (backend/payments/creem.ts, ported from notefeed) was written by hand: its own HTTP calls, its own signature check, its own reading of each delivery. Creem publishes packages for exactly this, and notefeed and Gamepanion would otherwise each keep a copy of the same code (#107).
Considered Options¶
- Creem's official packages where they are good, keeping only the mapping to Gamepanion's records.
- All of them, the Next.js adapter (
@creem_io/nextjs) as the webhook route too. - A payments library of our own, shared by notefeed and Gamepanion.
- Keep the hand-written module.
Decision Outcome¶
Chosen: the official packages where they are good (all MIT): the SDK, the delivery types and the embed. They cover the API, the signature and typed deliveries; what is left is a few lines per app.
creem(the SDK,backend/payments/creem-api.ts): plans (kept five minutes, the last list while Creem is down), checkout, the customer portal and cancel; test or live from the key's prefix.@creem_io/webhook-types(backend/payments/creem-deliveries.ts):generateSignaturefor the signature over the raw body, compared in constant time;parseWebhookEventand its types for the delivery; a switch over the kinds into Gamepanion's own events. The webhook stays the backend'sparseWebhook, behind the same interface as the fake.@creem_io/embed: the checkout opens as an overlay on/subscribe. The webhook alone decides access; the success page (/subscribe?checkout_id=…) works without JavaScript.
| Delivery | What Gamepanion does |
|---|---|
checkout.completed |
started: who paid (metadata.player_id), the tier from the product, paid-until |
subscription.active, subscription.paid |
renewed: paid-until moves on |
subscription.canceled, subscription.scheduled_cancel |
cancelled: access stays until paid-until (Creem sends canceled at once when someone cancels in the portal) |
subscription.expired, subscription.paused, subscription.unpaid |
ended |
refund.created, dispute.created |
refunded: ends at once, and the subscription is cancelled at Creem |
| anything else | nothing |
Not @creem_io/nextjs (decided 2026-11-04, after trying it in #110): it is some thirty lines around the two functions above, and those lines write every delivery to the console (a completed checkout with the buyer's name and address), compare the signature with !==, answer every error in our callbacks with a bare 500, and import next/server in a way only Next resolves. Its onGrantAccess and onRevokeAccess don't fit either: the first doesn't fire for checkout.completed, the second fires for canceled, where access must stay until paid-until.
Consequences¶
- Good, because the API calls, the signature and the delivery types are Creem's to keep right, and
backend/stays framework-free. - Good, because no delivery reaches a log.
- Bad, because the SDK checks answers strictly: an answer missing a field it requires fails the call. The sandbox shows whether Creem's answers fit.
- Bad, because the SDK is large (26 MB installed; the standalone build traces only what is used).