高级使用指南
View Source本文档提供了 ExWechatpay SDK 的高级使用指南和最佳实践,帮助开发者更好地使用 SDK 完成微信支付相关功能。
目录
异步通知处理
微信支付会通过异步通知的方式将支付结果、退款结果等信息推送给商户。正确处理这些通知是保证业务准确性的关键。
支付结果通知
defmodule MyAppWeb.WechatPayController do
use MyAppWeb, :controller
require Logger
# 支付结果通知处理
def payment_notify(conn, _params) do
# 1. 读取请求体和请求头
{:ok, body, conn} = read_body(conn)
headers = Enum.map(conn.req_headers, fn {k, v} -> {k, v} end)
# 2. 验证签名
if MyWechat.verify(headers, body) do
# 3. 解析通知数据
case Jason.decode(body) do
{:ok, notification} ->
# 4. 获取资源数据并解密(如果需要)
resource = notification["resource"]
resource_type = notification["resource_type"]
if resource_type == "encrypt-resource" do
case MyWechat.decrypt(resource) do
{:ok, decrypted_data} ->
case Jason.decode(decrypted_data) do
{:ok, payment_data} ->
# 5. 处理支付结果
handle_payment_result(payment_data)
# 6. 返回成功响应
json_response(conn, 200, %{code: "SUCCESS", message: "成功"})
{:error, _} ->
Logger.error("解析解密后的数据失败")
json_response(conn, 500, %{code: "FAIL", message: "解析解密后的数据失败"})
end
{:error, error} ->
Logger.error("解密数据失败: #{inspect(error)}")
json_response(conn, 500, %{code: "FAIL", message: "解密数据失败"})
end
else
# 数据未加密,直接处理
handle_payment_result(resource)
json_response(conn, 200, %{code: "SUCCESS", message: "成功"})
end
{:error, _} ->
Logger.error("解析通知数据失败")
json_response(conn, 400, %{code: "FAIL", message: "解析通知数据失败"})
end
else
# 签名验证失败
Logger.warn("通知签名验证失败")
json_response(conn, 401, %{code: "FAIL", message: "签名验证失败"})
end
end
# 退款结果通知处理
def refund_notify(conn, _params) do
{:ok, body, conn} = read_body(conn)
headers = Enum.map(conn.req_headers, fn {k, v} -> {k, v} end)
# 使用 SDK 提供的退款通知处理函数,自动完成验签和解密
case MyWechat.handle_refund_notification(headers, body) do
{:ok, notification} ->
# 处理退款结果
refund_status = get_in(notification, ["resource", "status"])
out_refund_no = get_in(notification, ["resource", "out_refund_no"])
# 更新业务系统中的退款状态
case update_refund_status(out_refund_no, refund_status) do
:ok ->
json_response(conn, 200, %{code: "SUCCESS", message: "成功"})
{:error, reason} ->
Logger.error("更新退款状态失败: #{reason}")
json_response(conn, 500, %{code: "FAIL", message: "处理退款结果失败"})
end
{:error, error} ->
Logger.error("处理退款通知失败: #{inspect(error)}")
json_response(conn, 400, %{code: "FAIL", message: "处理退款通知失败"})
end
end
# 处理支付结果
defp handle_payment_result(payment_data) do
# 从 payment_data 中提取关键信息
out_trade_no = payment_data["out_trade_no"]
transaction_id = payment_data["transaction_id"]
trade_state = payment_data["trade_state"]
# 根据 trade_state 更新订单状态
case trade_state do
"SUCCESS" ->
# 支付成功
update_order_status(out_trade_no, :paid, transaction_id)
"REFUND" ->
# 转入退款
update_order_status(out_trade_no, :refunding, transaction_id)
"NOTPAY" ->
# 未支付
update_order_status(out_trade_no, :pending, transaction_id)
"CLOSED" ->
# 已关闭
update_order_status(out_trade_no, :closed, transaction_id)
"USERPAYING" ->
# 用户支付中
update_order_status(out_trade_no, :processing, transaction_id)
"PAYERROR" ->
# 支付失败
update_order_status(out_trade_no, :failed, transaction_id)
_ ->
# 其他状态
Logger.warn("未处理的支付状态: #{trade_state}")
end
end
# 更新订单状态(示例)
defp update_order_status(out_trade_no, status, transaction_id) do
# 实际业务中,这里应该更新数据库中的订单状态
Logger.info("更新订单 #{out_trade_no} 状态为 #{status},微信支付订单号: #{transaction_id}")
end
# 更新退款状态(示例)
defp update_refund_status(out_refund_no, status) do
# 实际业务中,这里应该更新数据库中的退款状态
Logger.info("更新退款单 #{out_refund_no} 状态为 #{status}")
:ok
end
# JSON 响应辅助函数
defp json_response(conn, status, data) do
conn
|> put_resp_content_type("application/json")
|> send_resp(status, Jason.encode!(data))
end
end
配置回调路由
在 Phoenix 项目中,需要在路由文件中配置回调路径:
# lib/my_app_web/router.ex
defmodule MyAppWeb.Router do
use MyAppWeb, :router
# ...
scope "/api", MyAppWeb do
pipe_through :api
# 微信支付回调路由
post "/wechat_pay/payment_notify", WechatPayController, :payment_notify
post "/wechat_pay/refund_notify", WechatPayController, :refund_notify
end
end
证书管理
微信支付平台证书会定期更新,正确管理证书是确保支付通知验签正常工作的关键。
初始化证书
首次使用 SDK 时,需要获取并配置微信支付平台证书:
# 获取平台证书
{:ok, certificates} = MyWechat.get_certificates()
# 更新配置中的证书信息
cert_list = Enum.map(certificates["data"], fn cert ->
{cert["serial_no"], cert["certificate"]}
end)
MyWechat.update_config(wx_pubs: cert_list)
自动更新证书
为避免证书过期导致验签失败,建议启用自动更新证书功能:
# 在应用启动时启用自动更新证书
# Application 模块的 start/2 函数中
def start(_type, _args) do
children = [
# 其他子进程
MyWechat
]
# 启动子进程
{:ok, pid} = Supervisor.start_link(children, strategy: :one_for_one)
# 启用自动更新证书(每天更新一次)
MyWechat.enable_auto_update_certificates()
{:ok, pid}
end
手动更新证书
如果不想使用自动更新,也可以通过定时任务手动更新证书:
# 创建一个定时任务模块
defmodule MyApp.CertUpdateTask do
use GenServer
require Logger
def start_link(_) do
GenServer.start_link(__MODULE__, %{})
end
@impl true
def init(state) do
# 启动后立即执行一次更新
schedule_update()
{:ok, state}
end
@impl true
def handle_info(:update_certificates, state) do
Logger.info("开始更新微信支付平台证书")
case MyWechat.update_certificates() do
{:ok, _} ->
Logger.info("微信支付平台证书更新成功")
{:error, error} ->
Logger.error("微信支付平台证书更新失败: #{inspect(error)}")
end
# 安排下一次更新(24小时后)
schedule_update()
{:noreply, state}
end
defp schedule_update do
# 24小时 = 24 * 60 * 60 * 1000 毫秒
Process.send_after(self(), :update_certificates, 24 * 60 * 60 * 1000)
end
end
# 将此模块添加到应用的监督树中
children = [
# 其他子进程
MyApp.CertUpdateTask
]
错误处理与重试策略
微信支付 API 调用可能因网络问题、服务器繁忙等原因失败,适当的错误处理和重试策略可以提高系统稳定性。
全局错误处理
defmodule MyApp.WechatPayService do
require Logger
@doc """
创建支付订单并处理可能的错误
"""
def create_payment(params) do
case MyWechat.create_native_transaction(params) do
{:ok, result} ->
{:ok, result}
{:error, %ExWechatpay.Exception{message: "SYSTEMERROR", details: details}} ->
# 系统错误,可以重试
Logger.warn("微信支付系统错误,准备重试: #{inspect(details)}")
retry_create_payment(params, 3)
{:error, %ExWechatpay.Exception{message: "PARAM_ERROR", details: details}} ->
# 参数错误,需要修正参数
Logger.error("微信支付参数错误: #{inspect(details)}")
{:error, :invalid_params}
{:error, %ExWechatpay.Exception{message: "INVALID_REQUEST", details: details}} ->
# 无效请求
Logger.error("微信支付无效请求: #{inspect(details)}")
{:error, :invalid_request}
{:error, %ExWechatpay.Exception{message: "RESOURCE_ALREADY_EXISTS", details: details}} ->
# 资源已存在,可能是重复请求
Logger.warn("微信支付资源已存在: #{inspect(details)}")
# 查询订单获取之前创建的信息
out_trade_no = params["out_trade_no"]
MyWechat.query_transaction_by_out_trade_no(out_trade_no)
{:error, error} ->
# 其他错误
Logger.error("微信支付未知错误: #{inspect(error)}")
{:error, :unknown_error}
end
end
@doc """
使用递减重试次数的方式重试创建支付
"""
def retry_create_payment(_params, 0) do
{:error, :max_retries_reached}
end
def retry_create_payment(params, retries) do
# 等待一段时间后重试
:timer.sleep(1000)
case MyWechat.create_native_transaction(params) do
{:ok, result} ->
{:ok, result}
{:error, _} ->
Logger.warn("重试创建支付失败,剩余重试次数: #{retries - 1}")
retry_create_payment(params, retries - 1)
end
end
end
使用 Task.Supervisor 异步处理支付
对于需要高并发处理的场景,可以使用 Task.Supervisor 异步处理支付请求:
defmodule MyApp.PaymentProcessor do
use GenServer
require Logger
def start_link(_) do
GenServer.start_link(__MODULE__, %{}, name: __MODULE__)
end
@impl true
def init(_) do
# 启动任务监督者
{:ok, supervisor} = Task.Supervisor.start_link(name: MyApp.PaymentTaskSupervisor)
{:ok, %{supervisor: supervisor}}
end
@doc """
异步创建支付
"""
def async_create_payment(params) do
GenServer.cast(__MODULE__, {:create_payment, params})
end
@impl true
def handle_cast({:create_payment, params}, %{supervisor: supervisor} = state) do
# 启动异步任务处理支付
Task.Supervisor.async_nolink(supervisor, fn ->
try do
case MyApp.WechatPayService.create_payment(params) do
{:ok, result} ->
# 处理成功结果
handle_payment_success(params, result)
{:error, reason} ->
# 处理失败结果
handle_payment_failure(params, reason)
end
rescue
e ->
Logger.error("处理支付请求异常: #{inspect(e)}")
handle_payment_failure(params, :exception)
end
end)
{:noreply, state}
end
defp handle_payment_success(params, result) do
# 处理成功逻辑,如更新订单状态、发送通知等
Logger.info("支付创建成功: #{inspect(result)}")
# ...
end
defp handle_payment_failure(params, reason) do
# 处理失败逻辑,如标记失败、发送通知等
Logger.error("支付创建失败: #{inspect(reason)}")
# ...
end
end
自定义配置
SDK 允许通过自定义配置来满足不同的业务需求。
动态配置
在某些场景下,可能需要在运行时动态调整配置:
# 更新超时配置
MyWechat.update_config(timeout: 10000)
# 切换到沙箱环境
MyWechat.update_config(service_host: "api.mch.weixin.qq.com/sandboxnew")
# 更新日志级别
MyWechat.update_config(log_level: :debug)
基于环境的配置
可以基于不同的环境(开发、测试、生产)使用不同的配置:
# config/dev.exs
config :my_app, MyWechat,
appid: "wx_dev_app_id",
mchid: "dev_mch_id",
service_host: "api.mch.weixin.qq.com/sandboxnew", # 使用沙箱环境
# 其他配置...
log_level: :debug
# config/prod.exs
config :my_app, MyWechat,
appid: "wx_prod_app_id",
mchid: "prod_mch_id",
service_host: "api.mch.weixin.qq.com", # 使用生产环境
# 其他配置...
log_level: :info
覆盖初始化配置
可以通过覆盖 init/1
回调函数来自定义配置初始化逻辑:
defmodule MyWechat do
use ExWechatpay, otp_app: :my_app
def init(config) do
# 从环境变量中读取敏感配置
config =
config
|> maybe_put_env(:appid, "WECHAT_PAY_APPID")
|> maybe_put_env(:mchid, "WECHAT_PAY_MCHID")
|> maybe_put_env(:apiv3_key, "WECHAT_PAY_APIV3_KEY")
|> maybe_put_env(:client_key, "WECHAT_PAY_CLIENT_KEY")
# 根据环境选择不同的服务主机
config =
if Application.get_env(:my_app, :env) == :prod do
Keyword.put(config, :service_host, "api.mch.weixin.qq.com")
else
Keyword.put(config, :service_host, "api.mch.weixin.qq.com/sandboxnew")
end
{:ok, config}
end
# 辅助函数:如果环境变量存在,则使用环境变量的值
defp maybe_put_env(config, key, env_var) do
case System.get_env(env_var) do
nil -> config
value -> Keyword.put(config, key, value)
end
end
end
多商户支持
如果您的应用需要支持多个商户,可以创建多个微信支付客户端实例。
定义多个客户端模块
defmodule MyApp.WechatPayA do
use ExWechatpay, otp_app: :my_app
end
defmodule MyApp.WechatPayB do
use ExWechatpay, otp_app: :my_app
end
配置多个客户端
# config/config.exs
config :my_app, MyApp.WechatPayA,
appid: "wx_app_id_a",
mchid: "mch_id_a",
# 其他配置...
config :my_app, MyApp.WechatPayB,
appid: "wx_app_id_b",
mchid: "mch_id_b",
# 其他配置...
动态选择客户端
defmodule MyApp.PaymentService do
@doc """
根据商户 ID 选择合适的微信支付客户端
"""
def get_wechat_client(merchant_id) do
case merchant_id do
"merchant_a" -> MyApp.WechatPayA
"merchant_b" -> MyApp.WechatPayB
_ -> raise "未知的商户 ID: #{merchant_id}"
end
end
@doc """
创建支付订单
"""
def create_payment(merchant_id, params) do
client = get_wechat_client(merchant_id)
client.create_native_transaction(params)
end
@doc """
查询订单
"""
def query_order(merchant_id, out_trade_no) do
client = get_wechat_client(merchant_id)
client.query_transaction_by_out_trade_no(out_trade_no)
end
end
沙箱环境
微信支付提供了沙箱环境用于开发和测试。使用沙箱环境可以避免在开发过程中产生真实的交易。
配置沙箱环境
# config/dev.exs
config :my_app, MyWechat,
appid: "wx_sandbox_app_id",
mchid: "sandbox_mch_id",
service_host: "api.mch.weixin.qq.com/sandboxnew", # 关键配置:使用沙箱主机
# 其他配置与生产环境相同
沙箱环境测试
沙箱环境的使用方式与生产环境相同,但不会产生真实交易:
# 创建沙箱测试订单
{:ok, result} = MyWechat.create_native_transaction(%{
"description" => "沙箱测试商品",
"out_trade_no" => "SANDBOX_ORDER_001",
"amount" => %{
"total" => 100, # 1元
"currency" => "CNY"
}
})
# 使用沙箱环境的二维码进行测试支付
sandbox_code_url = result["code_url"]
完整支付流程示例
以下是一个完整的支付流程示例,从创建订单到处理支付结果:
1. 订单服务
defmodule MyApp.OrderService do
require Logger
alias MyApp.Repo
alias MyApp.Orders.Order
@doc """
创建订单并发起支付
"""
def create_order_and_pay(user_id, product_id, quantity) do
# 1. 创建订单记录
with {:ok, product} <- get_product(product_id),
{:ok, order} <- create_order(user_id, product, quantity),
{:ok, payment_result} <- create_payment(order) do
# 2. 返回支付信息
{:ok, %{order: order, payment: payment_result}}
end
end
# 获取商品信息
defp get_product(product_id) do
case Repo.get(MyApp.Products.Product, product_id) do
nil -> {:error, :product_not_found}
product -> {:ok, product}
end
end
# 创建订单记录
defp create_order(user_id, product, quantity) do
# 计算订单金额
amount = product.price * quantity
# 生成唯一订单号
out_trade_no = "ORDER_#{generate_order_id()}"
# 创建订单记录
%Order{}
|> Order.changeset(%{
user_id: user_id,
product_id: product.id,
quantity: quantity,
amount: amount,
out_trade_no: out_trade_no,
status: "pending"
})
|> Repo.insert()
end
# 创建支付
defp create_payment(order) do
# 准备支付参数
payment_params = %{
"description" => "购买商品 #{order.product_id}",
"out_trade_no" => order.out_trade_no,
"amount" => %{
"total" => trunc(order.amount * 100), # 转换为分
"currency" => "CNY"
}
}
# 根据场景选择不同的支付方式
case get_payment_scene() do
:native ->
# 扫码支付
MyWechat.create_native_transaction(payment_params)
:jsapi ->
# JSAPI 支付(需要 openid)
params_with_payer = Map.put(payment_params, "payer", %{
"openid" => get_user_openid(order.user_id)
})
MyWechat.create_jsapi_transaction(params_with_payer)
:h5 ->
# H5 支付
params_with_scene = Map.put(payment_params, "scene_info", %{
"payer_client_ip" => get_client_ip(),
"device_id" => get_device_id()
})
MyWechat.create_h5_transaction(params_with_scene)
end
end
# 生成订单 ID
defp generate_order_id do
# 生成一个带时间戳的唯一 ID
timestamp = :os.system_time(:millisecond)
random = :rand.uniform(999999)
"#{timestamp}#{random}"
end
# 获取支付场景(示例)
defp get_payment_scene do
# 根据实际业务逻辑决定使用哪种支付方式
:native
end
# 获取用户 OpenID(示例)
defp get_user_openid(user_id) do
# 实际业务中,从数据库或缓存中获取用户的 OpenID
"example_openid"
end
# 获取客户端 IP(示例)
defp get_client_ip do
# 实际业务中,从请求中获取客户端 IP
"127.0.0.1"
end
# 获取设备 ID(示例)
defp get_device_id do
# 实际业务中,从请求中获取设备 ID
"example_device_id"
end
@doc """
处理支付结果
"""
def handle_payment_result(payment_data) do
# 从支付数据中提取关键信息
out_trade_no = payment_data["out_trade_no"]
transaction_id = payment_data["transaction_id"]
trade_state = payment_data["trade_state"]
# 查找订单
with %Order{} = order <- Repo.get_by(Order, out_trade_no: out_trade_no) do
# 更新订单状态
new_status = case trade_state do
"SUCCESS" -> "paid"
"REFUND" -> "refunding"
"NOTPAY" -> "pending"
"CLOSED" -> "closed"
"USERPAYING" -> "processing"
"PAYERROR" -> "failed"
_ -> "unknown"
end
# 更新订单记录
order
|> Order.changeset(%{
status: new_status,
transaction_id: transaction_id,
payment_time: payment_data["success_time"]
})
|> Repo.update()
# 如果支付成功,触发后续业务流程
if new_status == "paid" do
process_paid_order(order)
end
else
nil ->
Logger.error("找不到订单: #{out_trade_no}")
{:error, :order_not_found}
end
end
# 处理已支付订单的后续业务流程
defp process_paid_order(order) do
# 实际业务中,可能包括:
# - 发送支付成功通知
# - 更新库存
# - 生成发货单
# - 记录流水账
Logger.info("处理已支付订单: #{order.id}")
# 示例:发送支付成功通知
MyApp.NotificationService.send_payment_success_notification(order)
end
end
2. 支付控制器
defmodule MyAppWeb.PaymentController do
use MyAppWeb, :controller
alias MyApp.OrderService
@doc """
创建支付
"""
def create(conn, %{"product_id" => product_id, "quantity" => quantity}) do
# 获取当前用户 ID
user_id = conn.assigns.current_user.id
case OrderService.create_order_and_pay(user_id, product_id, quantity) do
{:ok, %{order: order, payment: payment_result}} ->
# 根据支付方式返回不同的结果
cond do
Map.has_key?(payment_result, "code_url") ->
# Native 支付,返回二维码链接
render(conn, "native_payment.json", %{
order_id: order.id,
code_url: payment_result["code_url"]
})
Map.has_key?(payment_result, "prepay_id") ->
# JSAPI 支付,生成支付参数
pay_params = MyWechat.miniapp_payform(payment_result["prepay_id"])
render(conn, "jsapi_payment.json", %{
order_id: order.id,
pay_params: pay_params
})
Map.has_key?(payment_result, "h5_url") ->
# H5 支付,返回支付链接
render(conn, "h5_payment.json", %{
order_id: order.id,
h5_url: payment_result["h5_url"]
})
end
{:error, :product_not_found} ->
conn
|> put_status(:not_found)
|> render("error.json", %{message: "商品不存在"})
{:error, %Ecto.Changeset{} = changeset} ->
conn
|> put_status(:unprocessable_entity)
|> render("error.json", %{changeset: changeset})
{:error, error} ->
conn
|> put_status(:internal_server_error)
|> render("error.json", %{message: "创建支付失败", error: error})
end
end
@doc """
查询支付状态
"""
def query(conn, %{"order_id" => order_id}) do
# 查询订单
order = MyApp.Repo.get!(MyApp.Orders.Order, order_id)
# 查询支付状态
case MyWechat.query_transaction_by_out_trade_no(order.out_trade_no) do
{:ok, payment_info} ->
render(conn, "payment_status.json", %{
order_id: order.id,
status: payment_info["trade_state"],
description: payment_info["trade_state_desc"]
})
{:error, _error} ->
conn
|> put_status(:internal_server_error)
|> render("error.json", %{message: "查询支付状态失败"})
end
end
@doc """
关闭支付
"""
def close(conn, %{"order_id" => order_id}) do
# 查询订单
order = MyApp.Repo.get!(MyApp.Orders.Order, order_id)
# 关闭支付
case MyWechat.close_transaction(order.out_trade_no) do
:ok ->
# 更新订单状态为已关闭
MyApp.Orders.update_order_status(order, "closed")
conn
|> put_status(:ok)
|> render("success.json", %{message: "支付已关闭"})
{:error, _error} ->
conn
|> put_status(:internal_server_error)
|> render("error.json", %{message: "关闭支付失败"})
end
end
end
3. 退款控制器
defmodule MyAppWeb.RefundController do
use MyAppWeb, :controller
@doc """
申请退款
"""
def create(conn, %{"order_id" => order_id, "reason" => reason}) do
# 查询订单
order = MyApp.Repo.get!(MyApp.Orders.Order, order_id)
# 生成退款单号
out_refund_no = "REFUND_#{:os.system_time(:millisecond)}"
# 申请退款
refund_params = %{
"out_refund_no" => out_refund_no,
"out_trade_no" => order.out_trade_no,
"reason" => reason,
"amount" => %{
"refund" => trunc(order.amount * 100), # 全额退款,单位为分
"total" => trunc(order.amount * 100),
"currency" => "CNY"
}
}
case MyWechat.create_refund(refund_params) do
{:ok, refund_info} ->
# 创建退款记录
{:ok, refund} = MyApp.Refunds.create_refund(%{
order_id: order.id,
out_refund_no: out_refund_no,
refund_id: refund_info["refund_id"],
amount: order.amount,
reason: reason,
status: refund_info["status"]
})
# 更新订单状态
MyApp.Orders.update_order_status(order, "refunding")
render(conn, "refund.json", %{
refund_id: refund.id,
status: refund_info["status"]
})
{:error, error} ->
conn
|> put_status(:internal_server_error)
|> render("error.json", %{message: "申请退款失败", error: error})
end
end
@doc """
查询退款
"""
def show(conn, %{"id" => refund_id}) do
# 查询退款记录
refund = MyApp.Repo.get!(MyApp.Refunds.Refund, refund_id)
# 查询退款状态
case MyWechat.query_refund(refund.out_refund_no) do
{:ok, refund_info} ->
# 更新退款状态
{:ok, updated_refund} = MyApp.Refunds.update_refund_status(refund, refund_info["status"])
render(conn, "refund_status.json", %{
refund: updated_refund,
status: refund_info["status"],
success_time: refund_info["success_time"]
})
{:error, error} ->
conn
|> put_status(:internal_server_error)
|> render("error.json", %{message: "查询退款失败", error: error})
end
end
end
4. 前端集成示例
微信小程序支付集成
// 微信小程序支付
function requestPayment(payParams) {
return new Promise((resolve, reject) => {
wx.requestPayment({
timeStamp: payParams.timeStamp,
nonceStr: payParams.nonceStr,
package: payParams.package,
signType: payParams.signType,
paySign: payParams.paySign,
success: function(res) {
resolve(res);
},
fail: function(err) {
reject(err);
}
});
});
}
// 调用支付接口
async function createOrder(productId, quantity) {
try {
// 调用创建订单接口
const response = await fetch('/api/payments', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
product_id: productId,
quantity: quantity
})
});
const result = await response.json();
if (result.pay_params) {
// JSAPI 支付,直接调起支付
await requestPayment(result.pay_params);
return { success: true, orderId: result.order_id };
} else {
throw new Error('不支持的支付方式');
}
} catch (error) {
console.error('支付失败', error);
return { success: false, error: error.message };
}
}
Web 端扫码支付集成
// Web 端扫码支付
async function createQRCodePayment(productId, quantity) {
try {
// 调用创建订单接口
const response = await fetch('/api/payments', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
product_id: productId,
quantity: quantity
})
});
const result = await response.json();
if (result.code_url) {
// 生成二维码
generateQRCode(result.code_url, 'qrcode-container');
// 开始轮询订单状态
startPollingOrderStatus(result.order_id);
return { success: true, orderId: result.order_id };
} else {
throw new Error('不支持的支付方式');
}
} catch (error) {
console.error('创建支付失败', error);
return { success: false, error: error.message };
}
}
// 生成二维码
function generateQRCode(codeUrl, containerId) {
// 使用 qrcode.js 库生成二维码
new QRCode(document.getElementById(containerId), {
text: codeUrl,
width: 256,
height: 256
});
}
// 轮询订单状态
function startPollingOrderStatus(orderId) {
const intervalId = setInterval(async () => {
try {
const response = await fetch(`/api/payments/${orderId}/status`);
const result = await response.json();
// 显示支付状态
updatePaymentStatus(result.status, result.description);
// 如果支付成功或已关闭,停止轮询
if (['SUCCESS', 'CLOSED', 'PAYERROR'].includes(result.status)) {
clearInterval(intervalId);
if (result.status === 'SUCCESS') {
// 支付成功,跳转到成功页面
window.location.href = `/orders/${orderId}/success`;
}
}
} catch (error) {
console.error('查询支付状态失败', error);
}
}, 3000); // 每 3 秒查询一次
// 5 分钟后自动停止轮询
setTimeout(() => {
clearInterval(intervalId);
}, 5 * 60 * 1000);
}
// 更新支付状态显示
function updatePaymentStatus(status, description) {
const statusElement = document.getElementById('payment-status');
statusElement.textContent = description || status;
// 根据状态设置不同样式
statusElement.className = `payment-status payment-status-${status.toLowerCase()}`;
}
H5 支付集成
// H5 支付
async function createH5Payment(productId, quantity) {
try {
// 调用创建订单接口
const response = await fetch('/api/payments', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
product_id: productId,
quantity: quantity
})
});
const result = await response.json();
if (result.h5_url) {
// 记录订单 ID,用于支付完成后查询
localStorage.setItem('current_order_id', result.order_id);
// 跳转到微信支付页面
window.location.href = result.h5_url;
return { success: true, orderId: result.order_id };
} else {
throw new Error('不支持的支付方式');
}
} catch (error) {
console.error('创建支付失败', error);
return { success: false, error: error.message };
}
}
// H5 支付完成后的回调处理
function handleH5PaymentReturn() {
// 从本地存储获取订单 ID
const orderId = localStorage.getItem('current_order_id');
if (orderId) {
// 查询订单状态
fetch(`/api/payments/${orderId}/status`)
.then(response => response.json())
.then(result => {
// 显示支付结果
if (result.status === 'SUCCESS') {
showPaymentSuccess();
} else {
showPaymentFailed(result.description);
}
})
.catch(error => {
console.error('查询支付状态失败', error);
showPaymentFailed('查询支付状态失败');
})
.finally(() => {
// 清除本地存储的订单 ID
localStorage.removeItem('current_order_id');
});
}
}
// 页面加载时检查是否是支付回调
document.addEventListener('DOMContentLoaded', function() {
// 检查 URL 参数或其他标记,判断是否是从微信支付页面返回
const isPaymentReturn = new URLSearchParams(window.location.search).has('payment_return');
if (isPaymentReturn) {
handleH5PaymentReturn();
}
});
以上示例展示了一个完整的微信支付集成流程,包括创建支付、查询状态、处理回调和前端集成。实际项目中,可能需要根据具体业务需求进行调整和优化。