Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Implement WeChat Pay V3 subscription billing functionality (预约扣费功能/连续包月功能) #3688

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
Copilot wants to merge 3 commits into develop
base: develop
Choose a base branch
Loading
from copilot/fix-3593
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
194 changes: 194 additions & 0 deletions weixin-java-pay/SUBSCRIPTION_BILLING_USAGE.md
View file Open in desktop
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
# 微信支付预约扣费功能使用说明

## 概述

微信支付预约扣费功能(连续包月功能)允许商户在用户授权的情况下,按照约定的时间和金额,自动从用户的支付账户中扣取费用。主要适用于连续包月、订阅服务等场景。

## 功能特性

- **预约扣费**:创建未来某个时间点的扣费计划
- **查询预约**:查询已创建的扣费计划状态
- **取消预约**:取消已创建的扣费计划
- **立即扣费**:立即执行扣费操作
- **扣费记录查询**:查询历史扣费记录

## 快速开始

### 1. 获取服务实例

```java
// 通过 WxPayService 获取预约扣费服务
SubscriptionBillingService subscriptionService = wxPayService.getSubscriptionBillingService();
```

### 2. 创建预约扣费

```java
// 创建预约扣费请求
SubscriptionScheduleRequest request = new SubscriptionScheduleRequest();
request.setOutTradeNo("subscription_" + System.currentTimeMillis());
request.setOpenid("用户的openid");
request.setDescription("腾讯视频VIP会员");
request.setScheduleTime("2024年09月01日T10: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());
```

### 3. 查询预约扣费

```java
// 通过预约扣费ID查询
String subscriptionId = "从预约扣费结果中获取的ID";
SubscriptionQueryResult queryResult = subscriptionService.querySubscription(subscriptionId);
System.out.println("预约状态: " + queryResult.getStatus());
```

### 4. 取消预约扣费

```java
// 创建取消请求
SubscriptionCancelRequest cancelRequest = new SubscriptionCancelRequest();
cancelRequest.setSubscriptionId(subscriptionId);
cancelRequest.setCancelReason("用户主动取消");

// 取消预约扣费
SubscriptionCancelResult cancelResult = subscriptionService.cancelSubscription(cancelRequest);
System.out.println("取消结果: " + cancelResult.getStatus());
```

### 5. 立即扣费

```java
// 创建立即扣费请求
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());
```

### 6. 查询扣费记录

```java
// 创建查询请求
SubscriptionTransactionQueryRequest queryRequest = new SubscriptionTransactionQueryRequest();
queryRequest.setOpenid("用户的openid");
queryRequest.setBeginTime("2024年08月01日T00:00:00+08:00");
queryRequest.setEndTime("2024年08月31日T23: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());
}
```

## 扣费计划类型

- `MONTHLY`:按月扣费
- `WEEKLY`:按周扣费
- `DAILY`:按日扣费
- `YEARLY`:按年扣费

## 预约状态说明

- `SCHEDULED`:已预约
- `CANCELLED`:已取消
- `EXECUTED`:已执行
- `FAILED`:执行失败

## 交易状态说明

- `SUCCESS`:支付成功
- `REFUND`:转入退款
- `NOTPAY`:未支付
- `CLOSED`:已关闭
- `REVOKED`:已撤销(刷卡支付)
- `USERPAYING`:用户支付中
- `PAYERROR`:支付失败

## 注意事项

1. **用户授权**:使用预约扣费功能前,需要用户在微信内完成签约授权
2. **商户资质**:需要具备相应的业务资质才能开通此功能
3. **金额限制**:扣费金额需要在签约模板规定的范围内
4. **频率限制**:API调用有频率限制,请注意控制调用频次
5. **异常处理**:建议对所有API调用进行异常处理

## 相关文档

- [微信支付预约扣费API文档](https://pay.weixin.qq.com/doc/v3/merchant/4012161105)
- [微信支付开发指南](https://pay.weixin.qq.com/wiki/doc/apiv3/index.shtml)

## 示例完整代码

```java
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月01日T10: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);
}
}
}
```
View file Open in desktop
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package com.github.binarywang.wxpay.bean.subscriptionbilling;

import com.google.gson.annotations.SerializedName;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;

/**
* 扣费计划信息
* <pre>
* 文档地址:https://pay.weixin.qq.com/doc/v3/merchant/4012161105
* </pre>
*
* @author Binary Wang
*/
@Data
@NoArgsConstructor
public class BillingPlan implements Serializable {
private static final long serialVersionUID = 1L;

/**
* <pre>
* 字段名:计划类型
* 变量名:plan_type
* 是否必填:是
* 类型:string(32)
* 描述:
* 扣费计划类型
* MONTHLY:按月扣费
* WEEKLY:按周扣费
* DAILY:按日扣费
* YEARLY:按年扣费
* 示例值:MONTHLY
* </pre>
*/
@SerializedName("plan_type")
private String planType;

/**
* <pre>
* 字段名:扣费周期
* 变量名:period
* 是否必填:是
* 类型:int
* 描述:
* 扣费周期,配合plan_type使用
* 例如:plan_type为MONTHLY,period为1,表示每1个月扣费一次
* 示例值:1
* </pre>
*/
@SerializedName("period")
private Integer period;

/**
* <pre>
* 字段名:总扣费次数
* 变量名:total_count
* 是否必填:否
* 类型:int
* 描述:
* 总扣费次数,不填表示无限次扣费
* 示例值:12
* </pre>
*/
@SerializedName("total_count")
private Integer totalCount;

/**
* <pre>
* 字段名:已扣费次数
* 变量名:executed_count
* 是否必填:否
* 类型:int
* 描述:
* 已扣费次数,查询时返回
* 示例值:2
* </pre>
*/
@SerializedName("executed_count")
private Integer executedCount;

/**
* <pre>
* 字段名:计划开始时间
* 变量名:start_time
* 是否必填:否
* 类型:string(32)
* 描述:
* 计划开始时间,遵循rfc3339标准格式,格式为YYYY-MM-DDTHH:mm:ss+TIMEZONE
* 示例值:2018年06月08日T10:34:56+08:00
* </pre>
*/
@SerializedName("start_time")
private String startTime;

/**
* <pre>
* 字段名:计划结束时间
* 变量名:end_time
* 是否必填:否
* 类型:string(32)
* 描述:
* 计划结束时间,遵循rfc3339标准格式,格式为YYYY-MM-DDTHH:mm:ss+TIMEZONE
* 示例值:2019年06月08日T10:34:56+08:00
* </pre>
*/
@SerializedName("end_time")
private String endTime;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package com.github.binarywang.wxpay.bean.subscriptionbilling;

import com.google.gson.annotations.SerializedName;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;

/**
* 预约扣费金额信息
* <pre>
* 文档地址:https://pay.weixin.qq.com/doc/v3/merchant/4012161105
* </pre>
*
* @author Binary Wang
*/
@Data
@NoArgsConstructor
public class SubscriptionAmount implements Serializable {
private static final long serialVersionUID = 1L;

/**
* <pre>
* 字段名:总金额
* 变量名:total
* 是否必填:是
* 类型:int
* 描述:
* 订单总金额,单位为分
* 示例值:100
* </pre>
*/
@SerializedName("total")
private Integer total;

/**
* <pre>
* 字段名:货币类型
* 变量名:currency
* 是否必填:否
* 类型:string(16)
* 描述:
* CNY:人民币,境内商户号仅支持人民币
* 示例值:CNY
* </pre>
*/
@SerializedName("currency")
private String currency;
}
Loading

AltStyle によって変換されたページ (->オリジナル) /