WxJava 微信支付预约扣费(连续包月)功能实战指南
【免费下载链接】WxJava微信开发 Java SDK ,支持包括微信支付,开放平台,小程序,企业微信,视频号,公众号等的后端开发项目地址: https://gitcode.com/gh_mirrors/wx/WxJava
导读
本文基于 WxJava 微信支付 SDK 的subscriptionbilling模块,完整讲解"预约扣费(连续包月)"能力的接入与使用。该功能允许商户在用户签约授权后,按约定时间与金额自动从用户支付账户扣费,是视频会员、云服务、知识付费等订阅制业务的标准支付方案。读完本文,你将掌握从服务实例获取、预约创建、查询、取消、立即扣费到扣费记录查询的完整调用链,并了解各请求参数在源码中的字段映射与底层实现。
功能特性
微信支付预约扣费(连续包月)功能在 WxJava 中由SubscriptionBillingService统一封装,通过 SubscriptionBillingService.java 对外暴露以下五个能力:
| 能力 | 接口方法 | 说明 |
|---|---|---|
| 预约扣费 | scheduleSubscription | 创建未来某个时间点的扣费计划,可附带周期计划实现连续包月 |
| 查询预约 | querySubscription | 按subscription_id查询已创建的扣费计划状态 |
| 取消预约 | cancelSubscription | 取消尚未执行的扣费计划 |
| 立即扣费 | instantBilling | 立即执行扣费,常用于补扣失败费用或特殊情况即时扣费 |
| 扣费记录查询 | queryTransactions | 按时间窗口分页查询历史扣费记录 |
快速开始
1. 获取服务实例
预约扣费服务无需单独构建,直接通过WxPayService获取:
SubscriptionBillingService subscriptionService = wxPayService.getSubscriptionBillingService();从源码看,该服务由BaseWxPayServiceImpl在内部以new SubscriptionBillingServiceImpl(this)的方式持有(见 BaseWxPayServiceImpl.java),并通过 WxPayService.java 的getSubscriptionBillingService()方法暴露。因此你只需保证wxPayService已正确配置(含商户号、APIv3 密钥、商户证书序列号与私钥),即可直接使用。
2. 创建预约扣费
// 创建预约扣费请求 SubscriptionScheduleRequest request = new SubscriptionScheduleRequest(); request.setOutTradeNo("subscription_" + System.currentTimeMillis()); request.setOpenid("用户的openid"); request.setDescription("腾讯视频VIP会员"); request.setScheduleTime("2024-09-01T10:00:00+08:00"); // 设置扣费金额 SubscriptionAmount amount = new SubscriptionAmount(); amount.setTotal(3000); // 30元,单位为分 amount.setCurrency("CNY"); request.setAmount(amount); // 设置扣费计划(可选) BillingPlan billingPlan = new BillingPlan(); billingPlan.setPlanType("MONTHLY"); // 按月扣费 billingPlan.setPeriod(1); // 每1个月 billingPlan.setTotalCount(12); // 总共12次 request.setBillingPlan(billingPlan); // 发起预约扣费 SubscriptionScheduleResult result = subscriptionService.scheduleSubscription(request); System.out.println("预约扣费ID: " + result.getSubscriptionId());预约扣费请求参数详解
SubscriptionScheduleRequest(源码见 SubscriptionScheduleRequest.java)通过@SerializedName注解完成 Java 字段与微信 API JSON 字段的映射,各参数说明如下:
| 参数 | 对应JSON字段 | 必填 | 类型 | 说明 |
|---|---|---|---|---|
outTradeNo | out_trade_no | 是 | string(32) | 商户系统内部订单号,只能是数字、大小写字母、_、-、*,同一商户号下唯一 |
openid | openid | 是 | string(128) | 用户在直连商户 appid 下的唯一标识 |
description | description | 是 | string(127) | 订单描述,如"腾讯充值中心-QQ会员充值" |
amount | amount | 是 | object | 预约扣费金额信息,见下节 |
scheduleTime | schedule_time | 是 | string(32) | 预约扣费时间,遵循 RFC3339,格式YYYY-MM-DDTHH:mm:ss+TIMEZONE,如2018-06-08T10:34:56+08:00 |
billingPlan | billing_plan | 否 | object | 扣费计划信息,用于连续包月等场景 |
notifyUrl | notify_url | 否 | string(256) | 异步接收微信支付结果通知的回调地址,必须为外网可访问的 URL 且不能携带参数 |
attach | attach | 否 | string(128) | 附加数据,在查询 API 和支付通知中原样返回,可作为自定义参数使用 |
金额与扣费计划对象
SubscriptionAmount(SubscriptionAmount.java):
total:订单总金额,单位为分(int 类型);currency:货币类型,CNY表示人民币,境内商户号仅支持人民币。
BillingPlan(BillingPlan.java):
planType(plan_type,必填):计划类型,取值MONTHLY/WEEKLY/DAILY/YEARLY;period(必填):扣费周期,配合planType使用,例如planType=MONTHLY, period=1表示每 1 个月扣费一次;totalCount(total_count,选填):总扣费次数,不填表示无限次扣费;executedCount(executed_count,选填):已扣费次数,查询时由微信返回;startTime(start_time,选填)与endTime(end_time,选填):计划开始/结束时间,同样遵循 RFC3339 格式。
3. 查询预约扣费
// 通过预约扣费ID查询 String subscriptionId = "从预约扣费结果中获取的ID"; SubscriptionQueryResult queryResult = subscriptionService.querySubscription(subscriptionId); System.out.println("预约状态: " + queryResult.getStatus());subscription_id即创建预约时返回的SubscriptionScheduleResult.getSubscriptionId()。查询返回结果包含预约状态status、预约时间schedule_time、创建时间create_time、金额amount以及扣费计划billing_plan等字段(见 SubscriptionScheduleResult.java)。
4. 取消预约扣费
// 创建取消请求 SubscriptionCancelRequest cancelRequest = new SubscriptionCancelRequest(); cancelRequest.setSubscriptionId(subscriptionId); cancelRequest.setCancelReason("用户主动取消"); // 取消预约扣费 SubscriptionCancelResult cancelResult = subscriptionService.cancelSubscription(cancelRequest); System.out.println("取消结果: " + cancelResult.getStatus());SubscriptionCancelRequest(SubscriptionCancelRequest.java)仅有两个字段:必填的subscriptionId(subscription_id,微信支付预约扣费 ID)和选填的cancelReason(cancel_reason,取消原因描述,string(256))。
5. 立即扣费
立即扣费用于在预约计划之外临时发起扣费,例如补扣上月会员费、处理失败重试等:
// 创建立即扣费请求 SubscriptionInstantBillingRequest instantRequest = new SubscriptionInstantBillingRequest(); instantRequest.setOutTradeNo("instant_" + System.currentTimeMillis()); instantRequest.setOpenid("用户的openid"); instantRequest.setDescription("补扣上月会员费"); // 设置扣费金额 SubscriptionAmount instantAmount = new SubscriptionAmount(); instantAmount.setTotal(3000); // 30元 instantAmount.setCurrency("CNY"); instantRequest.setAmount(instantAmount); // 执行立即扣费 SubscriptionInstantBillingResult instantResult = subscriptionService.instantBilling(instantRequest); System.out.println("扣费结果: " + instantResult.getTradeState());SubscriptionInstantBillingRequest(SubscriptionInstantBillingRequest.java)字段与预约扣费请求大体一致,包括必填的outTradeNo、openid、description、amount,以及选填的notifyUrl、attach(注意:立即扣费请求不包含billing_plan与schedule_time)。
6. 查询扣费记录
// 创建查询请求 SubscriptionTransactionQueryRequest queryRequest = new SubscriptionTransactionQueryRequest(); queryRequest.setOpenid("用户的openid"); queryRequest.setBeginTime("2024-08-01T00:00:00+08:00"); queryRequest.setEndTime("2024-08-31T23:59:59+08:00"); queryRequest.setLimit(20); queryRequest.setOffset(0); // 查询扣费记录 SubscriptionTransactionQueryResult transactionResult = subscriptionService.queryTransactions(queryRequest); System.out.println("总记录数: " + transactionResult.getTotalCount()); for (SubscriptionTransactionQueryResult.SubscriptionTransaction transaction : transactionResult.getData()) { System.out.println("订单号: " + transaction.getOutTradeNo() + ", 状态: " + transaction.getTradeState()); }查询结果SubscriptionTransactionQueryResult(SubscriptionTransactionQueryResult.java)包含总数量totalCount与扣费记录列表data。每条记录SubscriptionTransaction字段包括:微信支付订单号transactionId、商户订单号outTradeNo、预约扣费 IDsubscriptionId(仅预约扣费产生的交易有此字段)、交易状态tradeState、支付完成时间successTime、扣费金额amount、用户标识openid、订单描述description及附加数据attach。
扣费计划类型
BillingPlan.planType支持的取值:
MONTHLY:按月扣费WEEKLY:按周扣费DAILY:按日扣费YEARLY:按年扣费
配合period使用,例如MONTHLY + period=2表示每 2 个月扣费一次;totalCount不填则代表无限期扣费,直到用户主动解约。
预约状态说明
预约扣费计划(SubscriptionScheduleResult.status)可能处于以下状态:
SCHEDULED:已预约(待执行)CANCELLED:已取消EXECUTED:已执行FAILED:执行失败
交易状态说明
扣费记录(SubscriptionTransaction.tradeState)的取值与微信支付通用交易状态一致:
SUCCESS:支付成功REFUND:转入退款NOTPAY:未支付CLOSED:已关闭REVOKED:已撤销(刷卡支付)USERPAYING:用户支付中PAYERROR:支付失败
源码级实现原理
从 SubscriptionBillingServiceImpl.java 可以看到,五个接口方法均复用WxPayService的统一 APIv3 请求通道(postV3/getV3),并通过 Gson 完成请求序列化与响应反序列化,对应端点如下:
| 方法 | HTTP | 请求URL(前缀 + 路径) | 是否需要证书 |
|---|---|---|---|
scheduleSubscription | POST | /v3/subscription-billing/schedule | 是 |
querySubscription | GET | /v3/subscription-billing/schedule/{subscription_id} | 否 |
cancelSubscription | POST | /v3/subscription-billing/schedule/{subscription_id}/cancel | 是 |
instantBilling | POST | /v3/subscription-billing/instant-billing | 是 |
queryTransactions | GET | /v3/subscription-billing/transactions?openid=&begin_time=&end_time=&limit=&offset= | 否 |
URL 前缀来自payService.getPayBaseUrl()(默认https://api.mch.weixin.qq.com),服务内所有 APIv3 请求统一走该基础地址。几个值得注意的实现细节:
- 取消预约:
cancelSubscription将subscription_id拼接进 URL 路径,请求体仍整体序列化发送,其中subscription_id字段同时存在于 URL 与 body; - 记录查询的分页与过滤:
queryTransactions内部按openid、begin_time、end_time、limit、offset顺序拼接查询串,limit与offset用于分页,时间字段均采用 RFC3339 格式;查询走 GET 请求,不需要商户证书; - 异常处理:所有方法统一抛出
WxPayException,由底层 APIv3 通道在请求失败时封装错误码与错误信息。
注意事项
- 用户授权:使用预约扣费功能前,需要用户在微信内完成签约授权,未签约用户无法发起预约扣费;
- 商户资质:需要具备相应的业务资质才能开通此功能,且 APIv3 密钥、商户证书序列号与私钥必须正确配置;
- 金额限制:扣费金额需要在签约模板规定的范围内,
total单位为分; - 频率限制:API 调用有频率限制,请注意控制调用频次,避免触发限流;
- 异常处理:建议对所有 API 调用进行异常处理(捕获
WxPayException),并对支付结果通知做签名验签与幂等处理; - 时间格式:所有时间字段必须遵循 RFC3339 标准格式(如
2024-09-01T10:00:00+08:00),否则微信侧会校验失败; - 取消时机:取消操作通常只对
SCHEDULED(已预约未执行)状态的计划生效,取消后状态变为CANCELLED。
示例完整代码
将上述步骤串联起来,即得到完整的可运行示例(类路径对应源码包com.github.binarywang.wxpay.bean.subscriptionbilling):
import com.github.binarywang.wxpay.service.SubscriptionBillingService; import com.github.binarywang.wxpay.bean.subscriptionbilling.*; public class SubscriptionBillingExample { private SubscriptionBillingService subscriptionService; public void example() throws Exception { // 1. 创建预约扣费 SubscriptionScheduleRequest request = new SubscriptionScheduleRequest(); request.setOutTradeNo("subscription_" + System.currentTimeMillis()); request.setOpenid("用户openid"); request.setDescription("VIP会员续费"); request.setScheduleTime("2024-09-01T10:00:00+08:00"); SubscriptionAmount amount = new SubscriptionAmount(); amount.setTotal(3000); amount.setCurrency("CNY"); request.setAmount(amount); BillingPlan plan = new BillingPlan(); plan.setPlanType("MONTHLY"); plan.setPeriod(1); plan.setTotalCount(12); request.setBillingPlan(plan); SubscriptionScheduleResult result = subscriptionService.scheduleSubscription(request); // 2. 查询预约状态 SubscriptionQueryResult query = subscriptionService.querySubscription(result.getSubscriptionId()); // 3. 如需取消 if ("SCHEDULED".equals(query.getStatus())) { SubscriptionCancelRequest cancelReq = new SubscriptionCancelRequest(); cancelReq.setSubscriptionId(result.getSubscriptionId()); cancelReq.setCancelReason("用户取消"); SubscriptionCancelResult cancelResult = subscriptionService.cancelSubscription(cancelReq); } } }相关代码与文档索引
- 服务接口:SubscriptionBillingService.java
- 服务实现:SubscriptionBillingServiceImpl.java
- 服务获取入口:WxPayService.java、BaseWxPayServiceImpl.java
- 请求/响应模型:weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/subscriptionbilling/ 目录下的 11 个 Bean 类
- 使用文档:本指南的原始出处 SUBSCRIPTION_BILLING_USAGE.md
- 更多用法:微信支付模块的 MULTI_APPID_USAGE.md(多商户号场景)与 CONNECTION_POOL.md(连接池调优)可帮助你完善生产环境部署
【免费下载链接】WxJava微信开发 Java SDK ,支持包括微信支付,开放平台,小程序,企业微信,视频号,公众号等的后端开发项目地址: https://gitcode.com/gh_mirrors/wx/WxJava
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考