Skip to content

Conversation

@littlewrite
Copy link
Contributor

@littlewrite littlewrite commented Sep 16, 2025

通过creem的产品购买功能,实现充值功能。

在支付设置新增creem配置,支持: screet , sign screet, test mode 选项,以及产品配置。

通过产品Id, 展示价格,amout 定义产品,并展示在 个人中心。

当用户点击 产品,会创建订单到 topup,通过在creem预定的product id,唤起creem 支付页面,

用户支付后,回调new-api 地址: https://my.new-api/api/creem/webhook 在回调中验证请求。

请求验证后,通过 callback中的 订单id 找到 topup 的未完成订单, 按照订单中设置 amout 值 加到 用户的 quote 上。

Summary by CodeRabbit

  • New Features
    • Added Creem payment gateway: hosted checkout for user top-ups, webhook-driven finalization, and endpoints to initiate payments and receive events.
    • API now exposes Creem-enabled flag and configured Creem products.
  • UI / Localization
    • New admin settings UI for Creem (API key, webhook secret, test mode) with product management (add/edit/delete, price, currency, quota) and added translations.
  • Chores
    • Added Creem configuration options to settings.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 16, 2025

Walkthrough

Adds a Creem payment integration: backend controller and webhook handlers, a Creem adaptor and checkout flow, a model RechargeCreem function, new settings and option keys, API routes for payment and webhook, top-up response exposure, and an admin web UI plus i18n entries for Creem configuration.

Changes

Cohort / File(s) Summary
Controller: Creem payment integration
controller/topup_creem.go
New Creem adaptor, request handler, checkout link generation, webhook endpoint, event parsing, and completion handler that finalizes top-ups via model.RechargeCreem. Adds PaymentMethodCreem and CreemSignatureHeader.
Controller: Top-up info exposure
controller/topup.go
Returns enable_creem_topup and creem_products in top-up info based on settings.
Router: API endpoints
router/api-router.go
Adds POST /api/creem/webhookcontroller.CreemWebhook and POST /api/user/creem/paycontroller.RequestCreemPay (with CriticalRateLimit).
Model: Recharge logic
model/topup.go
Adds RechargeCreem(referenceId, customerEmail, customerName) to atomically complete pending top-ups, credit user quota, and optionally set user email.
Settings: Creem config
setting/payment_creem.go, model/option.go
Adds exported settings CreemApiKey, CreemProducts, CreemTestMode, CreemWebhookSecret and wires them into OptionMap load/update.
Web: Creem settings UI
web/src/pages/Setting/Payment/SettingsPaymentGatewayCreem.js
New React admin UI to manage Creem API key, webhook secret, test mode, and product catalog (CRUD), persisting via PUT to options.
Web: i18n
web/src/i18n/locales/en.json, web/src/i18n/locales/zh.json
Adds English and Chinese localization keys/values for Creem UI, product fields, tips, and messages.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant FE as Frontend (Top-up)
  participant BE as Controller (RequestCreemPay)
  participant S as Settings
  participant DB as DB
  participant Creem as Creem API
  participant WH as Controller (CreemWebhook)
  participant M as Model (RechargeCreem)

  User->>FE: select Creem product & pay
  FE->>BE: POST /api/user/creem/pay {productId, paymentMethod:"creem"}
  BE->>S: read Creem settings & products
  BE->>DB: create pending TopUp (referenceId)
  BE->>Creem: create checkout (x-api-key, metadata)
  Creem-->>BE: {checkout_url, order_id}
  BE-->>FE: {checkout_url, order_id}

  note over User,Creem: User completes payment at Creem
  Creem-->>WH: POST /api/creem/webhook (checkout.completed)
  WH->>WH: verify signature (if configured)
  WH->>DB: find TopUp by referenceId
  alt pending & paid
    WH->>M: RechargeCreem(referenceId,email,name)
    M->>DB: tx: mark success, add quota, set email if empty
    DB-->>M: ok
    M-->>WH: ok
  else invalid
    WH-->>Creem: ignore / 400
  end
Loading
sequenceDiagram
  autonumber
  actor Admin
  participant FE as Web (SettingsPaymentGatewayCreem)
  participant API as Backend Options API
  participant SET as setting / model.option

  Admin->>FE: edit Creem settings/products
  FE->>API: PUT options {CreemApiKey, CreemWebhookSecret, CreemTestMode, CreemProducts}
  API->>SET: updateOptionMap(...)
  SET-->>API: persisted
  API-->>FE: per-option success responses
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • alpha -> main #1910 — Modifies model/option.go option map initialization/update for payment-related settings; touches similar option wiring logic.

Suggested reviewers

  • creamlike1024
  • Calcium-Ion

Poem

I nibble keys and hop through logs,
A Creem checkout, bright as clover clogs.
Webhook thumps — the carrots stream,
Quota sprout where pixels gleam.
Hooray, another top-up dream! 🥕✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.67% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The PR title "新增 creem 支付" (Add Creem Payment) directly and accurately describes the primary change in the changeset. The entire PR is focused on implementing comprehensive Creem payment integration across backend services, frontend UI, configuration, and webhook handling. The title is concise, clear, and specific enough that a teammate reviewing the commit history would immediately understand that Creem was added as a new payment method. It does not use vague terminology or misleading language.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

🧹 Nitpick comments (10)
setting/payment_creem.go (1)

1-6: Consider adding validation constants and documentation.

The configuration variables lack documentation and validation constants. Consider adding comments to explain their purpose and define validation constants for better maintainability.

 package setting
 
+// Creem payment gateway configuration
+const (
+	// DefaultCreemProducts represents the default empty product list
+	DefaultCreemProducts = "[]"
+)
+
+// CreemApiKey is the API key for authenticating with Creem payment gateway
 var CreemApiKey = ""
-var CreemProducts = "[]"
+
+// CreemProducts is a JSON string containing the list of available Creem products
+var CreemProducts = DefaultCreemProducts
+
+// CreemTestMode enables test mode for Creem payment processing
 var CreemTestMode = false
+
+// CreemWebhookSecret is used to verify webhook signatures from Creem
 var CreemWebhookSecret = ""
web/src/pages/TopUp/index.js (1)

304-346: Consider adding error boundaries for payment flows.

The Creem payment implementation mirrors the Stripe flow well. However, both implementations open new tabs/windows without handling potential popup blocker scenarios.

 const processCreemCallback = (data) => {
-    // 与 Stripe 保持一致的实现方式
-    window.open(data.checkout_url, '_blank');
+    // 与 Stripe 保持一致的实现方式
+    const newWindow = window.open(data.checkout_url, '_blank');
+    if (!newWindow || newWindow.closed || typeof newWindow.closed === 'undefined') {
+      showError(t('请允许弹出窗口以完成支付'));
+    }
 };

Apply the same improvement to processStripeCallback for consistency.

router/api-router.go (1)

42-42: Security: Creem webhook is signature-verified in handler — consider optional hardening

CreemWebhook in controller/topup_creem.go calls verifyCreemSignature(string(bodyBytes), signature, setting.CreemWebhookSecret) and aborts with 401 on failure.

  • Confirm verifyCreemSignature uses a secure HMAC and constant-time comparison and that the webhook secret is stored/rotated securely.
  • Optional: add middleware for rate-limiting / IP allowlist / centralized logging or extract the signature check into reusable middleware.
controller/misc.go (1)

77-78: Validate CreemProducts JSON before exposing to the frontend.

controller/misc.go currently uses a string check (setting.CreemProducts != "[]") which doesn't validate JSON; invalid JSON can reach the frontend. controller/topup_creem.go already json.Unmarshals CreemProducts and handles errors, but misc.go should parse/validate before setting the enable flag.

-		"enable_creem_topup":       setting.CreemApiKey != "" && setting.CreemProducts != "[]",
+		"enable_creem_topup":       isCreemEnabled(),
 		"creem_products":           setting.CreemProducts,

Suggested helper (add to an appropriate package/file and import encoding/json):

func isCreemEnabled() bool {
    if setting.CreemApiKey == "" {
        return false
    }
    var products []interface{}
    if err := json.Unmarshal([]byte(setting.CreemProducts), &products); err != nil {
        return false
    }
    return len(products) > 0
}
model/topup.go (1)

136-154: Comment drift.

Comment says “优先使用邮箱,如果邮箱为空则使用用户名” but only email is considered. Align the comment or implement the username fallback.

web/src/pages/Setting/Payment/SettingsPaymentGatewayCreem.js (3)

78-85: Secrets cannot be cleared; send only if changed (including empty).

Currently secrets are only sent if non-empty, preventing clearing. Compare with origin and push when changed.

-            if (inputs.CreemApiKey && inputs.CreemApiKey !== '') {
-                options.push({ key: 'CreemApiKey', value: inputs.CreemApiKey });
-            }
+            if (inputs.CreemApiKey !== originInputs.CreemApiKey) {
+                options.push({ key: 'CreemApiKey', value: inputs.CreemApiKey });
+            }
@@
-            if (inputs.CreemWebhookSecret && inputs.CreemWebhookSecret !== '') {
-                options.push({ key: 'CreemWebhookSecret', value: inputs.CreemWebhookSecret });
-            }
+            if (inputs.CreemWebhookSecret !== originInputs.CreemWebhookSecret) {
+                options.push({ key: 'CreemWebhookSecret', value: inputs.CreemWebhookSecret });
+            }

86-91: Keep origin snapshot in sync with saved products.

Persist the JSON you actually saved so change detection stays correct.

-            options.push({ key: 'CreemProducts', value: JSON.stringify(products) });
+            const productsJson = JSON.stringify(products);
+            options.push({ key: 'CreemProducts', value: productsJson });
@@
-                setOriginInputs({ ...inputs });
+                setOriginInputs({ ...inputs, CreemProducts: productsJson });

Also applies to: 110-113


195-196: Normalize price display.

Ensure two decimals for currency display.

-            render: (price, record) => `${record.currency === 'EUR' ? '€' : '$'}${price}`,
+            render: (price, record) => `${record.currency === 'EUR' ? '€' : '$'}${Number(price).toFixed(2)}`,
controller/topup_creem.go (2)

97-105: Harden request path: auth check and reduce noisy logs.

Verify user id exists before creating orders; avoid logging entire request bodies.

-	id := c.GetInt("id")
+	id := c.GetInt("id")
+	if id <= 0 {
+		c.JSON(http.StatusUnauthorized, gin.H{"message": "error", "data": "未登录"})
+		return
+	}

Optionally drop the body echo in RequestCreemPay to DEBUG only.

Also applies to: 140-164


371-399: Set Accept header and shorter user-agent (optional).

Some APIs require Accept; adding a UA aids vendor debugging.

 	req.Header.Set("Content-Type", "application/json")
 	req.Header.Set("x-api-key", setting.CreemApiKey)
+	req.Header.Set("Accept", "application/json")
+	req.Header.Set("User-Agent", "new-api/creem-integration")
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 046c8b2 and a7d6a8b.

📒 Files selected for processing (9)
  • controller/misc.go (1 hunks)
  • controller/topup_creem.go (1 hunks)
  • model/option.go (2 hunks)
  • model/topup.go (1 hunks)
  • router/api-router.go (2 hunks)
  • setting/payment_creem.go (1 hunks)
  • web/src/components/settings/PaymentSetting.js (4 hunks)
  • web/src/pages/Setting/Payment/SettingsPaymentGatewayCreem.js (1 hunks)
  • web/src/pages/TopUp/index.js (8 hunks)
🧰 Additional context used
🧬 Code graph analysis (8)
router/api-router.go (2)
controller/topup_creem.go (2)
  • CreemWebhook (237-284)
  • RequestCreemPay (140-164)
middleware/rate-limit.go (1)
  • CriticalRateLimit (103-105)
web/src/pages/Setting/Payment/SettingsPaymentGatewayCreem.js (2)
web/src/components/settings/PaymentSetting.js (1)
  • inputs (13-33)
web/src/helpers/utils.js (3)
  • showError (94-123)
  • showSuccess (129-131)
  • a (231-231)
model/option.go (2)
common/constants.go (1)
  • OptionMap (36-36)
setting/payment_creem.go (4)
  • CreemApiKey (3-3)
  • CreemProducts (4-4)
  • CreemTestMode (5-5)
  • CreemWebhookSecret (6-6)
web/src/components/settings/PaymentSetting.js (1)
web/src/pages/Setting/Payment/SettingsPaymentGatewayCreem.js (2)
  • SettingsPaymentGatewayCreem (25-387)
  • inputs (28-33)
web/src/pages/TopUp/index.js (3)
web/src/helpers/utils.js (1)
  • showError (94-123)
web/src/helpers/api.js (6)
  • res (186-186)
  • res (187-187)
  • res (228-228)
  • res (229-229)
  • API (5-13)
  • API (5-13)
web/src/helpers/render.js (1)
  • renderQuotaWithAmount (856-864)
model/topup.go (7)
common/database.go (1)
  • UsingPostgreSQL (10-10)
model/main.go (1)
  • DB (63-63)
common/constants.go (2)
  • TopUpStatusPending (198-198)
  • TopUpStatusSuccess (199-199)
common/utils.go (1)
  • GetTimestamp (192-194)
model/user.go (1)
  • User (18-47)
model/log.go (1)
  • RecordLog (76-92)
common/logger.go (1)
  • FormatQuota (107-113)
controller/misc.go (1)
setting/payment_creem.go (2)
  • CreemApiKey (3-3)
  • CreemProducts (4-4)
controller/topup_creem.go (5)
setting/payment_creem.go (4)
  • CreemProducts (4-4)
  • CreemWebhookSecret (6-6)
  • CreemApiKey (3-3)
  • CreemTestMode (5-5)
model/user.go (1)
  • GetUserById (224-236)
common/hash.go (1)
  • Sha1 (22-24)
model/topup.go (3)
  • TopUp (11-20)
  • GetTopUpByTradeNo (44-52)
  • RechargeCreem (102-171)
common/constants.go (1)
  • TopUpStatusPending (198-198)
🔇 Additional comments (14)
router/api-router.go (1)

68-68: Good use of rate limiting for payment endpoints.

The use of CriticalRateLimit middleware for the Creem payment endpoint is appropriate and consistent with other payment methods.

model/option.go (2)

84-87: CreemTestMode boolean-string conversion is handled correctly.

The implementation properly converts CreemTestMode between boolean and string representations for storage and retrieval, maintaining consistency with other boolean options.


333-340: LGTM! Proper handling of Creem configuration updates.

The update logic correctly handles all Creem-related settings, including the string-to-boolean conversion for CreemTestMode.

web/src/pages/TopUp/index.js (4)

449-456: Good defensive JSON parsing with fallback.

The code properly handles potential JSON parsing errors for CreemProducts with a safe fallback to an empty array.


1017-1026: Banner logic correctly handles all payment methods.

The warning banner now properly checks all three payment methods (online, Stripe, and Creem) before displaying the "no payment methods enabled" warning.


1182-1253: UI implementation for Creem products is well-structured.

The Creem product selection UI is clean, responsive, and consistent with the overall design. The separate desktop and mobile layouts ensure good user experience across devices.


1421-1424: Good attention to mobile UX with bottom spacing.

Adding bottom spacing to prevent content from being hidden behind the fixed payment area shows attention to detail.

model/topup.go (2)

115-131: Row-level lock style is legacy gorm; confirm on v2.

Set("gorm:query_option", "FOR UPDATE") is a v1 idiom. If you’re on gorm v2, prefer Clauses(clause.Locking{Strength: "UPDATE"}). If the codebase standardizes on the current pattern, keep as-is.

Do you want me to scan the repo to confirm gorm version and lock usage consistency?


168-169: LGTM on transactional integrity and idempotency.

Status check + FOR UPDATE + single commit give good idempotency and race safety for webhook replays.

web/src/pages/Setting/Payment/SettingsPaymentGatewayCreem.js (1)

47-67: LGTM on initial load and robust product parsing.

Good defensive JSON parse with fallback and proper form hydration.

controller/topup_creem.go (4)

287-308: Consider validating paid amount/product against the pending order.

Before crediting, compare webhook order.amount_paid/product.id with the pending TopUp.Money/Amount to detect mismatches or tampering.

Do you want me to add a guarded check (with currency minor-unit handling) that fails fast on discrepancies?

Also applies to: 333-351


131-138: LGTM on order creation and reference binding.

Binding request_id to our hashed reference and locking on webhook side gives a clean idempotent path.


347-349: Update call site after removing customerName from model.

If you accept the model signature change, adjust here.

-	err := model.RechargeCreem(referenceId, customerEmail, customerName)
+	err := model.RechargeCreem(referenceId, customerEmail)

22-25: Constant naming LGTM.

Clear, scoped, and consistent with existing patterns.

littlewrite and others added 4 commits September 27, 2025 11:40
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a7d6a8b and 0192585.

📒 Files selected for processing (2)
  • controller/topup_creem.go (1 hunks)
  • model/topup.go (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
model/topup.go (7)
common/database.go (1)
  • UsingPostgreSQL (10-10)
model/main.go (1)
  • DB (63-63)
common/constants.go (2)
  • TopUpStatusPending (198-198)
  • TopUpStatusSuccess (199-199)
common/utils.go (1)
  • GetTimestamp (192-194)
model/user.go (1)
  • User (18-47)
model/log.go (2)
  • RecordLog (76-92)
  • LogTypeTopup (41-41)
common/logger.go (1)
  • FormatQuota (107-113)
controller/topup_creem.go (5)
setting/payment_creem.go (4)
  • CreemTestMode (5-5)
  • CreemProducts (4-4)
  • CreemWebhookSecret (6-6)
  • CreemApiKey (3-3)
model/user.go (1)
  • GetUserById (224-236)
common/hash.go (1)
  • Sha1 (22-24)
model/topup.go (3)
  • TopUp (11-20)
  • GetTopUpByTradeNo (44-52)
  • RechargeCreem (101-170)
common/constants.go (1)
  • TopUpStatusPending (198-198)
🔇 Additional comments (1)
controller/topup_creem.go (1)

317-360: Remove PII from checkout-completed logs.

Lines 317 and 358 still emit raw customer emails (and previously the customer name), which was already flagged earlier. These logs violate our “no PII in logs” policy. Please redact or drop the email/name before logging.

-	log.Printf("处理Creem支付完成 - 订单号: %s, Creem订单ID: %s, 支付金额: %d %s, 客户邮箱: %s, 产品: %s",
-		referenceId,
-		event.Object.Order.Id,
-		event.Object.Order.AmountPaid,
-		event.Object.Order.Currency,
-		event.Object.Customer.Email,
-		event.Object.Product.Name)
+	log.Printf("处理Creem支付完成 - 订单号: %s, Creem订单ID: %s, 支付金额: %d %s",
+		referenceId,
+		event.Object.Order.Id,
+		event.Object.Order.AmountPaid,
+		event.Object.Order.Currency)
@@
-	log.Printf("Creem充值成功 - 订单号: %s, 充值额度: %d, 支付金额: %.2f, 客户邮箱: %s, 客户姓名: %s",
-		referenceId, topUp.Amount, topUp.Money, customerEmail, customerName)
+	log.Printf("Creem充值成功 - 订单号: %s, 充值额度: %d, 支付金额: %.2f",
+		referenceId, topUp.Amount, topUp.Money)

Comment on lines +351 to +352
err := model.RechargeCreem(referenceId, customerEmail, customerName)
if err != nil {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Fix RechargeCreem call signature.

Line 351 still invokes model.RechargeCreem with three parameters, but the function now takes only (referenceId, customerEmail). This won’t compile. Drop the extra argument.

-	err := model.RechargeCreem(referenceId, customerEmail, customerName)
+	err := model.RechargeCreem(referenceId, customerEmail)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
err := model.RechargeCreem(referenceId, customerEmail, customerName)
if err != nil {
err := model.RechargeCreem(referenceId, customerEmail)
if err != nil {
// handle error...
}
🤖 Prompt for AI Agents
In controller/topup_creem.go around lines 351 to 352, the call to
model.RechargeCreem is passing three arguments but the function signature now
accepts only (referenceId, customerEmail); remove the extra customerName
argument so the call matches the updated signature and the code will compile.

Comment on lines +422 to +424
log.Printf("发送Creem支付请求 - URL: %s, 产品ID: %s, 用户邮箱: %s, 订单号: %s",
apiUrl, product.ProductId, email, referenceId)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Redact email in payment request log.

Line 422 logs the payer’s email address when hitting the Creem API. That’s PII we don’t want in logs; omit or mask it.

-	log.Printf("发送Creem支付请求 - URL: %s, 产品ID: %s, 用户邮箱: %s, 订单号: %s",
-		apiUrl, product.ProductId, email, referenceId)
+	log.Printf("发送Creem支付请求 - URL: %s, 产品ID: %s, 订单号: %s",
+		apiUrl, product.ProductId, referenceId)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
log.Printf("发送Creem支付请求 - URL: %s, 产品ID: %s, 用户邮箱: %s, 订单号: %s",
apiUrl, product.ProductId, email, referenceId)
log.Printf("发送Creem支付请求 - URL: %s, 产品ID: %s, 订单号: %s",
apiUrl, product.ProductId, referenceId)
🤖 Prompt for AI Agents
In controller/topup_creem.go around lines 422 to 424, the payment request log
currently includes the payer’s full email (PII); remove or mask the email before
logging. Replace the email argument with a redacted value (e.g., "[REDACTED]" or
a masked string that preserves minimal context like first character and domain)
so the log call no longer contains the full address while retaining the other
fields (apiUrl, product.ProductId, referenceId).

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (8)
web/src/pages/TopUp/index.js (5)

304-311: Guard against invalid product before opening modal

Add a quick field check (productId) to fail fast and avoid opening the modal with a misconfigured product.

 const creemPreTopUp = async (product) => {
   if (!enableCreemTopUp) {
     showError(t('管理员未开启 Creem 充值!'));
     return;
   }
+  if (!product?.productId) {
+    showError(t('产品配置错误,请联系管理员'));
+    return;
+  }
   setSelectedCreemProduct(product);
   setCreemOpen(true);
 };

551-553: Ensure Stripe amount reflects server pricing

Setting stripeAmount = preset.value can display a wrong amount. Recompute via API instead.

   setStripeTopUpCount(preset.value);
-  setStripeAmount(preset.value);
+  getStripeAmount(preset.value);

675-684: Modal title: avoid hardcoded $

“确定要充值 $” may be misleading. Use a neutral title for Stripe, amount is shown below.

-      <Modal
-        title={t('确定要充值 $')}
+      <Modal
+        title={t('充值确认')}

694-719: Modal title: reflect Creem context or remove $

Same as Stripe: avoid the literal “$”. Prefer a generic title; details below already show currency/price.

-      <Modal
-        title={t('确定要充值 $')}
+      <Modal
+        title={t('充值确认')}

454-461: Harden parsing of creem_products

Consider validating entries (required keys and types) after JSON.parse to prevent broken items from being clickable.

   try {
-    const products = JSON.parse(statusState.status.creem_products || '[]');
-    setCreemProducts(products);
+    const products = JSON.parse(statusState.status.creem_products || '[]');
+    const valid = Array.isArray(products)
+      ? products.filter(p => p && p.name && p.productId && p.price > 0 && p.quota > 0)
+      : [];
+    setCreemProducts(valid);
   } catch (e) {
     setCreemProducts([]);
   }
web/src/pages/Setting/Payment/SettingsPaymentGatewayCreem.js (3)

92-101: Batch update options to avoid partial writes

Multiple parallel PUTs can leave settings inconsistent if one fails. Prefer a single batch endpoint or transactional update.

  • Option A: implement API to accept an array of {key,value} and apply atomically.
  • Option B: sequence requests and abort on first failure with rollback (if supported).

Also applies to: 100-118


175-179: Confirm before deleting a product

Avoid accidental deletion by adding a confirmation dialog.

-const deleteProduct = (productId) => {
-  const newProducts = products.filter(p => p.productId !== productId);
-  setProducts(newProducts);
-};
+const deleteProduct = (productId) => {
+  Modal.confirm({
+    title: t('确认删除'),
+    content: t('删除后不可恢复,是否继续?'),
+    onOk: () => {
+      setProducts(products.filter(p => p.productId !== productId));
+    },
+  });
+};

78-85: Secrets cannot be cleared via UI

Because empty values are skipped, there’s no way to intentionally remove a key/secret. Confirm this is desired; otherwise, add an explicit “Clear” action.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0192585 and 6ccb404.

📒 Files selected for processing (7)
  • controller/topup_creem.go (1 hunks)
  • model/topup.go (1 hunks)
  • web/src/components/settings/PaymentSetting.js (4 hunks)
  • web/src/i18n/locales/en.json (1 hunks)
  • web/src/i18n/locales/zh.json (1 hunks)
  • web/src/pages/Setting/Payment/SettingsPaymentGatewayCreem.js (1 hunks)
  • web/src/pages/TopUp/index.js (9 hunks)
🧰 Additional context used
🧬 Code graph analysis (5)
web/src/components/settings/PaymentSetting.js (1)
web/src/pages/Setting/Payment/SettingsPaymentGatewayCreem.js (2)
  • SettingsPaymentGatewayCreem (25-385)
  • inputs (28-33)
web/src/pages/TopUp/index.js (4)
web/src/helpers/utils.js (1)
  • showError (94-123)
web/src/helpers/api.js (6)
  • res (186-186)
  • res (187-187)
  • res (228-228)
  • res (229-229)
  • API (5-13)
  • API (5-13)
web/src/pages/Setting/Payment/SettingsPaymentGatewayCreem.js (1)
  • products (35-35)
web/src/helpers/render.js (1)
  • renderQuotaWithAmount (856-864)
web/src/pages/Setting/Payment/SettingsPaymentGatewayCreem.js (2)
web/src/components/settings/PaymentSetting.js (1)
  • inputs (13-33)
web/src/helpers/utils.js (3)
  • showError (94-123)
  • showSuccess (129-131)
  • a (231-231)
model/topup.go (7)
common/database.go (1)
  • UsingPostgreSQL (10-10)
model/main.go (1)
  • DB (63-63)
common/constants.go (2)
  • TopUpStatusPending (198-198)
  • TopUpStatusSuccess (199-199)
common/utils.go (1)
  • GetTimestamp (192-194)
model/user.go (1)
  • User (18-47)
model/log.go (2)
  • RecordLog (76-92)
  • LogTypeTopup (41-41)
common/logger.go (1)
  • FormatQuota (107-113)
controller/topup_creem.go (6)
setting/payment_creem.go (4)
  • CreemTestMode (5-5)
  • CreemProducts (4-4)
  • CreemWebhookSecret (6-6)
  • CreemApiKey (3-3)
model/user.go (1)
  • GetUserById (224-236)
common/hash.go (1)
  • Sha1 (22-24)
web/src/pages/TopUp/index.js (1)
  • TopUp (42-1548)
model/topup.go (3)
  • TopUp (11-20)
  • GetTopUpByTradeNo (44-52)
  • RechargeCreem (101-169)
common/constants.go (1)
  • TopUpStatusPending (198-198)
🔇 Additional comments (21)
model/topup.go (5)

101-169: Correct function signature based on past review.

The past review comment indicates that the RechargeCreem function signature was updated to take only two parameters (referenceId string, customerEmail string), but the current implementation still has three parameters including customerName. This creates a compilation mismatch between the function definition and where it's called in the controller.

Apply this fix:

-func RechargeCreem(referenceId string, customerEmail string, customerName string) (err error) {
+func RechargeCreem(referenceId string, customerEmail string) (err error) {

Additionally, update the function body to not reference the removed customerName parameter.


106-106: LGTM: Proper integer quota handling.

Good change to use int64 for quota to maintain consistency with the TopUp.Amount field type and avoid float precision issues.


131-133: LGTM: Direct quota assignment from amount.

This approach correctly uses the integer amount directly as quota, avoiding the float conversion that could introduce precision drift. This aligns with the Creem payment model where products have fixed amounts.


139-152: LGTM: Conditional email update logic.

The logic properly checks if the user's current email is empty before updating it with the customer email from the payment provider. This prevents overwriting existing user emails while still allowing new users to have their payment email set.


166-166: Remove PII from log as noted in past review.

The log message still includes customerEmail which is PII and should be removed based on the past review comment. Also, since the customerName parameter is being removed, this log should be updated accordingly.

Apply this fix:

-RecordLog(topUp.UserId, LogTypeTopup, fmt.Sprintf("使用Creem充值成功,充值额度: %v,支付金额:%.2f", common.FormatQuota(int(quota)), topUp.Money))
+RecordLog(topUp.UserId, LogTypeTopup, fmt.Sprintf("使用Creem充值成功,充值额度: %v,支付金额:%.2f", common.FormatQuota(int(quota)), topUp.Money))
controller/topup_creem.go (8)

352-352: Fix function call signature mismatch.

This call still passes three arguments to model.RechargeCreem, but according to the past review comments, the function signature was updated to only accept two parameters. The call needs to match the updated signature.

Apply this fix:

-err := model.RechargeCreem(referenceId, customerEmail, customerName)
+err := model.RechargeCreem(referenceId, customerEmail)

155-156: Remove PII logging as noted in past review.

The debug logging still prints the raw request body which may contain sensitive user information like email addresses.

Apply this fix:

-// 打印body内容
-log.Printf("creem pay request body: %s", string(bodyBytes))

423-424: Redact email in payment request log.

Line 423 logs the payer's email address, which is PII that shouldn't be stored in logs.

Apply this fix:

-log.Printf("发送Creem支付请求 - URL: %s, 产品ID: %s, 用户邮箱: %s, 订单号: %s",
-	apiUrl, product.ProductId, email, referenceId)
+log.Printf("发送Creem支付请求 - URL: %s, 产品ID: %s, 订单号: %s",
+	apiUrl, product.ProductId, referenceId)

22-25: LGTM: Well-defined constants.

Good practice to define payment method and signature header constants for consistency across the codebase.


30-49: LGTM: Secure signature verification with test mode bypass.

The signature verification properly uses HMAC-SHA256 and correctly implements the test mode bypass only when explicitly enabled, addressing the security concern from past reviews.


104-106: LGTM: Proper unique reference ID generation.

The reference ID generation using timestamp, user ID, and random string provides good uniqueness and traceability.


378-461: LGTM: Comprehensive Creem API integration.

The genCreemLink function properly handles:

  • API key validation
  • Test vs production environment selection
  • Complete request data construction with customer info
  • Proper HTTP client with timeout
  • Comprehensive error handling
  • Response validation

The implementation correctly accepts any 2xx status codes as mentioned in past reviews.


295-362: LGTM: Robust webhook event processing.

The handleCheckoutCompleted function implements proper validation:

  • Order status verification (paid)
  • Order type validation (onetime)
  • Local order existence check
  • Pending status verification to prevent duplicate processing
  • Comprehensive error handling and logging

Good defensive programming practices throughout.

web/src/i18n/locales/zh.json (1)

12-14: LGTM: Proper localization additions.

The new translation entries for Creem integration are properly added:

  • Correct trailing comma after existing entry
  • Appropriate Chinese translations for the Creem introduction and configuration tips
  • Good integration with the existing localization structure
web/src/components/settings/PaymentSetting.js (2)

52-54: Fix inconsistent CreemProducts handling as noted in past review.

The try-catch block doesn't actually parse JSON but just assigns the value directly, which is inconsistent with other similar cases like TopupGroupRatio.

Apply this fix:

          case 'CreemProducts':
-            newInputs[item.key] = item.value || '[]';
+            try {
+              newInputs[item.key] = item.value ? JSON.parse(item.value) : [];
+            } catch (error) {
+              console.error('解析CreemProducts出错:', error);
+              newInputs[item.key] = [];
+            }
            break;

6-6: LGTM: Clean integration of Creem settings component.

The integration properly:

  • Imports the new Creem settings component
  • Initializes Creem-related state fields with appropriate defaults
  • Renders the component in a new card with proper styling and props

The implementation follows the established pattern used for other payment gateways.

Also applies to: 30-32, 104-106

web/src/i18n/locales/en.json (1)

1784-1816: LGTM: Comprehensive English translations for Creem integration.

The translation additions are well-structured and provide:

  • Clear, professional English translations for all Creem-related UI elements
  • Proper context-aware translations (e.g., currency symbols, examples)
  • Consistent terminology throughout the Creem feature set
  • Good integration with the existing translation structure
web/src/pages/TopUp/index.js (3)

313-346: Validation added for Creem payment request — looks good

Checks for selection and productId are in place; this addresses the earlier review ask.


1187-1254: Creem product grid UX is clear

Good presentation of name/quota/price and consistent currency symbol handling.


313-346: Creem pay response schema matches frontend assumptions
Confirmed controller/topup_creem.go returns "message": "success" and a "data" object containing checkout_url.

web/src/pages/Setting/Payment/SettingsPaymentGatewayCreem.js (1)

150-173: Basic product validation is good

Presence and >0 checks for price/quota are in place; unique productId enforced; disabling productId edit prevents accidental ID changes.

Comment on lines +356 to +365
<InputNumber
value={productForm.price}
onChange={(value) => setProductForm({ ...productForm, price: value })}
placeholder={t('例如:4.99')}
min={0.01}
precision={2}
size='large'
className='w-full'
defaultValue={4.49}
/>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Controlled InputNumber should not use defaultValue

You’re providing value plus defaultValue, which is conflicting. Remove defaultValue.

 <InputNumber
   value={productForm.price}
   onChange={(value) => setProductForm({ ...productForm, price: value })}
   placeholder={t('例如:4.99')}
   min={0.01}
   precision={2}
   size='large'
   className='w-full'
-  defaultValue={4.49}
 />
🤖 Prompt for AI Agents
In web/src/pages/Setting/Payment/SettingsPaymentGatewayCreem.js around lines 356
to 365, the InputNumber is using both value and defaultValue which conflicts for
a controlled component; remove the defaultValue prop from the InputNumber and,
if you want an initial value, set productForm.price to 4.49 in the component's
state initialization so the input remains fully controlled.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (6)
model/topup.go (4)

110-117: Use GORM locking clause and avoid manual identifier quoting.

Prefer the v2 API with explicit row locking; also drop manual backtick/quote handling for portability and clarity.

- refCol := "`trade_no`"
- if common.UsingPostgreSQL {
-   refCol = `"trade_no"`
- }
- err = DB.Transaction(func(tx *gorm.DB) error {
-   err := tx.Set("gorm:query_option", "FOR UPDATE").Where(refCol+" = ?", referenceId).First(topUp).Error
+ err = DB.Transaction(func(tx *gorm.DB) error {
+   err := tx.
+     Clauses(clause.Locking{Strength: "UPDATE"}).
+     Where("trade_no = ?", referenceId).
+     First(topUp).Error

Add import:

import "gorm.io/gorm/clause"

135-139: Fix misleading comment to match behavior.

Comment says “use username if email empty,” but code only conditionally updates email.

- // 构建更新字段,优先使用邮箱,如果邮箱为空则使用用户名
+ // 构建更新字段;若用户当前邮箱为空且回调提供了邮箱,则更新邮箱

140-156: Avoid extra SELECT; conditionally set email in a single UPDATE.

You can atomically update quota and set email only if blank, avoiding the read and reducing contention inside the transaction.

- // 如果有客户邮箱,尝试更新用户邮箱(仅当用户邮箱为空时)
- if customerEmail != "" {
-   // 先检查用户当前邮箱是否为空
-   var user User
-   err = tx.Where("id = ?", topUp.UserId).First(&user).Error
-   if err != nil {
-     return err
-   }
-   // 如果用户邮箱为空,则更新为支付时使用的邮箱
-   if user.Email == "" {
-     updateFields["email"] = customerEmail
-   }
- }
- err = tx.Model(&User{}).Where("id = ?", topUp.UserId).Updates(updateFields).Error
+ // 原子更新:增加配额;若邮箱为空(或NULL)则写入支付邮箱
+ err = tx.Model(&User{}).
+   Where("id = ?", topUp.UserId).
+   Updates(map[string]interface{}{
+     "quota": gorm.Expr("quota + ?", quota),
+     "email": gorm.Expr("CASE WHEN (email = '' OR email IS NULL) AND ? <> '' THEN ? ELSE email END", customerEmail, customerEmail),
+   }).Error

167-167: Unify quota formatting in logs.

Use the existing quota formatter for consistency with other flows.

- RecordLog(topUp.UserId, LogTypeTopup, fmt.Sprintf("使用Creem充值成功,充值额度: %v,支付金额:%.2f", quota, topUp.Money))
+ RecordLog(topUp.UserId, LogTypeTopup, fmt.Sprintf("使用Creem充值成功,充值额度: %v,支付金额:%.2f", logger.FormatQuota(int(quota)), topUp.Money))
model/option.go (1)

341-343: Parse boolean robustly.

Use strconv.ParseBool to accept “1/0”, “t/f”, etc., and avoid brittle string compares.

-case "CreemTestMode":
-    setting.CreemTestMode = value == "true"
+case "CreemTestMode":
+    if b, err2 := strconv.ParseBool(value); err2 == nil {
+        setting.CreemTestMode = b
+    }
router/api-router.go (1)

42-42: Webhook hardening: consider scoped rate limit and body size limit.

Public route without auth is correct for webhooks, but add a tight rate limit and max body bytes to reduce abuse; keep signature checks in controller.

Would you like a lightweight middleware snippet (per‑IP small window, e.g., 30 req/min) and a max‑bytes reader (e.g., 256KB) for this endpoint?

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6ccb404 and fade73d.

📒 Files selected for processing (4)
  • controller/topup.go (1 hunks)
  • model/option.go (2 hunks)
  • model/topup.go (1 hunks)
  • router/api-router.go (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (4)
router/api-router.go (1)
controller/topup_creem.go (2)
  • CreemWebhook (240-292)
  • RequestCreemPay (144-167)
controller/topup.go (1)
setting/payment_creem.go (2)
  • CreemApiKey (3-3)
  • CreemProducts (4-4)
model/topup.go (6)
common/database.go (1)
  • UsingPostgreSQL (10-10)
model/main.go (1)
  • DB (63-63)
common/constants.go (2)
  • TopUpStatusPending (199-199)
  • TopUpStatusSuccess (200-200)
common/utils.go (1)
  • GetTimestamp (272-274)
model/user.go (1)
  • User (19-48)
model/log.go (1)
  • RecordLog (78-94)
model/option.go (2)
common/constants.go (1)
  • OptionMap (36-36)
setting/payment_creem.go (4)
  • CreemApiKey (3-3)
  • CreemProducts (4-4)
  • CreemTestMode (5-5)
  • CreemWebhookSecret (6-6)
🔇 Additional comments (2)
model/option.go (1)

85-88: Wiring new Creem options into OptionMap looks good.

Confirm these keys are masked/hidden appropriately in any admin UI exports and audit logs (especially CreemApiKey, CreemWebhookSecret).

model/topup.go (1)

102-106: Remove unused parameter customerName (compile-time error) and update callers.

customerName is not used in the function body; in Go, unused parameters cause a compile error. This was also flagged earlier and marked addressed, but it’s still present. Please drop it and adjust all call sites.

Apply:

-func RechargeCreem(referenceId string, customerEmail string, customerName string) (err error) {
+func RechargeCreem(referenceId string, customerEmail string) (err error) {

Then verify/rewrite all usages:

#!/bin/bash
# Find all call sites for RechargeCreem and show surrounding context
rg -nP -C3 '\bRechargeCreem\s*\('

Comment on lines +53 to 55
"enable_creem_topup": setting.CreemApiKey != "" && setting.CreemProducts != "[]",
"creem_products": setting.CreemProducts,
"pay_methods": payMethods,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

🧩 Analysis chain

Compute enable_creem_topup from parsed products; return parsed products, not a JSON string.

Current check setting.CreemProducts != "[]" can be true for invalid JSON; returning a raw JSON string forces clients to re‑parse. Parse once, validate, and return typed JSON.

- "enable_creem_topup":  setting.CreemApiKey != "" && setting.CreemProducts != "[]",
- "creem_products":      setting.CreemProducts,
+ "enable_creem_topup":  setting.CreemApiKey != "" && len(creemProducts) > 0,
+ "creem_products":      creemProducts,

Add before building data:

// at top of GetTopUpInfo
var creemProducts []CreemProduct
if err := json.Unmarshal([]byte(setting.CreemProducts), &creemProducts); err != nil {
    creemProducts = nil
}

Add import:

import "encoding/json"

Please confirm the frontend expects an array, not a string.


🏁 Script executed:

#!/bin/bash
# Grep UI for 'creem_products' usage to confirm expected type
rg -nP -C2 '\bcreem_products\b'

Length of output: 963


Return creem_products as an array and update frontend parsing

  • In controller/topup.go, unmarshal setting.CreemProducts before building data and switch to:

     // parse once at top of GetTopUpInfo
     var creemProducts []CreemProduct
     if err := json.Unmarshal([]byte(setting.CreemProducts), &creemProducts); err != nil {
         creemProducts = nil
     }
    
     // when constructing response
    - "enable_creem_topup": setting.CreemApiKey != "" && setting.CreemProducts != "[]",
    - "creem_products":      setting.CreemProducts,
    + "enable_creem_topup": setting.CreemApiKey != "" && len(creemProducts) > 0,
    + "creem_products":      creemProducts,

    and add import "encoding/json" at the top.

  • In web/src/components/topup/index.jsx (around line 383), remove the JSON.parse and consume the array directly:

    - const products = JSON.parse(data.creem_products || '[]');
    + const products = data.creem_products || [];
      setCreemProducts(products);

This ensures the backend supplies a validated array and the UI no longer re-parses a JSON string.

🤖 Prompt for AI Agents
In controller/topup.go around lines 53 to 55, the code currently returns
setting.CreemProducts as a raw JSON string; unmarshal setting.CreemProducts into
a Go slice (e.g., []string) before assembling the response, add import
"encoding/json" at the top of the file, handle unmarshal errors (return or log
and fall back to empty slice) and set "creem_products" in the response map to
that slice (not the original string); also update
web/src/components/topup/index.jsx (around line ~383) to remove JSON.parse usage
and consume the array directly from the backend.

selfRoute.POST("/amount", controller.RequestAmount)
selfRoute.POST("/stripe/pay", middleware.CriticalRateLimit(), controller.RequestStripePay)
selfRoute.POST("/stripe/amount", controller.RequestStripeAmount)
selfRoute.POST("/creem/pay", middleware.CriticalRateLimit(), controller.RequestCreemPay)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

🧩 Analysis chain

Drop full request body logging in RequestCreemPay; may leak PII.

Per snippet controller/topup_creem.go Lines 143‑166, the entire JSON body is logged unconditionally. That likely includes email/metadata. Restrict logs to minimal fields and only in test mode.

Proposed change in controller/topup_creem.go:

- // 打印body内容
- log.Printf("creem pay request body: %s", string(bodyBytes))
+ // 仅在测试模式下打印有限字段,避免泄露PII
+ if setting.CreemTestMode {
+   var peek struct {
+     ProductId string `json:"product_id"`
+   }
+   _ = json.Unmarshal(bodyBytes, &peek)
+   log.Printf("creem pay request (test) product_id=%s", peek.ProductId)
+ }

Search to confirm and remove any other full-body logs:


🏁 Script executed:

#!/bin/bash
rg -nP -C1 'log\.Printf\(\s*"creem (pay request body|Webhook - Signature:)' controller

Length of output: 164


Restrict full request-body logging to test mode only
Remove the unconditional log.Printf("creem pay request body: …") in controller/topup_creem.go and replace it with a test-mode–gated, minimal-field log. For example:

@@ controller/topup_creem.go:155-157
-   // 打印body内容
-   log.Printf("creem pay request body: %s", string(bodyBytes))
+   if setting.CreemTestMode {
+       var peek struct {
+           ProductId string `json:"product_id"`
+       }
+       _ = json.Unmarshal(bodyBytes, &peek)
+       log.Printf("creem pay request (test) product_id=%s", peek.ProductId)
+   }

This change prevents inadvertent leakage of PII in production.

🤖 Prompt for AI Agents
In router/api-router.go around line 70 and controller/topup_creem.go where the
current unconditional log.Printf("creem pay request body: …") is emitted, remove
the unconditional full-body logging and replace it with a test-mode guarded,
minimal-field log: detect test mode via the existing config/env flag (e.g., an
IsTest or APP_ENV == "test"), and only when that flag is true log a small,
non-PII subset of the request (transaction id, amount, status) or a single
masked summary; in non-test (production) flows do not log the full request
body—ensure PII fields are excluded or masked and keep the logging conditional
so production cannot leak sensitive data.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
model/topup.go (1)

308-308: Remove the unused customerName parameter.

The customerName parameter is declared but never used in the function body. Remove it from the signature and update all call sites accordingly.

Based on past review comments.

🧹 Nitpick comments (1)
model/topup.go (1)

373-373: Format quota consistently with other recharge functions.

Use logger.FormatQuota to format the quota value, maintaining consistency with Recharge (line 101) and ManualCompleteTopUp (line 305).

Apply this diff:

-	RecordLog(topUp.UserId, LogTypeTopup, fmt.Sprintf("使用Creem充值成功,充值额度: %v,支付金额:%.2f", quota, topUp.Money))
+	RecordLog(topUp.UserId, LogTypeTopup, fmt.Sprintf("使用Creem充值成功,充值额度: %v,支付金额:%.2f", logger.FormatQuota(int(quota)), topUp.Money))
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fade73d and ac3baac.

📒 Files selected for processing (4)
  • controller/topup.go (1 hunks)
  • model/option.go (2 hunks)
  • model/topup.go (1 hunks)
  • router/api-router.go (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • controller/topup.go
  • model/option.go
  • router/api-router.go
🧰 Additional context used
🧬 Code graph analysis (1)
model/topup.go (6)
common/database.go (1)
  • UsingPostgreSQL (10-10)
model/main.go (1)
  • DB (64-64)
common/constants.go (2)
  • TopUpStatusPending (200-200)
  • TopUpStatusSuccess (201-201)
common/utils.go (1)
  • GetTimestamp (272-274)
model/user.go (1)
  • User (20-49)
model/log.go (2)
  • RecordLog (81-97)
  • LogTypeTopup (45-45)
🔇 Additional comments (1)
model/topup.go (1)

338-339: Original review comment is incorrect and based on flawed assumptions.

The code is not inconsistent. The Amount field has intentionally different semantics across payment methods:

  • Non-Creem methods (Stripe/Manual): Amount = raw money value → multiply by QuotaPerUnit at completion
  • Creem: Amount = pre-calculated quota from product configuration → use directly

In RequestPay (line ~105), the TopUp is created with:

Amount: selectedProduct.Quota     // Already final quota (int64)
Money:  selectedProduct.Price     // Actual payment amount

Since Creem products are pre-configured with both Quota and Price fields, the quota conversion happens once at product setup, not at transaction completion. Line 339's direct assignment is correct and the comment accurately describes this intentional difference.

Likely an incorrect or invalid review comment.

common.OptionMap["StripeWebhookSecret"] = setting.StripeWebhookSecret
common.OptionMap["StripePriceId"] = setting.StripePriceId
common.OptionMap["StripeUnitPrice"] = strconv.FormatFloat(setting.StripeUnitPrice, 'f', -1, 64)
common.OptionMap["CreemApiKey"] = setting.CreemApiKey
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

需要参考一下 setting目录下设置项的使用来,不能继续在这里堆不好维护的代码了

@seefs001 seefs001 merged commit 36c603f into QuantumNous:main Oct 28, 2025
1 check passed
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.

2 participants