Commit Graph
3531 Commits
Author SHA1 Message Date
erio 017f23bfa3 chore: bump version to 0.1.110.2 2026-04-11 15:46:43 +08:00
erio 576c34bbf7 fix(payment): add active upstream sync to polling and fix Stripe countdown
- Add POST /payment/orders/:id/sync endpoint that queries upstream
  provider on each poll, complementing webhooks for timely payment
  detection when webhooks are delayed or unreachable
- Fix Stripe payment countdown: pass expires_at to PaymentStatusPanel
  instead of empty string (was falling back to hardcoded 30 minutes)
- Idempotent: toPaid uses atomic UPDATE WHERE status=PENDING, so
  concurrent webhook + sync calls won't double-credit
2026-04-11 15:46:28 +08:00
erio 75deeb6e17 docs: update CLAUDE.md with correct PR and release workflows
- Release flow: start from our release branch, merge upstream in (not reverse)
- PR flow: base on upstream/main, cherry-pick from release, never merge PR into release
- Add forbidden files list, PR checklist, reverse sync instructions
- Add feat/* branch to branch strategy table
2026-04-11 14:50:07 +08:00
erio b3d4ea5aa6 chore: bump version to 0.1.110.1 2026-04-11 14:49:36 +08:00
erio b5b5e35757 refactor(payment): code standards fixes and file organization
Cherry-picked from PR branch (feat/payment-system-v2).
- Use payment.Type* constants instead of magic strings in factory
- Add wxpay success response constants
- Add validity unit constants (week/month)
- Add constants for easypay popup mode
- Split payment_order.go into payment_order_lifecycle.go
- Extract shared order utilities to orderUtils.ts
- Improve admin order components to use shared utilities
- Removed duplicate migration (097 already exists)
2026-04-11 13:24:17 +08:00
erio f4e4b1539b feat(payment): add H5/mobile payment support
Cherry-picked from PR branch (feat/payment-system-v2).
- EasyPay: parse payurl2 for H5 mobile links, prefer on mobile
- EasyPay: add device=mobile for popup mode on mobile
- Backend: expand isMobile() to detect iPad/iPod
- Frontend: auto-redirect on mobile instead of popup
- Frontend: fallback to redirect when popup blocked
- Stripe: use mobile_web client for WeChat Pay on mobile
- StripePopup: typed interface, extractApiErrorMessage
2026-04-11 13:22:12 +08:00
erio 4a3383ecdd fix(payment): widen popup window to 1000px for Alipay+ checkout
Alipay+ checkout page needs ~960px to display QR code and order details
side by side. Previous 680px forced users to manually resize.
2026-04-10 16:45:47 +08:00
erio 9d4b02688f Revert "fix(payment): restore decodeURLValue in EasyPay webhook verification"
This reverts commit 1430c82078.
2026-04-10 16:43:21 +08:00
erio 1430c82078 fix(payment): restore decodeURLValue in EasyPay webhook verification
The db139191 audit incorrectly removed the second URL decode pass.
EasyPay providers send values that need decoding after url.ParseQuery,
causing signature verification to fail on .147+.
Production .146 worked because it still had decodeURLValue.
2026-04-10 16:40:42 +08:00
erio 7547ea9f4e fix(payment): include RECHARGING in result page success states
The PaymentResultView only checked COMPLETED and PAID status, but orders
in RECHARGING state (balance being applied after payment) were incorrectly
shown as failed.
2026-04-10 16:23:23 +08:00
erio 0113bf60fd feat(payment): renewal modal, clean confirm card, platform color refresh
- Replace gradient header in subscription confirm with clean card layout
  matching sub2apipay design (platform accent text instead of full gradient)
- Add renewal plan selection modal: when group has multiple plans show
  picker, single plan skips directly to payment method selection
- Restyle SubscriptionsView with platform-specific colors (badge, border,
  button) instead of hardcoded purple
- Update platformColors to match sub2apipay style (transparent badges,
  subtle borders with /20 opacity)
2026-04-10 16:12:28 +08:00
erio 9df96a45c7 feat(payment): redesign Stripe popup to match sub2apipay clean card style
Replace purple gradient header with clean white card layout, method-specific
colored amount text and spinner, larger card and spinner sizes.
2026-04-10 15:50:28 +08:00
erio 8bf91c0915 revert: remove double URL decode, provider-side encoding issue 2026-04-10 14:34:50 +08:00
erio 9b38e44e1d fix(payment): restore double URL decode for EasyPay webhook values
EasyPay callbacks arrive with double-encoded query values (e.g. %25E5 instead
of %E5) due to redirect chains. url.ParseQuery decodes once; decodeURLValue
applies a second safe decode so the sign matches what EasyPay computed.
Also removes temporary debug logging.
2026-04-10 14:27:44 +08:00
erio 72f7677815 debug: log webhook raw body and sign comparison 2026-04-10 14:17:39 +08:00
erio 7ea6cafadb debug: add webhook provider lookup logging 2026-04-10 14:10:37 +08:00
erio 0f32416d5d chore: bump version to 0.1.108.148 2026-04-10 14:04:19 +08:00
erio d642a16f79 fix(payment): webhook uses order's provider instance for signature verification
When multiple provider instances exist (e.g. 3 EasyPay accounts), the webhook
handler now extracts out_trade_no from the callback, looks up the order, and
uses the order's original provider instance for verification instead of picking
an arbitrary instance from the registry.
2026-04-10 14:03:59 +08:00
erio a92c0eabbc chore: bump version to 0.1.108.147 2026-04-10 10:42:30 +08:00
erio cd1bbebb77 feat(payment): subscription renewal UI, payment button colors, QR branding, refund day deduction
- Add btn-alipay/btn-wxpay CSS classes; confirm button color follows payment method
- Subscription confirm header uses platform gradient (Anthropic orange, Gemini blue, etc.)
- Subscription confirm page shows plan details (rate, limits, validity)
- SubscriptionPlanCard: show "Renew" button when user has active subscription
- SubscriptionsView: add renewal button on active subscription cards
- QR code display: brand-color border + center logo overlay (Alipay blue, WeChat green)
- StripePaymentView: WeChat QR green border + logo, Alipay spinner brand color
- Backend: fix subscription refund to deduct days (ExtendSubscription -days or Revoke)
- Backend: rollback subscription days on gateway failure
2026-04-10 10:42:06 +08:00
erio db139191e3 fix(payment): critical audit fixes for security, idempotency and correctness
Backend fixes:
- #1: doSub subscription idempotency via audit log check
- #2: markFailed only when status=RECHARGING (prevents overwriting COMPLETED)
- #3: ExpireTimedOutOrders checks upstream payment before expiring
- #4: Public verify endpoint for payment result page (no auth required)
- #5: EasyPay QueryOrder returns amount, confirmPayment handles zero amount
- #6: WxPay notifyUrl priority: request-first, config-fallback
- #7: EasyPay remove double URL decode in VerifyNotification
- #8: checkPaid/cancelUpstreamPayment use order's provider instance
- #9: Amount NaN/Inf/negative validation in order creation and refund
- #10: Refund amount comparison uses tolerance instead of float64 ==
- #11: Skip balance deduction on retry when previous rollback failed
- #12: checkPaid logs fulfillment errors instead of silently ignoring
- #13: WxPay certSerial added to required config fields

Frontend fixes:
- Payment result page no longer requires authentication
- Public verify API fallback for expired sessions
2026-04-10 02:23:19 +08:00
erio 72b77306da fix(admin): reload provider list after save to get correct data format
UpdateProvider API returns raw DB entity (encrypted config, string types),
but frontend needs ProviderInstanceResponse (decrypted, array types).
Reload full list after save to ensure correct data and sort order.
2026-04-10 01:28:54 +08:00
erio e45c774ab9 fix(payment): refresh provider registry on config changes
- Call RefreshProviders after Create/Update/Delete provider instance
- Call RefreshProviders after saving payment settings
- Auto-save settings when provider dialog saves
- Fixes webhook signature verification using stale pkey
2026-04-10 00:50:12 +08:00
erio 087cd72371 style(admin): widen time unit select, shorten cancel limit label 2026-04-10 00:19:32 +08:00
erio 7617ed56d3 style(admin): cancel rate limit inline with toggle, greyed when off 2026-04-10 00:13:16 +08:00
erio 83643f2dde style(admin): cancel rate limit as toggle switch, config in single row 2026-04-10 00:03:54 +08:00
erio a6728b9b69 style(admin): move cancel rate limit checkbox inline with pending orders row 2026-04-09 23:58:52 +08:00
erio 62e9453ddc refactor(frontend): extract shared OrderTable component
- Create OrderTable.vue with shared cell rendering (ID, order number,
  amount, payment method, status badge, created time)
- Accepts showUser prop to conditionally show user_email column
- Actions column uses scoped slot for view-specific buttons
- UserOrdersView and AdminOrdersView both use OrderTable
- Removes duplicated DataTable cell templates from both views

chore: bump version to 0.1.108.144
2026-04-09 23:49:47 +08:00
erio 753a87dee3 feat(admin): add user info and keyword search to admin order list
Backend:
- Add Keyword field to OrderListParams
- AdminListOrders supports keyword search on out_trade_no, user_email, user_name
- PaymentOrder already has user_email/user_name/user_notes fields (no join needed)

Frontend:
- Replace inline status badge with OrderStatusBadge component
- Replace user_id column with user_email (shows email, fallback to username, with notes)
- Keyword search matches order number and user info

chore: bump version to 0.1.108.143
2026-04-09 23:41:59 +08:00
erio e9aa1b1330 fix(payment): use order's original provider instance for refund
Previously gwRefund used registry.GetProvider(paymentType) which returns
an arbitrary instance for that type. When multiple instances share the
same payment type (e.g., two EasyPay merchants both supporting alipay),
the refund would be sent to the wrong merchant.

Now getRefundProvider() reads the order's ProviderInstanceID, loads that
instance's config, and creates the correct provider. Falls back to
registry lookup for legacy orders without an instance ID.

chore: bump version to 0.1.108.142
2026-04-09 23:21:38 +08:00
erio 69f885eb5f style: fix gofmt formatting in payment_handler.go 2026-04-09 22:52:11 +08:00
erio fdc9835c52 fix(ci): update Go version check to 1.26.2 in CI workflows 2026-04-09 22:43:14 +08:00
erio 63c4172dd9 fix(ci): upgrade Go to 1.26.2 for security fixes, fix flaky WS test
- Upgrade Go from 1.26.1 to 1.26.2 in go.mod and Dockerfile to fix
  govulncheck findings (GO-2026-4947, GO-2026-4946, GO-2026-4870, etc.)
- Fix flaky PassthroughModeRelaysByCaddyAdapter test: tolerate
  StatusNormalClosure error from server after client closes connection
2026-04-09 22:38:39 +08:00
erio e25e7b3f2d fix(admin): fix refund dialog amount for REFUND_REQUESTED, improve button styles
- Don't treat refund_amount as "already refunded" in REFUND_REQUESTED status
  (it's the requested amount, not actually refunded)
- Pre-fill refund amount with user's requested amount
- Show "already refunded" only for PARTIALLY_REFUNDED/REFUNDED orders
- Change action buttons from stacked icon+text to compact inline style
2026-04-09 22:07:06 +08:00
erio 66cd16fce3 fix: remove unused canRefund, fix TS type assertions 2026-04-09 21:49:05 +08:00
erio cc858c3cc9 feat(admin): enhance refund workflow to match sub2apipay
Admin order list:
- Add REFUND_REQUESTED "approve refund" button with amount badge
- Add REFUND_FAILED "retry refund" button
- Add missing status filters (REFUND_REQUESTED, REFUND_FAILED)
- Order detail dialog: show refund request info + audit logs

Admin refund dialog:
- Show refund request info card (violet) when user requested
- Display user balance with insufficient balance warning
- Add "no deduction" info when deduct_balance unchecked
- Add force refund checkbox (shown when requireForce=true)
- Add warning display prop
- Pre-fill reason from user's refund request
- Default deduct_balance to true

chore: bump version to 0.1.108.141
2026-04-09 21:47:39 +08:00
erio ceee2d0584 fix: audit fixes - magic strings to constants, frontend any/catch, LB tests
Backend:
- Define OrderTypeBalance/Subscription, EntityStatusActive, DeductionType*,
  NotificationStatus* constants in payment/types.go
- Replace all magic strings in payment_order, payment_fulfillment, payment_refund
- Add local constants in easypay.go (tradeStatusSuccess, signTypeMD5)
- Add 27 unit tests for load balancer (filterByLimits, pickLeastAmount,
  getInstanceChannelLimits, startOfDay)

Frontend:
- Remove all `any` types in SettingsView.vue (18 catch blocks + 1 payload)
- Fix bare catch blocks in PaymentResultView, PaymentView
- Add `unknown` type annotation to all catch blocks

chore: bump version to 0.1.108.140
2026-04-09 21:29:49 +08:00
erio 44a9d91c8f refactor(payment): rewrite load balancer with complete limit checking
- Batch-query daily usage in one SQL (GroupBy) instead of N queries
- Include PENDING orders in daily usage to prevent over-committing
- Check remaining capacity (used + orderAmount > limit) not just used >= limit
- Check SingleMin/SingleMax per-channel limits
- Pre-attach usage to candidates, reuse across filter and strategy pick
- Fallback to full list with warn log when all instances exhausted

chore: bump version to 0.1.108.139
2026-04-09 21:20:13 +08:00
erio 8808910ee7 fix(frontend): unify currency to $ and add order number to admin orders
- Replace all ¥ / ¥ with $ across all payment views and components
- Add out_trade_no column to admin order list table
- Add order number field to admin order detail dialog

chore: bump version to 0.1.108.138
2026-04-09 21:14:57 +08:00
erio e5dd87743f fix(payment): filter instances by remaining capacity and single amount range
Check used + orderAmount > dailyLimit (not just used >= limit) and
also filter by SingleMin/SingleMax per-channel limits.

chore: bump version to 0.1.108.137
2026-04-09 19:33:36 +08:00
erio fc99a88fee fix(payment): implement least-amount load balance strategy
Previously SelectInstance always used round-robin regardless of the
configured strategy. Now it reads the strategy from payment config
and selects the instance with the lowest daily transaction amount
when "least-amount" is configured.

chore: bump version to 0.1.108.136
2026-04-09 19:20:20 +08:00
erio b25d7f3179 fix(frontend): restore RechargeSubscriptionIcon for purchase menu
chore: bump version to 0.1.108.135
2026-04-09 19:01:34 +08:00
erio 766f9fc590 fix: cast platform to string to fix TS2345 2026-04-09 18:48:42 +08:00
erio 05ce6d6bc0 feat(admin): add platform colors to plan edit group selector
chore: bump version to 0.1.108.134
2026-04-09 18:46:58 +08:00
erio acb86102f4 feat(admin): color plan names by group platform
chore: bump version to 0.1.108.133
2026-04-09 18:33:36 +08:00
erio 0c00a883a3 chore: bump version to 0.1.108.132 2026-04-09 18:19:32 +08:00
erio 4bacf47e45 fix(admin): only show subscription-type groups in plan group selector 2026-04-09 18:19:31 +08:00
erio 3d547d85cb chore: bump version to 0.1.108.131 2026-04-09 18:04:04 +08:00
erio 3ce57cade8 fix(payment): use Stripe built-in QR dialog for WeChat Pay
Remove handleActions:false and custom QR rendering. Let Stripe's
native QR dialog handle WeChat Pay display in the popup window.
2026-04-09 18:04:04 +08:00
erio 5c17718633 chore: bump version to 0.1.108.130 2026-04-09 17:49:09 +08:00