springboot集成微信APP支付V3最新版
初识微信支付(不明白微信文档的请看,否则请跳过)
微信文档地址
众所周知,看别人的文档是个痛苦的事情,尤其是复杂的文档,这里说一下自己总结看文档的经验,先不着急上去就看某一个功能,先把文档的分类搞明白,然后是每个分类下的每个功能列表,切忌直接进去一顿操作跳转,结果自己都晕了,感觉到处都是支付的信息,总结一句话,看文档之前,先搞懂写文档人的套路,然后你会发现,真的是手到擒来,废话不多说,直接进入正题。
第一步,进入文档中心
主要看这几个,最重要的是API字典!
指引文档
这里主要是告诉我们接入微信支付的前置条件以及满足接入条件之后的开发步骤。这里建议大概看一下。
- 接入前的准备
主要是准备一些支付所需要的资料,例如:appid,证书等,基本按照文档一步步操作就能轻松搞定。 - 开发指引
这里主要是讲开发步骤,以及提供一些各语言的参考代码,建议主看,初看的时候尽量先看一遍流程,先不要点里边的链接跳转过去看,搞明白了之后再回头逐步去研究每个步骤的详细介绍。
总结一下,简单的流程:
1、前端请求后端支付接口(一般给个订单号即可)
2、后端加载证书,配置预支付所需要的参数
3、请求微信支付下单接口,获取预支付id,也就是所谓的 prepay_id
4、组装好前端拉起微信支付所需要的参数,返回给前端即可
5、前端拉起微信支付,支付或取消,微信会以异步通知告知后端,这里需要后端提供一个接口给微信支付官方(文档说是必须用https,我用的http也可以)
6、接收微信异步通知,获取相关参数,处理对应逻辑
详细流程及代码逻辑,下边会具体说!!!
API字典
这个比较简单但也是最重要的,后边我们会频繁用到
这个就不多说了,提醒一下,请求接口的时候一定要正确填写接口地址和请求方式!!!
接口规则
这里主要讲签名,验签,敏感信息加解密(例如:异步通知返回的订单信息),证书更新(微信支付证书有时间限制,需要定期更新)的介绍
具体流程+代码
第一步:申请证书并创建应用
准备必要的材料,去微信开放平台-管理中心,创建移动应用(应用没有上线也可以申请,资料填好就行,没有的先不填,基本可以通过审核),之后申请开通微信支付功能,得到所有配置所需要的参数。
注意,这里有个很容易配错的地方:应用签名,创建app应用的时候会让你填写应用签名和应用包名,应用包名是你自己填写的,保持跟安卓包名一致即可,应用签名并不是你keystore文件里的MD5值,而是需要你用微信提供的工具安装到手机上(同时安装你的app),获取你app的签名。
微信开发指引
如果这里的连接下载不了签名生成工具,可以去开放平台安卓资源下载微信资源下载
如果配错应用签名,你会发现app只能首次拉起微信支付,之后支付的时候,微信怎么都拉不起来!!!
第二步:下单支付(熟悉调用流程、封装参数配置类、公用支付方法)
下载证书,配置微信支付。
证书申请参考:证书申请
配置微信支付config类:
public class WxPayInfoConfig {
public final static String mchId = "********";
public final static String appid = "********";
public final static String apiV3Key = "********";
/**
* 退款异步通知地址
*/
public final static String refund_notify_url = "http://api.wx.com/refundNotify/refunded/wxNotify";
/**
* 下单异步通知地址,你后端的开放的接口地址,必须能外网访问,确保微信能正常调用,且不要加任何限制
* 改成你自己的地址
*/
public final static String pay_notify_url = "http://api.wx.com/payNotify/payed/wxNotify";
/**
* 证书相对路径
*/
public final static String privateKeyPath = "wx/apiclient_key.pem";
/**
* 下单地址
*/
public final static String singleUrl = "https://api.mch.weixin.qq.com/v3/pay/transactions/app";
/**
* 退款地址
*/
public final static String refundUrl = "https://api.mch.weixin.qq.com/v3/refund/domestic/refunds";
/**
* 查询订单地址
* transaction_id:微信支付系统生成的订单号 或者 商户订单号
* mchid:直连商户的商户号,由微信支付生成并下发
* 组合地址:String url = String.format(WxPayInfoConfig.signUrl, transaction_id, WxPayInfoConfig.mchId);
*/
public final static String queryUrl = "https://api.mch.weixin.qq.com/v3/pay/transactions/id/%s?mchid=%s";
/**
* 关闭订单地址
*/
public final static String closeUrl = "https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/%s/close";
public static String mchSerialNo;
public static PrivateKey privateKey;
static {
// 加载商户私钥(privateKey:私钥字符串)
privateKey = WxPayUtil.getPrivateKey(privateKeyPath);
// 加载平台证书(mchId:商户号,mchSerialNo:商户证书序列号,apiV3Key:V3密钥)
X509Certificate certificate = WxPayUtil.getX509Certificate();
// 证书的序列号 也有用
mchSerialNo = certificate.getSerialNumber().toString(16).toUpperCase();
}
}
提取微信支付工具类:
这里注意,微信证书会过期,所以需要你定期更新证书,可手动更新,也可以自动更新,这里说下自动更新,自动更新,微信提供了java库wechatpay-apache-httpclient(不仅封装了证书相关发放,还封装了下单,加解密的相关方法,可以细细研究一下)
在我们往下看之前,请务必了解微信支付请求流程:
以下单支付流程为例:
<dependency>
<groupId>com.github.wechatpay-apiv3</groupId>
<artifactId>wechatpay-apache-httpclient</artifactId>
<version>0.2.3</version>
</dependency>
详细介绍
工具类配置
public class WxPayUtil {
/**
* 保存微信平台证书
*/
private static final ConcurrentHashMap<String, AutoUpdateCertificatesVerifier> verifierMap = new ConcurrentHashMap<>();
/**
* 功能描述:获取平台证书,自动更新
* 注意:这个方法内置了平台证书的获取和返回值解密
* @return com.wechat.pay.contrib.apache.httpclient.auth.AutoUpdateCertificatesVerifier
* @author zhouwenjie
* @date 2021/6/13 20:29
*/
static AutoUpdateCertificatesVerifier getVerifier(String serialNumber) {
AutoUpdateCertificatesVerifier verifier = null;
if (verifierMap.isEmpty() || !verifierMap.containsKey(serialNumber)) {
verifierMap.clear();
try {
//刷新
PrivateKeySigner signer = new PrivateKeySigner(WxPayInfoConfig.mchSerialNo, WxPayInfoConfig.privateKey);
WechatPay2Credentials credentials = new WechatPay2Credentials(WxPayInfoConfig.mchId, signer);
verifier = new AutoUpdateCertificatesVerifier(credentials
, WxPayInfoConfig.apiV3Key.getBytes("utf-8"));
verifierMap.put(verifier.getValidCertificate().getSerialNumber()+"", verifier);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
} else {
verifier = verifierMap.get(serialNumber);
}
return verifier;
}
/**
* 获取httpClient,用于之后请求微信接口
* 用这个工具类的好处就是不用你自己去拼接签名参数了,每次请求会自动帮你创建签名
* 详情可以点击进去看源码
*/
static CloseableHttpClient getHttpClient(String serialNumber) {
//获取平台证书
AutoUpdateCertificatesVerifier verifier = getVerifier(serialNumber);
if (verifier == null) {
ExceptionCast.cast(CommonCode.OPERATION_ERROR);
}
// 初始化httpClient httpClient会在发送请求之前拦截并且自动根据请求信息创建签名
CloseableHttpClient httpClient = WechatPayHttpClientBuilder.create()
.withMerchant(WxPayInfoConfig.mchId, WxPayInfoConfig.mchSerialNo, WxPayInfoConfig.privateKey)
.withValidator(new WechatPay2Validator(verifier)).build();
return httpClient;
}
/**
* 获取私钥。
*
* @return 私钥对象
*/
public static PrivateKey getPrivateKey(String fileName) {
try {
ClassPathResource resource = new ClassPathResource(fileName);
String content = IOUtils.toString(resource.getInputStream(), "utf-8");
String privateKey = content.replace("-----BEGIN PRIVATE KEY-----", "")
.replace("-----END PRIVATE KEY-----", "")
.replaceAll("\\s+", "");
KeyFactory kf = KeyFactory.getInstance("RSA");
PrivateKey key = kf.generatePrivate(new PKCS8EncodedKeySpec(Base64.getDecoder().decode(privateKey)));
return key;
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("当前Java环境不支持RSA", e);
} catch (InvalidKeySpecException e) {
throw new RuntimeException("无效的密钥格式");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
/**
* 功能描述: 移动端支付请求签名
*
* @param prepayid
* @param nonceStr
* @return java.lang.String
* @author zhouwenjie
* @date 2021/6/10 9:09
*/
@SneakyThrows
public static String appPaySign(String prepayid, String nonceStr, String timestamp) {
String signatureStr = Stream.of(WxPayInfoConfig.appid, timestamp, nonceStr, prepayid)
.collect(Collectors.joining("\n", "", "\n"));
Signature sign = Signature.getInstance("SHA256withRSA");
sign.initSign(WxPayInfoConfig.privateKey);
sign.update(signatureStr.getBytes(StandardCharsets.UTF_8));
return Base64Utils.encodeToString(sign.sign());
}
/**
* 功能描述: 验证签名
* 注意:使用微信支付平台公钥验签
* Wechatpay-Signature 微信返签名
* Wechatpay-Serial 微信平台证书序列号
*
* @return java.lang.String
* @author zhouwenjie
* @date 2021/6/10 9:09
*/
@SneakyThrows
public static boolean verifySign(HttpServletRequest request,String body) {
boolean verify = false;
try {
String wechatPaySignature = request.getHeader("Wechatpay-Signature");
String wechatPayTimestamp = request.getHeader("Wechatpay-Timestamp");
String wechatPayNonce = request.getHeader("Wechatpay-Nonce");
String wechatPaySerial = request.getHeader("Wechatpay-Serial");
//组装签名串
String signStr = Stream.of(wechatPayTimestamp, wechatPayNonce, body)
.collect(Collectors.joining("\n", "", "\n"));
//获取平台证书
AutoUpdateCertificatesVerifier verifier = getVerifier(wechatPaySerial);
//获取失败 验证失败
if (verifier != null) {
Signature signature = Signature.getInstance("SHA256withRSA");
signature.initVerify(verifier.getValidCertificate());
//放入签名串
signature.update(signStr.getBytes(StandardCharsets.UTF_8));
verify = signature.verify(Base64.getDecoder().decode(wechatPaySignature.getBytes()));
}
} catch (InvalidKeyException e) {
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return verify;
}
/**
* 功能描述: 获取X509Certificate
*
* @param
* @return java.security.cert.X509Certificate
* @author zhouwenjie
* @date 2021/6/15 11:49
*/
public static X509Certificate getX509Certificate() {
ClassPathResource resource = new ClassPathResource("wx/apiclient_cert.p12");
KeyStore keyStore;
X509Certificate certificate = null;
try {
keyStore = KeyStore.getInstance("PKCS12");
keyStore.load(resource.getInputStream(), WxPayInfoConfig.mchId.toCharArray());
certificate = (X509Certificate) keyStore.getCertificate("Tenpay Certificate");
certificate.checkValidity();
} catch (KeyStoreException | IOException e) {
e.printStackTrace();
} catch (CertificateNotYetValidException e) {
e.printStackTrace();
} catch (CertificateExpiredException e) {
e.printStackTrace();
} catch (CertificateException e) {
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return certificate;
}
/**
* 证书和回调报文解密
*
* @param associatedData response.body.data[i].encrypt_certificate.associated_data 附加数据包(可能为空)
* @param nonce response.body.data[i].encrypt_certificate.nonce 加密使用的随机串初始化向量
* @param ciphertext response.body.data[i].encrypt_certificate.ciphertext Base64编码后的密文
* @return the string
* @throws GeneralSecurityException the general security exception
*/
public static String decryptToString(String associatedData, String nonce, String ciphertext) {
String cert = null;
try {
//简化
AesUtil aesUtil = new AesUtil(WxPayInfoConfig.apiV3Key.getBytes(StandardCharsets.UTF_8));
cert = aesUtil.decryptToString(associatedData.getBytes(StandardCharsets.UTF_8), nonce.getBytes(StandardCharsets.UTF_8), ciphertext);
} catch (GeneralSecurityException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return cert;
}
/**
* 敏感信息加解密-解密.
* 使用商户私钥解密
* <p>
* `为了保证通信过程中敏感信息字段(如用户的住址、银行卡号、手机号码等)的机密性,
* `微信支付API v3要求商户对上送的敏感信息字段进行加密。
* `与之相对应,微信支付会对下行的敏感信息字段进行加密,商户需解密后方能得到原文
*
* @param ciphertext 密文
*/
public static String rsaDecryptOAEP(String ciphertext) {
// 使用商户私钥解密
String text = null;
try {
text = RsaCryptoUtil.decryptOAEP(ciphertext, WxPayInfoConfig.privateKey);
} catch (BadPaddingException e) {
e.printStackTrace();
}
return text;
}
/**
* 敏感信息加解密-加密.
* 使用微信支付平台证书中的公钥加密
* 使用封装的RsaCryptoUtil
* https://github.com/wechatpay-apiv3/wechatpay-apache-httpclient
*
* @param message 需要加密的信息
* certificate 证书
*/
public static String rsaEncryptOAEP(String message) {
String ciphertext = null;
try {
AutoUpdateCertificatesVerifier verifier = getVerifier(null);
X509Certificate certificate = verifier.getValidCertificate();
ciphertext = RsaCryptoUtil.encryptOAEP(message, certificate);
} catch (IllegalBlockSizeException e) {
e.printStackTrace();
}
return ciphertext;
}
/**
* 功能描述: 发起get请求
* 动态返回自定义的实体类型
*
* @param method 请求方式 GET POST
* @param url
* @param t 泛型
* @return T 实体类class
* @author zhouwenjie
* @date 2021/6/15 14:49
*/
public static <T> T wxHttpRequest(String method, String url, String reqdata, Class<T> t) {
T resultBean = null;
T instance = null;
CloseableHttpResponse response;
CloseableHttpClient httpClient = null;
try {
if (!verifierMap.isEmpty()) {
String bigInteger = verifierMap.keys().nextElement();
//httpClient 内置有签名信息
httpClient = getHttpClient(bigInteger);
} else {
httpClient = getHttpClient("1");
}
//创建实体,相当于new对象
instance = t.newInstance();
if ("get".equalsIgnoreCase(method)) {
HttpGet httpGet = new HttpGet(url);
httpGet.setHeader("Accept", "application/json");
//完成签名并执行请求
response = httpClient.execute(httpGet);
} else {
StringEntity entity = new StringEntity(reqdata, StandardCharsets.UTF_8);
entity.setContentType("application/json");
HttpPost httpPost = new HttpPost(url);
httpPost.setEntity(entity);
httpPost.setHeader("Accept", "application/json");
//完成签名并执行请求
response = httpClient.execute(httpPost);
}
//如果状态码为200,就是正常返回
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode == 200) {
String result = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8);
JSONObject jsonObject = JSON.parseObject(result);
if (instance instanceof JSONObject) {
resultBean = (T) jsonObject;
} else {
resultBean = JSONObject.parseObject(jsonObject.toString(), t);
}
} else if (statusCode == 204) {
log.info("关单成功");
String code = "204";
resultBean = (T) code;
} else {
log.error("错误状态码 = " + statusCode + ",返回的错误信息 = " + EntityUtils.toString(response.getEntity()));
ExceptionCast.cast(CommonCode.WX_ORDER_REAUEST_FAIL);
}
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (ClientProtocolException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (httpClient != null) {
httpClient.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return resultBean;
}
/**
* 功能描述:生成单个订单预付单
*
* @param totalPrice
* @param out_trade_no
* @param description
* @return java.util.Map<java.lang.String, java.lang.String>
* @author zhouwenjie
* @date 2021/6/10 10:16
*/
public static Map<String, String> createOrder(Integer totalPrice, String out_trade_no, String
description) {
WxOnePayVo wxOnePayVo = new WxOnePayVo(totalPrice);
wxOnePayVo.setAppid(WxPayInfoConfig.appid);
wxOnePayVo.setMchid(WxPayInfoConfig.mchId);
wxOnePayVo.setNotify_url(WxPayInfoConfig.pay_notify_url);
wxOnePayVo.setDescription(description);
wxOnePayVo.setAttach(CommonConstant.pay_order);
wxOnePayVo.setOut_trade_no(out_trade_no);
//计算失效时间 15分钟订单关闭
wxOnePayVo.setTime_expire(CommonUtil.getTimeExpire());
String reqdata = JSON.toJSONString(wxOnePayVo);
Map<String, String> returnMap = getPayReturnMap("POST", reqdata, WxPayInfoConfig.singleUrl);
return returnMap;
}
public static Map<String, String> getPayReturnMap(String method, String reqdata, String url) {
HashMap<String, String> returnMap = new HashMap<String, String>();
JSONObject jsonObject = wxHttpRequest(method, url, reqdata, JSONObject.class);
//处理成功
String prepay_id = (String) jsonObject.get("prepay_id");
//生成代签名的支付信息
String nonceStr = UUID.randomUUID().toString(true);
String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
String signature = appPaySign(prepay_id, nonceStr, timestamp);
returnMap.put("appid", WxPayInfoConfig.appid);
returnMap.put("partnerid", WxPayInfoConfig.mchId);
returnMap.put("prepayid", prepay_id);
returnMap.put("package", "Sign=WXPay");
returnMap.put("noncestr", nonceStr);
returnMap.put("timestamp", timestamp);
returnMap.put("sign", signature);
return returnMap;
}
/**
* 功能描述:退款操作
* 注意:
* 1、交易时间超过一年的订单无法提交退款
* <p>
* 2、微信支付退款支持单笔交易分多次退款(不超50次),多次退款需要提交原支付订单的商户订单号和设置不同的退款单号。申请退款总金额不能超过订单金额。 一笔退款失败后重新提交,请不要更换退款单号,请使用原商户退款单号
* <p>
* 3、错误或无效请求频率限制:6qps,即每秒钟异常或错误的退款申请请求不超过6次
* <p>
* 4、每个支付订单的部分退款次数不能超过50次
* <p>
* 5、如果同一个用户有多笔退款,建议分不同批次进行退款,避免并发退款导致退款失败
* <p>
* 6、申请退款接口的返回仅代表业务的受理情况,具体退款是否成功,需要通过退款查询接口获取结果
* <p>
* 7、一个月之前的订单申请退款频率限制为:5000/min
*/
public static WxRefundReturnInfoVo refundOrder(WxRefundInfoVo wxRefundInfoVo) {
wxRefundInfoVo.setNotify_url(WxPayInfoConfig.refund_notify_url);
String reqdata = JSON.toJSONString(wxRefundInfoVo);
WxRefundReturnInfoVo wxRefundReturnInfoVo = wxHttpRequest("POST", WxPayInfoConfig.refundUrl, reqdata, WxRefundReturnInfoVo.class);
return wxRefundReturnInfoVo;
}
/**
* 功能描述: 查询订单
* 查询订单可通过微信支付订单号和商户订单号两种方式查询,两种查询方式返回结果相同
*
* @param transaction_id
* @return void
* @author zhouwenjie
* @date 2021/6/15 16:08
*/
public static NotifyResourceVO QueryOrder(String transaction_id) {
String url = String.format(WxPayInfoConfig.queryUrl, transaction_id, WxPayInfoConfig.mchId);
NotifyResourceVO notifyResourceVO = wxHttpRequest("GET", url, null, NotifyResourceVO.class);
return notifyResourceVO;
}
/**
* 功能描述: 关闭订单
* POST 接口响应204,无内容 即成功
*
* @param out_trade_no 商户系统内部订单号
* @return void
* @author zhouwenjie
* @date 2021/6/15 16:08
*/
public static String CloseOrder(String out_trade_no) {
String url = String.format(WxPayInfoConfig.closeUrl, out_trade_no);
//请求body参数
String reqdata = "{\"mchid\": \"" + WxPayInfoConfig.mchId + "\"}";
String code = wxHttpRequest("POST", url, reqdata, String.class);
return code;
}
}
实体类:
WxOnePayVo:请求预付单标识的关键参数
@Data
public class WxOnePayVo {
/**
* 商户订单号 只能是数字、大小写字母_-*且在同一个商户号下唯一
* 示例值:1217752501201407033233368018
*/
private String out_trade_no;
/**
* 商户订单号 直连商户的商户号,由微信支付生成并下发。
* 示例值:1230000109
*/
private String mchid;
/**
* 应用ID
* 示例值:wxd678efh567hg6787
*/
private String appid;
/**
* 附加数据,在查询API和支付通知中原样返回,可作为自定义参数使用
*/
private String attach;
/**
* 商品描述
* 示例值:Image形象店-深圳腾大-QQ公仔
*/
private String description;
/**
* 通知地址 通知URL必须为直接可访问的URL,不允许携带查询串
* 示例值:https://www.weixin.qq.com/wxpay/pay.php
*/
private String notify_url;
/**
* 订单失效时间
* 格式为YYYY-MM-DDTHH:mm:ss+TIMEZONE
* 例如:2015-05-20T13:29:35+08:00表示,北京时间2015年5月20日 13点29分35秒。
* 示例值:2018-06-08T10:34:56+08:00
*/
private String time_expire;
/**
* amount 订单金额信息
* 示例值:
* "amount": {
* "total": 1, [订单总金额,单位为分]
* "currency": "CNY" [CNY:人民币,境内商户号仅支持人民币]
* }
*/
private Amount amount;
/**
* 订单金额
*/
@Data
class Amount {
/**
* 总金额
*/
@ApiModelProperty("总金额")
private Integer total;
@ApiModelProperty("CNY")
private String currency = "CNY";
}
public WxOnePayVo(Integer total){
Amount amount = new Amount();
amount.setTotal(total);
this.amount = amount;
}
}
代码里都有对应的方法,顺序可能有些乱,不过参照上边的流程图看,应该没问题,代码里的加解密敏感信息,我暂时我用到,只用了解密报文(decryptToString)。
第三步:接收异步通知
接收异步通知(按照第二步完成支付信息的封装并返回给前端之后,接下来就是默默等待异步通知了,所有支付的后续工作都在这里完成)
流程:定义异步通知接口-接收通知并解析处理
关键代码:
@ApiOperation(value = "微信异步通知", notes = "微信异步通知")
@PostMapping(value = "/wxNotify")
public Map<String, String> getTenPayNotify(HttpServletRequest request) throws IOException {
HashMap<String, String> returnMap = new HashMap<>(2);
String body = request.getReader().lines().collect(Collectors.joining());
WxPayAsyncVo wxPayAsyncVo = JSONObject.parseObject(body, WxPayAsyncVo.class);
//验证签名
boolean verifySign = WxPayUtil.verifySign(request, body);
if (verifySign) {
//https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_2_5.shtml
//获得加密数据,得到的资源对象
com.ls.modules.common.vo.wx.pay.Resource resource = wxPayAsyncVo.getResource();
//解密后资源数据
String notifyResourceStr = WxPayUtil.decryptToString(resource.getAssociated_data(), resource.getNonce(), resource.getCiphertext());
//通知资源数据对象
NotifyResourceVO notifyResourceVO = JSONObject.parseObject(notifyResourceStr, NotifyResourceVO.class);
}
}
封装的实体类对象:
WxPayAsyncVo:通知结果大对象
@ToString
@Data
public class WxPayAsyncVo {
/**
* 通知的唯一ID
* 示例值:EV-2018022511223320873
*/
private String id;
/**
* 通知创建的时间
* 示例值:2015-05-20T13:29:35+08:00
*/
private String create_time;
/**
* 通知的类型
* 示例值:REFUND.SUCCESS
*/
private String event_type;
/**
* 通知的资源数据类型,支付成功通知为encrypt-resource
* 示例值:encrypt-resource
*/
private String resource_type;
/**
* 回调摘要
* 示例值:支付成功
*/
private String summary;
/**
* 通知资源数据
*/
private Resource resource;
}
NotifyResourceVO:大对象中包含的资源对象
@Data
public class NotifyResourceVO {
/**
* 公众号ID
*/
private String appid;
/**
* 直连商户号
*/
private String mchid;
/**
* 商户订单号
*/
private String out_trade_no;
/**
* 微信支付订单号
*/
private String transaction_id;
/**
* 交易类型
*/
private String trade_type;
/**
* 交易状态
*/
private String trade_state;
/**
* 交易状态描述
*/
private String trade_state_desc;
/**
* 付款银行
*/
private String bank_type;
/**
* 支付完成时间
*/
private String success_time;
/**
* 附加数据 附加数据,在查询API和支付通知中原样返回,可作为自定义参数使用
*/
private String attach;
/**
* 支付者
*/
private Payer payer;
/**
* 订单金额
*/
private NotifyAmount amount;
/**
* 支付者
*/
@Data
public class Payer {
/**
* 用户标识
*/
private String openid;
}
}
@Data
public class WxRefundReturnInfoVo {
/**
*微信支付退款号
*/
private String refund_id;
/**
*商户退款单号 退款单号多次请求只退一笔。
*/
private String out_refund_no;
/**
*微信支付订单号
*/
private String transaction_id;
/**
*商户订单号
*/
private String out_trade_no;
/**
*退款渠道
* 枚举值:
* ORIGINAL—原路退款
* BALANCE—退回到余额
* OTHER_BALANCE—原账户异常退到其他余额账户
* OTHER_BANKCARD—原银行卡异常退到其他银行卡
*/
private String channel;
/**
*退款入账账户
* 取当前退款单的退款入账方,有以下几种情况:
* 1)退回银行卡:{银行名称}{卡类型}{卡尾号}
* 2)退回支付用户零钱:支付用户零钱
* 3)退还商户:商户基本账户商户结算银行账户
* 4)退回支付用户零钱通:支付用户零钱通
*/
private String user_received_account;
/**
*退款成功时间
* 示例值:2020-12-01T16:18:12+08:00
*/
private String success_time;
/**
*退款创建时间
* 示例值:2020-12-01T16:18:12+08:00
*/
private String create_time;
/**
*退款状态
* 退款到银行发现用户的卡作废或者冻结了,导致原路退款银行卡失败,可前往商户平台(pay.weixin.qq.com)-交易中心,手动处理此笔退款。
* 枚举值:
* SUCCESS:退款成功
* CLOSED:退款关闭
* PROCESSING:退款处理中
* ABNORMAL:退款异常
*/
private String status;
/**
*funds_account
* 退款所使用资金对应的资金账户类型
* 枚举值:
* UNSETTLED : 未结算资金
* AVAILABLE : 可用余额
* UNAVAILABLE : 不可用余额
* OPERATION : 运营户
* BASIC : 基本账户(含可用余额和不可用余额)
*/
private String funds_account;
/**
*微信支付退款号
*/
private Amount amount;
/**
*优惠商品发生退款时返回商品信息
*/
//private List<Promotion_detail> promotion_detail;
}
结束总结:
了解清楚app下单,那么其他的功能,比如:关单、查单、退款等等操作都基本相同,直接在公共类中增加对应的方法即可。
配置证书和各种id,key的时候一定要仔细,最容易出错的就是路径问题,id或者密钥等配反了、错了。
增加新功能的时候多查看微信提供的示例代码,另外注意的是,微信的示例代码有些是V2的或者自己手写实现的,最好看完示例代码,去这里java类库看看,里边封装了很多方法,能够节省很多工作量。