From 643abd13ea499688ebacc9dd6ca510bf8fd0a9ab Mon Sep 17 00:00:00 2001 From: stefanfeng Date: Mon, 19 Oct 2026 14:12:43 +0800 Subject: [PATCH] Pass recharge product ID to Huihui order API --- digital-avatar-app/backend/routers/tokens.py | 1 + .../backend/services/huihui_org_payment.py | 5 ++-- .../backend/tests/test_huihui_payment.py | 29 +++++++++++++++++++ .../backend/tests/test_token_billing.py | 1 + .../docs/H5_PRODUCTION_DEPLOYMENT.md | 2 +- 5 files changed, 35 insertions(+), 3 deletions(-) diff --git a/digital-avatar-app/backend/routers/tokens.py b/digital-avatar-app/backend/routers/tokens.py index 2b75db0..2fd39a4 100644 --- a/digital-avatar-app/backend/routers/tokens.py +++ b/digital-avatar-app/backend/routers/tokens.py @@ -434,6 +434,7 @@ def charge_via_huihui_org(payload: dict = Body(...), authorization: str = Header token=user.huihui_token, user_id=user.huihui_user_id, name=user.nickname, phone=user.phone, avatar=user.avatar_url, amount=f"{cents / 100:.2f}", + product_id=plan.id, ) except HuihuiPaymentError as exc: return fail(str(exc), 502) diff --git a/digital-avatar-app/backend/services/huihui_org_payment.py b/digital-avatar-app/backend/services/huihui_org_payment.py index c981b6f..315e262 100644 --- a/digital-avatar-app/backend/services/huihui_org_payment.py +++ b/digital-avatar-app/backend/services/huihui_org_payment.py @@ -36,9 +36,10 @@ class HuihuiOrgPaymentClient(HuihuiPaymentClient): return data def create_order(self, *, token: str, user_id: str, name: str, phone: str, - avatar: str, amount: str) -> dict: + avatar: str, amount: str, product_id: str) -> dict: body = {"chargeAmount": float(amount), - "commodityName": "数字分身积分充值", "userId": user_id, + "commodityName": "数字分身积分充值", "productId": product_id, + "userId": user_id, "name": name or "", "phoneNumber": phone or "", "avatar": avatar or ""} return self._request("POST", "/order/tokenRecharge", token=token, user_id=user_id, body=body) diff --git a/digital-avatar-app/backend/tests/test_huihui_payment.py b/digital-avatar-app/backend/tests/test_huihui_payment.py index 98b9f52..eb3734a 100644 --- a/digital-avatar-app/backend/tests/test_huihui_payment.py +++ b/digital-avatar-app/backend/tests/test_huihui_payment.py @@ -1,6 +1,7 @@ import os from unittest.mock import Mock, patch +from services.huihui_org_payment import HuihuiOrgPaymentClient from services.huihui_payment import HuihuiPaymentClient @@ -81,3 +82,31 @@ def test_request_refund_uses_configured_huihui_endpoint_without_exposing_secret( "refundReason": "用户申请", } assert "accessSecret" not in post.call_args.kwargs["params"] + + +def test_org_recharge_order_sends_package_product_id_as_string(): + env = { + "HUIHUI_ORG_BASE_URL": "https://fat-open.example/api/huihuiorg", + "HUIHUI_APP_ID": "app-id", + "HUIHUI_ACCESS_ID": "access-id", + "HUIHUI_ACCESS_SECRET": "access-secret", + } + response = Mock(status_code=200) + response.json.return_value = {"code": 200, "data": {"orderNo": "261016111200090123456"}} + + with patch.dict(os.environ, env), patch( + "services.huihui_org_payment.httpx.request", return_value=response + ) as request: + result = HuihuiOrgPaymentClient().create_order( + token="user-token", user_id="user-id", name="测试用户", + phone="", avatar="", amount="10.00", product_id="Basic", + ) + + assert result["orderNo"] == "261016111200090123456" + assert request.call_args.args == ( + "POST", "https://fat-open.example/api/huihuiorg/order/tokenRecharge" + ) + body = request.call_args.kwargs["json"] + assert body["chargeAmount"] == 10.0 + assert body["productId"] == "Basic" + assert isinstance(body["productId"], str) diff --git a/digital-avatar-app/backend/tests/test_token_billing.py b/digital-avatar-app/backend/tests/test_token_billing.py index 480b133..e8926e2 100644 --- a/digital-avatar-app/backend/tests/test_token_billing.py +++ b/digital-avatar-app/backend/tests/test_token_billing.py @@ -152,6 +152,7 @@ def test_org_recharge_only_credits_after_verified_provider_order(authorization_c assert created["balance"] == DEFAULT_TOKEN_GRANT assert created["checkoutToken"] == f"huihui-payment-{context['suffix']}" assert "pay_type" not in provider.create_order.call_args.kwargs + assert provider.create_order.call_args.kwargs["product_id"] == "Basic" details = {**created_data, "payStatus": 2, "userId": context["owner"].huihui_user_id} diff --git a/digital-avatar-app/docs/H5_PRODUCTION_DEPLOYMENT.md b/digital-avatar-app/docs/H5_PRODUCTION_DEPLOYMENT.md index ee144b7..32a5dec 100644 --- a/digital-avatar-app/docs/H5_PRODUCTION_DEPLOYMENT.md +++ b/digital-avatar-app/docs/H5_PRODUCTION_DEPLOYMENT.md @@ -89,7 +89,7 @@ CHAT_ATTACHMENT_CLEANUP_MINUTES=60 `EMBEDDING_API_URL` 同时支持 OpenAI 兼容基础地址(如上面的 `/v1`)和完整的 `/v1/embeddings` 地址,后端会统一请求 `/embeddings`。发布后必须在后端容器内执行一次最小向量探针,确认返回向量数量和维度,而不能只检查 `/api/health`。 -当前 H5 积分充值调用 `/api/token/charge/org` 创建会会组织充值订单,再用会会返回的订单号打开 `#/payOrder?id=<会会订单号>&isOrgUrl=true&evn=<入口环境>`;测试环境额外透传会会登录 token,生产环境不传。`id` 是收银台查询订单的编号,不能替换为套餐/道具 ID。普通微信 H5 走 JSAPI,App 和其他 H5 沿用会会普通收银台。支付回调确认状态成功且金额与套餐价格完全一致后才增加积分,重复回调不会重复到账。 +当前 H5 积分充值调用 `/api/token/charge/org` 创建会会组织充值订单。数字分身后端请求会会 `/order/tokenRecharge` 时,把所选套餐 ID 作为字符串 `productId` 传入(`Basic`、`Standard`、`Professional`、`Enterprise`),再用会会返回的订单号打开 `#/payOrder?id=<会会订单号>&isOrgUrl=true&evn=<入口环境>`;测试环境额外透传会会登录 token,生产环境不传。`id` 是收银台查询订单的编号,不能替换为套餐/道具 ID。普通微信 H5 走 JSAPI,App 和其他 H5 沿用会会普通收银台。支付回调确认状态成功且金额与套餐价格完全一致后才增加积分,重复回调不会重复到账。 微信小程序虚拟支付的服务端接口为:小程序先通过 `POST /api/token/wechat/session` 交换临时登录码,再由 `POST /api/token/charge`(`payScene=LITE`)返回已签名的 `requestVirtualPayment` 参数;其中 `productId` 直接采用套餐 ID(`Basic`、`Standard`、`Professional`、`Enterprise`)。小程序原生支付页仍需调用 `wx.requestVirtualPayment`,不能仅打开上述 H5 收银台 URL 来完成虚拟支付。微信回调地址配置为 `https://digital.99hui.com/api/token/payment/wechat/virtual/notify`。回调会复核签名、OpenID、环境、商品 ID 与实付金额,退款回调确认后才扣回积分。AppKey、AppSecret、session_key 均不得下发前端或写日志。