1. 24 Apr, 2026 1 commit
  2. 23 Apr, 2026 1 commit
    • erio's avatar
      fix(payment): ack unknown-order webhooks with 2xx to stop provider retries · 75e1b40f
      erio authored
      Introduce a sentinel ErrOrderNotFound in the payment service layer so the
      webhook handler can distinguish "the out_trade_no does not exist in our DB"
      from other fulfillment failures, and downgrade the former to a WARN log +
      success response.
      
      Background
      - Providers (Stripe, Alipay, Wxpay, EasyPay, ...) retry webhooks whenever
        we answer non-2xx. When a webhook endpoint is misconfigured (e.g. a
        foreign environment points at us) or our orders table has been wiped,
        we return 500 forever and the provider retries for days, spamming logs.
      - The old code also collapsed "order not found" and "DB query failed" into
        the same branch — a DB blip would be reported as "order not found" and
        swallowed.
      
      Service layer (payment_fulfillment.go)
      - Add `var ErrOrderNotFound = errors.New("payment order not found")`.
      - In HandlePaymentNotification, distinguish the two error paths:
        * dbent.IsNotFound(err) → wrap with ErrOrderNotFound so callers can
          errors.Is(...) it.
        * anything else → wrap the original err with %w so it still bubbles up
          as 500 and the provider retries (DB hiccup should be retried).
      
      Handler layer (payment_webhook_handler.go)
      - Before returning 500, check errors.Is(err, service.ErrOrderNotFound):
        emit a WARN (with provider / outTradeNo / tradeNo for discoverability),
        then call writeSuccessResponse so the provider sees its expected 2xx
        body (Stripe empty body / Wxpay JSON / others "success").
      - Other errors retain the existing 500 behavior.
      
      Monitoring note: because this path now swallows unknown-order webhooks
      silently from the provider's perspective, the WARN log line is the only
      signal. Alert on "unknown order, acking to stop retries" if you want
      visibility into misrouted webhooks or accidental data loss.
      75e1b40f
  3. 22 Apr, 2026 1 commit
  4. 21 Apr, 2026 4 commits
  5. 20 Apr, 2026 2 commits
  6. 14 Apr, 2026 6 commits
    • erio's avatar
      feat(payment): balance recharge multiplier and refund amount separation · 60a4b931
      erio authored
      - Add balance_recharge_multiplier system setting (e.g. 1.2 = charge 100 get 120)
      - Separate order_amount (credited balance) from pay_amount (actual payment)
      - Refund calculates gateway amount proportionally from pay_amount
      - Frontend shows both amounts in order details, payment status, refund dialog
      - Admin settings UI for configuring recharge multiplier
      60a4b931
    • erio's avatar
      fix: resolve cherry-pick conflicts and restore compilation · d6965b06
      erio authored
      - Restore gateway_cache.go to upstream (no lua embeds)
      - Restore payment_order.go to upstream (use out_trade_no lookup)
      - Restore payment_fulfillment.go to upstream (same reason)
      - Add FeaturesConfig field and IsWebSearchEmulationEnabled to Channel
      - Add applyAccountStatsCost wrapper function
      - Add SettingKeyWebSearchEmulationConfig constant
      - Add WebSearchEmulationEnabled to SystemSettings
      - Add notify code rate limiting methods to EmailCache interface
      - Remove AllowUserRefund references (ent schema not present)
      - Fix duplicate import in payment_handler.go
      - Fix wire_gen.go argument mismatches
      d6965b06
    • erio's avatar
      fix(payment): critical audit fixes for security, idempotency and correctness · c738cfec
      erio authored
      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
      c738cfec
    • erio's avatar
      fix: audit fixes - magic strings to constants, frontend any/catch, LB tests · 56e4a9a9
      erio authored
      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
      56e4a9a9
    • erio's avatar
      test(payment): add unit tests for payment audit fixes + allow empty supported_types · 3c884f8e
      erio authored
      Tests (1033 new lines, 100% coverage on modified functions):
      - amount.go: YuanToFen/FenToYuan with precision edge cases
      - wxpay: mapWxState, wxSV, formatPEM, NewWxpay validation
      - alipay: isTradeNotExist, NewAlipay validation
      - webhook: writeSuccessResponse (wxpay JSON, stripe empty, others text)
      - config: validateProviderRequest, isSensitiveConfigField, joinTypes
      - fulfillment: resolveRedeemAction idempotency logic
      
      Business logic changes:
      - Allow empty supported_types on provider instances
      - Block removing payment types when instance has pending orders
      - Extract resolveRedeemAction as testable pure function
      3c884f8e
    • erio's avatar
      fix(payment): audit fixes for alipay/wxpay/stripe payment providers · 5bae3b05
      erio authored
      Backend:
      - Extract YuanToFen/FenToYuan to payment/amount.go using shopspring/decimal
      - Require alipay publicKey in config validation
      - Fix wxpay webhook response to return JSON per V3 spec
      - Remove wxpay certSerial fallback to publicKeyId
      - Define magic strings as named constants in wxpay/alipay providers
      - Add slog warning for wxpay H5→Native payment downgrade
      - Make EncryptionKey validation return error on invalid (non-empty) key
      - Make decryptConfig propagate errors instead of returning nil
      - Add idempotency check in doBalance to prevent stuck FAILED retries
      
      Frontend:
      - Fix dashboard currency symbol from $ to ¥
      - Fix AdminPaymentPlansView any type to proper SubscriptionPlan type
      - Make quick amount buttons follow selected payment method limits
      - Center help image with larger height and text below
      5bae3b05
  7. 11 Apr, 2026 1 commit