Skip to content

feat: #3327【微信支付】平台收付通(注销申请) #3381

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

Merged
Merged
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.github.binarywang.wxpay.bean.ecommerce;

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

import java.io.Serializable;

/**
* 图片上传API
* <pre>
* https://pay.weixin.qq.com/docs/partner/apis/ecommerce-cancel/media/upload-media.html
* </pre>
*/
@Data
@NoArgsConstructor
public class AccountCancelApplicationsMediaResult implements Serializable {

/**
* 微信返回的媒体文件标识ID。
*/
@SerializedName(value = "media_id")
private String mediaId;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package com.github.binarywang.wxpay.bean.ecommerce;

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

import java.io.Serializable;
import java.util.List;

/**
* 提交注销申请单
* <pre>
* https://pay.weixin.qq.com/docs/partner/apis/ecommerce-cancel/cancel-applications/create-cancel-application.html
* </pre>
*/
@Data
@NoArgsConstructor
public class AccountCancelApplicationsRequest implements Serializable {

/**
* 【申请注销的二级商户号】 电商平台二级商户号,由微信支付生成并下发
*/
@SerializedName(value = "sub_mchid")
private String subMchid;

/**
* 【商户注销申请单号】 商户注销申请单号,由商户自定义生成,要求在服务商维度下是唯一的,必须仅包含大小写字母与数字
*/
@SerializedName(value = "out_apply_no")
private String outApplyNo;

/**
* 【注销申请材料】 注销申请材料,详见文档:注销申请材料
*/
@SerializedName(value = "application_info")
private List<CancelApplicationInfo> applicationInfo;

@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public static class CancelApplicationInfo implements Serializable {

/**
*【注销申请材料类型】 注销申请材料类型,详见文档:注销申请材料
* 可选取值:
* SP_MERCHANT_APPLICATION: 此枚举值已废弃,请使用新字段 SP_CANCEL_ACCOUNT_APPLICATION 以及新版本材料
* SUB_MERCHANT_APPLICATION: 此枚举值已废弃,请使用新字段 SUB_CANCEL_ACCOUNT_APPLICATION 以及新版本材料
* MISSING_OFFICIAL_SEAL_LETTER: 此材料已废弃,无需上传
* SP_CANCEL_ACCOUNT_APPLICATION: 电商服务商注销电商子申请书,请下载模板打印纸质版、填写盖章后拍照。模板文档详见:微信支付商户号注销申请书-服务商(纸质版)
* SUB_CANCEL_ACCOUNT_APPLICATION: 电商服务商子商户注销申请书,详见文档:微信支付商户号注销申请书-电商平台子商户适用(纸质版)
*/
@SerializedName("application_type")
private String applicationType;

/**
* 【注销申请材料照片ID】 注销申请材料照片ID,请填写通过上传图片接口预先上传图片生成好的media_id
*/
@SerializedName("application_media_id")
private String applicationMediaId;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package com.github.binarywang.wxpay.bean.ecommerce;

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

import java.io.Serializable;

/**
* 提交注销申请单
* <pre>
* https://pay.weixin.qq.com/docs/partner/apis/ecommerce-cancel/cancel-applications/create-cancel-application.html
* </pre>
*/
@Data
@NoArgsConstructor
public class AccountCancelApplicationsResult implements Serializable {

/**
* 【商户注销申请单号】 商户注销申请单号,原样返回请求参数里的内容
*/
@SerializedName(value = "out_apply_no")
private String outApplyNo;

/**
* 【二级商户号】 二级商户号
*/
@SerializedName(value = "sub_mchid")
private String subMchid;

/**
* 【驳回原因】 受理失败原因
*/
@SerializedName(value = "reject_reason")
private String rejectReason;

/**
* 【注销状态】 注销状态
* 可选取值:
* REVIEWING: 审核中
* REJECTED: 审核驳回,驳回原因详见reject_reason
* CANCEL_SUCCESS: 注销成功
*/
@SerializedName(value = "cancel_state")
private String cancelState;

/**
* 【最后更新时间】 最后更新时间。遵循rfc3339标准格式,格式为yyyy-MM-DDTHH:mm:ss+TIMEZONE,yyyy-MM-DD表示年月日,T出现在字符串中,表示time元素的开头,HH:mm:ss表示时分秒,TIMEZONE表示时区(+08:00表示东八区时间,领先UTC 8小时,即北京时间)。例如:2015-05-20T13:29:35+08:00表示,北京时间2015年5月20日 13点29分35秒。
*/
@SerializedName(value = "update_time")
private String updateTime;
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
import com.github.binarywang.wxpay.bean.ecommerce.enums.TradeTypeEnum;
import com.github.binarywang.wxpay.exception.WxPayException;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;

/**
Expand Down Expand Up @@ -535,4 +537,39 @@ public interface EcommerceService {
*/
SubsidiesCancelResult subsidiesCancel(SubsidiesCancelRequest subsidiesCancelRequest) throws WxPayException;

/**
* <pre>
* 提交注销申请单
* 文档地址: https://pay.weixin.qq.com/docs/partner/apis/ecommerce-cancel/cancel-applications/create-cancel-application.html
* </pre>
*
* @param accountCancelApplicationsRequest 提交注销申请单
* @return 返回数据 return AccountCancelApplicationsResult
* @throws WxPayException the wx pay exception
*/
AccountCancelApplicationsResult createdAccountCancelApplication(AccountCancelApplicationsRequest accountCancelApplicationsRequest) throws WxPayException;

/**
* <pre>
* 查询注销单状态
* 文档地址: https://pay.weixin.qq.com/docs/partner/apis/ecommerce-cancel/cancel-applications/get-cancel-application.html
* </pre>
*
* @param outApplyNo 注销申请单号
* @return 返回数据 return AccountCancelApplicationsResult
* @throws WxPayException the wx pay exception
*/
AccountCancelApplicationsResult getAccountCancelApplication(String outApplyNo) throws WxPayException;

/**
* <pre>
* 注销单资料图片上传
* 文档地址: https://pay.weixin.qq.com/docs/partner/apis/ecommerce-cancel/media/upload-media.html
* </pre>
*
* @param imageFile 图片
* @return 返回数据 return AccountCancelApplicationsResult
* @throws WxPayException the wx pay exception
*/
AccountCancelApplicationsMediaResult uploadMediaAccountCancelApplication(File imageFile) throws WxPayException, IOException;;
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,27 @@
import com.github.binarywang.wxpay.exception.WxPayException;
import com.github.binarywang.wxpay.service.EcommerceService;
import com.github.binarywang.wxpay.service.WxPayService;
import com.github.binarywang.wxpay.v3.WechatPayUploadHttpPost;
import com.github.binarywang.wxpay.v3.util.AesUtils;
import com.github.binarywang.wxpay.v3.util.RsaCryptoUtil;
import com.google.common.base.CaseFormat;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import lombok.RequiredArgsConstructor;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;

import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.text.DateFormat;
Expand Down Expand Up @@ -395,6 +400,36 @@ public SubsidiesCancelResult subsidiesCancel(SubsidiesCancelRequest subsidiesCan
String response = this.payService.postV3(url, GSON.toJson(subsidiesCancelRequest));
return GSON.fromJson(response, SubsidiesCancelResult.class);
}

@Override
public AccountCancelApplicationsResult createdAccountCancelApplication(AccountCancelApplicationsRequest accountCancelApplicationsRequest) throws WxPayException {
String url = String.format("%s/v3/ecommerce/account/cancel-applications", this.payService.getPayBaseUrl());
String response = this.payService.postV3(url, GSON.toJson(accountCancelApplicationsRequest));
return GSON.fromJson(response, AccountCancelApplicationsResult.class);
}

@Override
public AccountCancelApplicationsResult getAccountCancelApplication(String outApplyNo) throws WxPayException {
String url = String.format("%s/v3/ecommerce/account/cancel-applications/out-apply-no/%s", this.payService.getPayBaseUrl(), outApplyNo);
String result = this.payService.getV3(url);
return GSON.fromJson(result, AccountCancelApplicationsResult.class);
}

@Override
public AccountCancelApplicationsMediaResult uploadMediaAccountCancelApplication(File imageFile) throws WxPayException, IOException {
String url = String.format("%s/v3/ecommerce/account/cancel-applications/media", this.payService.getPayBaseUrl());
try (FileInputStream s1 = new FileInputStream(imageFile)) {
String sha256 = DigestUtils.sha256Hex(s1);
try (InputStream s2 = new FileInputStream(imageFile)) {
WechatPayUploadHttpPost request = new WechatPayUploadHttpPost.Builder(URI.create(url))
.withImage(imageFile.getName(), sha256, s2)
.buildEcommerceAccount();
String result = this.payService.postV3(url, request);
return GSON.fromJson(result, AccountCancelApplicationsMediaResult.class);
}
}
}

/**
* 校验通知签名
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,5 +72,33 @@ public WechatPayUploadHttpPost build() {

return request;
}

/**
* 平台收付通(注销申请)-图片上传-图片上传
* https://pay.weixin.qq.com/docs/partner/apis/ecommerce-cancel/media/upload-media.html
* @return WechatPayUploadHttpPost
*/
public WechatPayUploadHttpPost buildEcommerceAccount() {
if (fileName == null || fileSha256 == null || fileInputStream == null) {
throw new IllegalArgumentException("缺少待上传图片文件信息");
}

if (uri == null) {
throw new IllegalArgumentException("缺少上传图片接口URL");
}

String meta = String.format("{\"file_name\":\"%s\",\"file_digest\":\"%s\"}", fileName, fileSha256);
WechatPayUploadHttpPost request = new WechatPayUploadHttpPost(uri, meta);

MultipartEntityBuilder entityBuilder = MultipartEntityBuilder.create();
entityBuilder.setMode(HttpMultipartMode.RFC6532)
.addBinaryBody("file", fileInputStream, fileContentType, fileName)
.addTextBody("meta", meta, ContentType.APPLICATION_JSON);

request.setEntity(entityBuilder.build());
request.addHeader("Accept", ContentType.APPLICATION_JSON.toString());

return request;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,7 @@
package com.github.binarywang.wxpay.service.impl;
import com.google.common.collect.Lists;

import com.github.binarywang.wxpay.bean.ecommerce.CombineTransactionsRequest;
import com.github.binarywang.wxpay.bean.ecommerce.PartnerTransactionsQueryRequest;
import com.github.binarywang.wxpay.bean.ecommerce.PartnerTransactionsResult;
import com.github.binarywang.wxpay.bean.ecommerce.ProfitSharingReceiverRequest;
import com.github.binarywang.wxpay.bean.ecommerce.ProfitSharingReceiverResult;
import com.github.binarywang.wxpay.bean.ecommerce.SignatureHeader;
import com.github.binarywang.wxpay.bean.ecommerce.TransactionsResult;
import com.github.binarywang.wxpay.bean.ecommerce.*;
import com.github.binarywang.wxpay.bean.ecommerce.enums.SpAccountTypeEnum;
import com.github.binarywang.wxpay.bean.ecommerce.enums.TradeTypeEnum;
import com.github.binarywang.wxpay.exception.WxPayException;
Expand All @@ -19,6 +14,8 @@
import org.testng.annotations.Guice;
import org.testng.annotations.Test;

import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;

Expand Down Expand Up @@ -151,4 +148,29 @@ public void testSubDayEndBalance() throws WxPayException {
String date = "";
wxPayService.getEcommerceService().subDayEndBalance(subMchid, date);
}

@Test
public void testCreatedAccountCancelApplication() throws WxPayException {
AccountCancelApplicationsRequest request = new AccountCancelApplicationsRequest();
request.setSubMchid("");
request.setOutApplyNo("");
request.setApplicationInfo(Lists.newArrayList());

AccountCancelApplicationsResult result = wxPayService.getEcommerceService().createdAccountCancelApplication(request);
log.info("请求参数:{} 响应结果:{}", request, result);
}

@Test
public void testGetAccountCancelApplication() throws WxPayException {
String request = "申请单号";
AccountCancelApplicationsResult result = wxPayService.getEcommerceService().getAccountCancelApplication(request);
log.info("请求参数:{} 响应结果:{}", request, result);
}

@Test
public void testUploadMediaAccountCancelApplication() throws WxPayException, IOException {
AccountCancelApplicationsMediaResult result = wxPayService.getEcommerceService()
.uploadMediaAccountCancelApplication(new File("src\\test\\resources\\mm.jpeg"));
log.info("响应结果:{}", result);
}
}