Repository navigation
Conversation
Add Creem (Merchant of Record) as a payment provider alongside Stripe and Airwallex. Balance top-ups create a Creem checkout on a configured one-time product with custom_price set to the order amount, and redirect the user to the hosted checkout. - Provider: checkout creation, order query, webhook verification (Standard Webhooks and legacy creem-signature), full refunds with an idempotent already-refunded check, product validation (one-time, inclusive tax, matching USD/EUR currency). - Late payments: Creem checkout links cannot be cancelled when an order expires, so a payment that arrives after the expiry grace period is now refunded automatically via the new payment.LatePaymentRefunder hook instead of being left uncredited. Other providers are unaffected. - Admin settings, payment method selector, i18n (en/zh) and docs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Thank you for your contribution! Before we can merge this PR, we need you to sign our Contributor License Agreement (CLA). To sign, please reply with the following comment:
You only need to sign once — it will be valid for all your future contributions to this project. I have read the CLA Document and I hereby sign the CLA Kelly Chan seems not to be a GitHub user. You need a GitHub account to be able to sign the CLA. If you have already a GitHub account, please add the email address used for this commit to your account. |
|
Superseded: Creem was rebuilt on the latest upstream ( |
Problem
Sub2API can take payments through Stripe, Airwallex and the Chinese channels, but not Creem. Creem is a Merchant of Record and collects sales tax/VAT for us, which matters for international users.
Fix
creemprovider. Top-up amounts vary, so the admin configures one one-time product in Creem. Each order creates a checkout on that product withcustom_priceset to the order amount (Creem's range is 1.00–999,999.99), and the user is redirected to the Creem hosted checkout. Orders are credited by webhook, or by the existing active order query.webhook-id/-timestamp/-signature, 5-minute tolerance) and legacycreem-signatureHMAC signatures are verified. The provider handlescheckout.completedand ignores everything else with a 200.POST /v1/refundstakes just atransaction_id. Partial refunds are rejected before calling Creem. The refund response has no id, onlystatus, so the transaction id is used as the refund id. Before refunding, the provider checks whether the transaction is already refunded. This makes retries safe after a timeout or rollback; Creem itself returns 400 "no remaining amount" on a second refund.payment.LatePaymentRefunderinterface, implemented only by Creem, now triggers a full refund in that case and writes aLATE_PAYMENT_REFUNDEDaudit entry. If the refund fails, it writesLATE_PAYMENT_REFUND_FAILEDand returns 5xx so Creem retries. Other providers are unchanged.success_urlvalues whose host is an IP (e.g.http://127.0.0.1:8080) and fails the whole checkout. Those are now omitted; the payment still completes and is credited by webhook.docs/PAYMENT*.md(setup, webhook, limits).Verification
Method: local sub2api (Postgres 16 + Redis in throwaway containers) against
test-api.creem.io, paying with test card 4111… in a real (headed, Xvfb) browser. For real webhooks, a temporary CLI-delivery Creem webhook was created and its queued events were forwarded unmodified (Creem's own body andcreem-signatureheader) to the local endpoint. The webhook was deleted afterwards.refund.createdeventrefunded, order REFUNDED, balance deducted; event acknowledged and ignoredgo test -tags=unit ./..., golangci-lint v2.9 (CI version),vue-tsc, payment vitest suitesmain.The late-payment unit tests fail with the fix removed and pass with it.
Known risks / not covered
creem-signatureheaders; the Standard Webhooks path is covered by unit tests only. After deploying, send a test event from the Creem dashboard.LATE_PAYMENT_REFUNDEDaudit row. No second refund is made.Screenshots
Story 1: an admin adds Creem
Story 2: a user tops up $20 with Creem
custom_price)Files Affected
backend/internal/payment/provider/creem.go(+ tests): new providerbackend/internal/payment/types.go,currency.go,provider/factory.go:TypeCreem, Creem currency rules,LatePaymentRefunderinterfacebackend/internal/service/payment_fulfillment.go(+payment_late_payment_refund_test.go): automatic refund of late paymentsbackend/internal/service/payment_{config_providers,currency,order,order_provider_snapshot}.go: config, sensitive fields, snapshot validationbackend/internal/handler/payment_webhook_handler.go,server/routes/payment.go:POST /api/v1/payment/webhook/creemfrontend/src/components/payment/*,views/*,i18n/*,style.css,assets/icons/creem.png: admin and user UIdocs/PAYMENT.md,docs/PAYMENT_CN.md🤖 Generated with Claude Code