使用的maven版本:2.9.11
由于ijpay中提供的实体类没有设置回调参数的属性, 这里是自定义一个实体类:InitiateBatchTransferRequest代码如下:
package com.foo.web.controller.pay.wxpay;
import com.ijpay.wxpay.model.v3.TransferDetailInput;
import lombok.Getter;
import lombok.Setter;
import java.util.List;
public class InitiateBatchTransferRequest {
private String appid; //小程序APPID
private String out_batch_no; //商户单号
private String batch_name; //转账批次名称
private String batch_remark; //转账批次备注
private String notify_url; //回调地址
private Integer total_amount; //转账总金额
private Integer total_num; //转账总笔数
private List<TransferDetailInput> transfer_detail_list;//转账明细列表
private String transfer_scene_id;//转账场景id
public InitiateBatchTransferRequest() {
}
public InitiateBatchTransferRequest(String appid, String out_batch_no, String batch_name, String batch_remark, Integer total_amount, Integer total_num, List<TransferDetailInput> transfer_detail_list, String transfer_scene_id) {
this.appid = appid;
this.out_batch_no = out_batch_no;
this.batch_name = batch_name;
this.batch_remark = batch_remark;
this.total_amount = total_amount;
this.total_num = total_num;
this.transfer_detail_list = transfer_detail_list;
this.transfer_scene_id = transfer_scene_id;
}
public String getAppid() {
return this.appid;
}
public String getOut_batch_no() {
return this.out_batch_no;
}
public String getBatch_name() {
return this.batch_name;
}
public String getNotify_url() {
return this.notify_url;
}
public String getBatch_remark() {
return this.batch_remark;
}
public Integer getTotal_amount() {
return this.total_amount;
}
public Integer getTotal_num() {
return this.total_num;
}
public List<TransferDetailInput> getTransfer_detail_list() {
return this.transfer_detail_list;
}
public String getTransfer_scene_id() {
return this.transfer_scene_id;
}
public InitiateBatchTransferRequest setAppid(String appid) {
this.appid = appid;
return this;
}
public InitiateBatchTransferRequest setOut_batch_no(String out_batch_no) {
this.out_batch_no = out_batch_no;
return this;
}
public InitiateBatchTransferRequest setBatch_name(String batch_name) {
this.batch_name = batch_name;
return this;
}
public InitiateBatchTransferRequest setBatch_remark(String batch_remark) {
this.batch_remark = batch_remark;
return this;
}
public InitiateBatchTransferRequest setTotal_amount(Integer total_amount) {
this.total_amount = total_amount;
return this;
}
public InitiateBatchTransferRequest setTotal_num(Integer total_num) {
this.total_num = total_num;
return this;
}
public InitiateBatchTransferRequest setTransfer_detail_list(List<TransferDetailInput> transfer_detail_list) {
this.transfer_detail_list = transfer_detail_list;
return this;
}
public InitiateBatchTransferRequest setTransfer_scene_id(String transfer_scene_id) {
this.transfer_scene_id = transfer_scene_id;
return this;
}
public InitiateBatchTransferRequest setNotify_url(String notify_url) {
this.notify_url = notify_url;
return this;
}
}
然后直接写接口此处是单笔转账方法:
@RequestMapping("/batchTransfer")
@ResponseBody
public String batchTransfer(String openId, BigDecimal fee) throws Exception {
String out_batch_no = PayKit.generateStr();
InitiateBatchTransferRequest batchTransferModel = new InitiateBatchTransferRequest()
.setAppid("APPID")
.setOut_batch_no(out_batch_no)
.setBatch_name("转账批次名称")
.setBatch_remark("批次备注")
.setTotal_amount(fee.multiply(new BigDecimal("100")).intValue())
.setTotal_num(1)
.setNotify_url("回调地址")
.setTransfer_detail_list(Collections.singletonList(
new TransferDetailInput()
.setOut_detail_no(PayKit.generateStr())
.setTransfer_amount(fee.multiply(new BigDecimal("100")).intValue())
.setTransfer_remark("明细备注")
.setOpenid(openId)));
log.info("发起商家转账请求参数 {}", JSONUtil.toJsonStr(batchTransferModel));
IJPayHttpResponse response = WxPayApi.v3(
RequestMethodEnum.POST,
WxDomainEnum.CHINA.toString(),
TransferApiEnum.TRANSFER_BATCHES.toString(),
"商户号",
getSerialNumber(),
null,
"apiclient_key.pem",
JSONUtil.toJsonStr(batchTransferModel)
);
log.info("发起商家转账响应 {}", response);
if (response.getStatus() == 200) {
JSONObject jsonObject = JSONObject.parseObject(JSONUtil.toJsonStr(response.getBody()));
System.out.println("batch_status:" + jsonObject.getString("batch_status"));
System.out.println("batch_id:" + jsonObject.getString("batch_id"));
//处理你的业务
return "OK";
} else {
return "ERROR";
}
}
上述代码块中用到的方法getSerialNumber()如下:
private String getSerialNumber() {
if (StrUtil.isEmpty(serialNo)) {
// 获取证书序列号
X509Certificate certificate = PayKit.getCertificate("apiclient_cert.pem");
if (null != certificate) {
serialNo = certificate.getSerialNumber().toString(16).toUpperCase();
// 提前两天检查证书是否有效
boolean isValid = PayKit.checkCertificateIsValid(certificate, "商户号", -2);
log.info("证书是否可用 {} 证书有效期为 {}", isValid, DateUtil.format(certificate.getNotAfter(), DatePattern.NORM_DATETIME_PATTERN));
}
}
System.out.println("serialNo:" + serialNo);
return serialNo;
}
上述代码注意点, 用到的证书有两个:apiclient_cert.pem,apiclient_key.pem都是由商户API证书中下载所得
因为用户在调用转账接口后, 返回的订单状态都是已受理, 故而设置了回调地址, 起到监听管理员通过转账请求后刷新订单的作用
注意:由于回调中微信返回的为密文解密后才能使用, 所需需要验签和解密的过程,其中需要用到APIv3的密钥和平台证书
由于平台证书不能直接下载,此处先介绍证书的下载方法:
1、下载微信提供的CertificateDownloader.jar(下载地址:Releases · wechatpay-apiv3/CertificateDownloader (github.com))
2、编辑指令:java -jar CertificateDownloader.jar -k {apiV3key} -m {商户号} -f {apiclient_key.pem的物理路径} -s {证书序列号} -o {平台证书的导出路径}注意的是其中的证书序列号为商户API证书中显示的证书序列号 (替换关键字即可,指令中不需要{})
3、编写回调方法如下:
@RequestMapping(value = "/TransferNotify", method = {RequestMethod.POST, RequestMethod.GET})
@ResponseBody
public String TransferNotify(HttpServletRequest request, HttpServletResponse response) throws Exception {
Map<String, String> map = new HashMap<>(12);
try {
String timestamp = request.getHeader("Wechatpay-Timestamp");
String nonce = request.getHeader("Wechatpay-Nonce");
String serialNo = request.getHeader("Wechatpay-Serial");
String signature = request.getHeader("Wechatpay-Signature");
log.info("timestamp:{} nonce:{} serialNo:{} signature:{}", timestamp, nonce, serialNo, signature);
String result = HttpKit.readData(request);
log.info("支付通知密文 {}", result);
// 需要通过证书序列号查找对应的证书, verifyNotify中有验证证书的序列号
String plainText = WxPayKit.verifyNotify(serialNo, result, signature, nonce, timestamp,
"APIv3密钥", "通过指令导出的平台证书路径");
log.info("支付通知明文 {}", plainText);
if (StrUtil.isNotEmpty(plainText)) {
JSONObject jsonObject = JSONObject.parseObject(plainText);
System.out.println("batch_id:"+jsonObject.getString("batch_id"));
System.out.println("batch_status:"+jsonObject.getString("batch_status"));
response.setStatus(200);
map.put("code", "200");
map.put("message", "SUCCESS");
} else {
response.setStatus(500);
map.put("code", "ERROR");
map.put("message", "签名错误");
}
response.setHeader("Content-type", ContentType.JSON.toString());
response.getOutputStream().write(JSONUtil.toJsonStr(map).getBytes(StandardCharsets.UTF_8));
response.flushBuffer();
} catch (Exception e) {
log.error("系统异常", e);
}
return null;
}
到此微信支付的商家转账到零钱就结束了, 麻烦集中在用到的证书比较多容易混淆,其次就是平台证书导出时的问题!