import hashlib import hmac import json import os from types import SimpleNamespace from unittest.mock import Mock, patch from services import wechat_virtual_payment as virtual def test_build_payment_params_signs_the_exact_compact_payload(): order = SimpleNamespace(order_no="AV202609080001", plan_id="plan-1", price_cents=1000) plan = SimpleNamespace(id="plan-1", virtual_product_id="points_plan_1") env = { "WECHAT_VIRTUAL_ENV": "sandbox", "WECHAT_VIRTUAL_SANDBOX_APP_KEY": "sandbox-key", "WECHAT_VIRTUAL_OFFER_ID": "offer-1", } with patch.dict(os.environ, env, clear=False): result = virtual.build_payment_params(order=order, plan=plan, session_key="session-key") sign_data = result["signData"] assert sign_data == json.dumps({ "offerId": "offer-1", "buyQuantity": 1, "env": 1, "currencyType": "CNY", "productId": "points_plan_1", "goodsPrice": 1000, "outTradeNo": "AV202609080001", "attach": '{"orderNo":"AV202609080001","planId":"plan-1"}', }, ensure_ascii=False, separators=(",", ":")) assert result["paySig"] == hmac.new( b"sandbox-key", f"requestVirtualPayment&{sign_data}".encode(), hashlib.sha256 ).hexdigest() assert result["signature"] == hmac.new( b"session-key", sign_data.encode(), hashlib.sha256 ).hexdigest() def test_callback_signature_and_xml_body_are_supported(): with patch.dict(os.environ, {"WECHAT_VIRTUAL_CALLBACK_TOKEN": "callback-token"}): signature = hashlib.sha1("".join(sorted(["callback-token", "100", "nonce"])).encode()).hexdigest() assert virtual.verify_callback_signature(signature, "100", "nonce") payload = virtual.parse_callback_body( b"xpay_refund_notify1000" ) assert virtual.callback_value(payload, "event") == "xpay_refund_notify" assert virtual.callback_value(payload, "goodsinfo", "actualprice") == "1000" def test_xpay_request_uses_server_access_token_and_pay_signature(): token_response = Mock(status_code=200) token_response.json.return_value = {"access_token": "server-token", "expires_in": 7200} pay_response = Mock(status_code=200) pay_response.json.return_value = {"errcode": 0, "order": {"status": 2}} virtual._access_token_cache = ("", 0) env = { "WECHAT_MP_APP_ID": "wx-app", "WECHAT_MP_APP_SECRET": "wx-secret", "WECHAT_VIRTUAL_SANDBOX_APP_KEY": "sandbox-key", "WECHAT_VIRTUAL_ENV": "sandbox", } with patch.dict(os.environ, env), patch.object(virtual.httpx, "get", return_value=token_response), patch.object( virtual.httpx, "post", return_value=pay_response ) as post: result = virtual.query_order(openid="openid", order_no="AV1") assert result["order"]["status"] == 2 body = '{"openid":"openid","order_id":"AV1","env":1}' expected = hmac.new(b"sandbox-key", f"/xpay/query_order&{body}".encode(), hashlib.sha256).hexdigest() assert post.call_args.args[0] == "https://api.weixin.qq.com/xpay/query_order" assert post.call_args.kwargs["params"] == {"access_token": "server-token", "pay_sig": expected}