Skip to content

Dev#2

Open
liuyulin-1024 wants to merge 13 commits into
mainfrom
dev
Open

Dev#2
liuyulin-1024 wants to merge 13 commits into
mainfrom
dev

Conversation

@liuyulin-1024
Copy link
Copy Markdown
Collaborator

No description provided.

liuyulin-1024 and others added 13 commits April 30, 2026 11:15
Add db field mapping to each parameter in api_test.html, showing the
corresponding database table.column (e.g. payments.merchant_order_no,
subscriptions.plan_id) next to each form field. Also add k8s deployment
manifests for gateway, prod config, and target group binding.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add payment_method and payment_options fields to CreatePaymentRequest,
with schema-level validation (currency whitelist for alipay, client
option for wechat_pay) and Stripe adapter logic to conditionally set
payment_method_options and skip payment_intent_data for wechat_pay.
Fallback to card is now restricted to implicit method scenarios only.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…scriptions

Implement Worker-driven recurring billing lifecycle for payment methods
that lack native subscription support. Includes renewal scanning,
grace period enforcement, reminder re-delivery, and automatic refund
for duplicate/race-condition payments.

Key fixes applied during review:
- Keep SubscriptionResponse.plan as required field with fallback
- Auto-refund on idempotent renewal skip (prevents silent money loss)
- Deterministic webhook event_id with attempt suffix for reminders
- Grace period base uses max(period_end, now) to prevent shrinkage
- Fix hmac.new → hmac.HMAC for webhook signature

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The python-dateutil dependency was added to pyproject.toml but the lock
file was not regenerated, causing ModuleNotFoundError in Docker builds.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…payloads

Add payment_method field to subscription webhook and checkout_expires_at
to renewal webhooks for downstream consumers. Reorganize k8s configs into
dev/prod subdirectories and add dev environment deployment manifests.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…lnerabilities

- mako 1.3.10 → 1.3.12 (CVE-2026-44307, CVE-2026-41205 path traversal)
- python-dotenv 1.2.1 → 1.2.2 (CVE-2026-28684 symlink following)
- requests 2.32.5 → 2.33.1 (CVE-2026-25645 insecure temp file)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Stripe wechat_pay only accepts CNY/HKD as Checkout Session currency, but
hub had no upfront guard — sending a USD plan to /v1/subscriptions with
payment_method=wechat_pay reached Stripe and bubbled up an opaque 4xx.
Mirror the existing alipay validation: reject the request at the schema
level (CreatePaymentRequest) and at the service level
(SubscriptionService.create_subscription) with code 4006 so callers see a
deterministic error and never hit the gateway.

- Add WECHAT_PAY_SUPPORTED_CURRENCIES = {"CNY", "HKD"} alongside the
  existing alipay set in core.constants.
- Plumb the new validator into both code paths (Pydantic + service).
- Realign two existing tests that fixtured a default USD plan with a
  wechat_pay payment method (would now trip the guard).
…ovider

Two related gaps in the email lifecycle of subscription checkout:

1. App-managed checkout (alipay / wechat_pay) skips Stripe Customer
   binding and uses mode=payment Sessions. Stripe pre-fills the email
   field from session.customer_email, but SubscriptionService never put
   the user-supplied email there — the hosted page always rendered an
   empty input. Forward req.email through metadata.customer_email so
   StripeAdapter.create_payment can populate the field. We omit the key
   entirely when req.email is missing to keep metadata minimal.

2. When an existing Customer subscribes again with a changed email, the
   gateway only updated the local row; the upstream Stripe Customer
   stayed pinned to the old address, so card-flow Checkout kept
   pre-filling the stale email. Add a non-fatal call to
   adapter.update_customer_email so the change propagates. Failure
   downgrades to a warn log and the local row still wins, so the next
   subscribe will retry the sync.

Adds an optional update_customer_email to SubscriptionProviderMixin
(default raises NotImplementedError) and a Stripe.Customer.modify-based
implementation in StripeAdapter. Five regression tests cover the
metadata propagation and the three sync paths (changed / unchanged /
provider-failure).
Follow-up review of the previous email-prefill commit found four issues
all related to how req.email and customer.email flow into Stripe Checkout.

1. _get_or_create_customer's failure path was self-contradicting: the
   comment promised "next subscribe will retry", but customer.email was
   reassigned outside the try block, so a transient provider failure
   would freeze the local row on the new email and make every subsequent
   call short-circuit the diff check. Move the local assignment into the
   success path; only the explicit NotImplementedError branch (provider
   can never sync) advances the local row, so retry actually retries.

2. RenewalService skipped the same prefill step entirely. App-managed
   subscriptions go through _create_renewal_for_subscription /
   _send_reminder_for_subscription, both of which call
   adapter.create_payment without forwarding customer.email — so first
   subscribe got a pre-filled email but every renewal Checkout was
   blank. Fetch the Customer row once per call and inject customer_email
   into the renewal metadata.

3. StripeAdapter.create_payment was copying the entire metadata dict
   (now including the new customer_email key) into the Stripe Session's
   metadata field and into payment_intent_data.metadata. Stripe's own
   guidance is to keep PII out of metadata; the customer_email belongs
   only in the dedicated session_data["customer_email"] prefill field.
   Strip the key before composing session_metadata.

4. NotImplementedError from update_customer_email was silently passed,
   making future provider additions invisible. Demote to a structured
   info log with provider + customer_id so it shows up in observability.

Test changes:
- Rewrite test_existing_customer_provider_sync_failure_keeps_old_email
  to assert the local email stays on the old value (the previous
  assertion locked the buggy "always advance" behavior into the spec).
- Add test_existing_customer_not_implemented_advances_local_email to
  cover the explicit NotImplementedError fast-path.
- Add test_stripe_create_payment_keeps_customer_email_out_of_metadata
  for the PII isolation contract.
- Add test_renewal_payment_forwards_customer_email_for_prefill to lock
  in the renewal-side prefill.

Verified end-to-end against the local container: alipay subscribe still
returns customer_email on the Stripe Session and metadata no longer
leaks the email field.
…essions

Expand wechat_pay supported currencies to match Stripe's full list. When
currency is not CNY, set adaptive_pricing.enabled=true so Stripe shows
local currency on Checkout. Log currency_conversion events from webhooks
for reconciliation auditing.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…for adaptive pricing check

Replace hardcoded string tuple with existing constant and raw string
comparison with Currency enum to improve type safety and maintainability.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…orted

Stripe test mode only accepts CNY/HKD for wechat_pay sessions. When the
caller-supplied currency is rejected with a currency-related
InvalidRequestError, retry the session creation with HKD (and drop
adaptive_pricing, which is incompatible with the manual currency switch).
…ok forwarding

Extend payment gateway to support the ops platform's campaign system:
- Add Coupon and PromotionCode SQLAlchemy models with Alembic migration
- Add internal CRUD endpoints (/internal/v1/coupons, promotion-codes, plans, subscriptions, refunds)
- Extract promotion_code_id from checkout.session.completed events (new + legacy path fallback)
- Forward coupon.redeemed and coupon.refunded webhooks to ops-server via WebhookDelivery
- Store promotion_code_id in subscription metadata for refund correlation
- Add per-App max_delivery_retries override for webhook retry configuration
- Pass allow_promotion_codes through to Stripe Checkout session creation

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant