支付宝支付
开发前准备
文档
链接:
微信支付流程介绍V2
.
链接:
微信扫码支付接口文档V2-SDK
.
链接:
微信V3-官方提供github
.
链接:
微信支付指引文档
.
本文以刷脸支付分为例指引开发微信支付,因为这里用到了V2的接口和V3的接口
链接:
本文主要按照微信刷脸支付分支付:流程
.
先明确一下份工,一下画框的地方是后端要解决的问题
第一个接口:提供给硬件调用的接口和此接口需要加密
返回给硬件什么参数?
剩下的接口就是V3的这时候就需要上面第四个连接提供的指引文档了
开发准备
微信开发大致流程首先找到要调用接口 -> 构建参数 -> 发起http调用 -> 同步接收数据 -> 存在回调接口接收回调【解密、幂等校验和回复成功】
//启动类注入http调用Bean
@Bean
public RestTemplate restTemplate() {
return new RestTemplate(new OkHttp3ClientHttpRequestFactory());
}
maven
<-- 发起http调用 -->
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
</dependency>
微信配置类
之前写的支付宝的配置是在配置文件中写死的,就算之后用远程配置也是比较麻烦的,这里直接放入字典表,构建后直接从库中、缓存中取出配置对维护和公用来说都很方便。
id | module_type | module_lable_type | module_lable_desc | module_lable_value | lable_desc |
---|---|---|---|---|---|
100 | pay | vxpay_config | vx_app_id | wx123********** | 微信:APPID |
101 | pay | vxpay_config | vx_mch_id | 123********** | 微信:商户号 |
102 | pay | vxpay_config | vx_mch_serial_number | 123********** | 微信:商家api序列号 |
103 | pay | vxpay_config | vx_mch_v3_private_key | 123********** | 微信:报文解密V3密钥key |
104 | pay | vxpay_config | vx_api_private_key | 123********** | 微信: API密钥 |
105 | pay | vxpay_config | vx_key_path | /**/vx/key/apiclient_key.pem | 微信:密钥【apiclient_key.pem】地址 |
106 | pay | vxpay_config | vx_service_id | 123********** | 微信:调用接口所需service_id |
107 | pay | vxpay_config | vx_pay_notify_url | http://IP:POST/vx/payCallBack | 微信:支付回调地址 |
108 | pay | vxpay_config | vx_refund_notify_url | http://IP:POST/vx/refundCallBack | 微信:退款回调地址 |
这里也提供了配置文件的写法基本不变
wx:
appId: wx123**********
# 商户号
mchid: 123**********
# 微信商家api序列号
mchSerialNo: 123**********
# 回调报文解密V3密钥key
v3Key: 123**********
# 商户的key【API密匙】存放路径
KeyPath: /**/vx/key/apiclient_key.pem
#API密钥
keyApi: 123**********
微信V2-SDK
下载上面提供的第二个连接中API-SDK对应的JAVA版解压,然后放入项目中
微信V3工具
公共配置类也就是上面字典根据【module_lable_type】 得到【module_lable_desc 】不同值【module_lable_value 】
可以加上【@ConfigurationProperties(prefix = “wx”)】注解直接使用配置类中配置的参数
@Data
public class WxMpProperties {
/**
* 微信:APPID
*/
private String appId;
/**
* 微信:商户号
*/
private String mchId;
/**
* 微信:报文解密V3密钥key
*/
private String v3Key;
/**
* 微信: API密钥
*/
private String keyApi;
/**
* 微信:密钥【apiclient_key.pem】地址
*/
private String KeyPath;
/**
* 微信:商家api序列号
*/
private String mchSerialNo;
/**
* 微信:调用接口所需service_id
*/
private String serviceId;
/**
* 微信:支付回调地址
*/
private String payNotifyUrl;
/**
* 微信:退款回调地址
*/
private String refundNotifyUrl;
}
此类用来发起http调用
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
public class HttpUtils {
private static final ObjectMapper JSON=new ObjectMapper();
/**
* 封装get请求
* @param url
* @return
*/
public static String doGet(String url,WxMpProperties wxMpProperties){
CloseableHttpClient httpClient = HttpClientBuilder.create().build();
HttpGet httpget = new HttpGet(url);
httpget.addHeader("Content-Type", "application/json;charset=UTF-8");
httpget.addHeader("Accept", "application/json");
try{
String token = WechatPayUtils.getToken("GET", new URL(url), "",wxMpProperties);
httpget.addHeader("Authorization", token);
CloseableHttpResponse httpResponse = httpClient.execute(httpget);
if(httpResponse.getStatusLine().getStatusCode() == 200){
String jsonResult = EntityUtils.toString( httpResponse.getEntity());
return jsonResult;
}else{
System.err.println(EntityUtils.toString( httpResponse.getEntity()));
}
}catch (Exception e){
e.printStackTrace();
}finally {
try {
httpClient.close();
}catch (Exception e){
e.printStackTrace();
}
}
return null;
}
/**
* 封装post请求
* @return
*/
public static Map<String,Object> doPostWexin(String url, String body, WxMpProperties wxMpProperties){
CloseableHttpClient httpClient = HttpClients.createDefault();
HttpPost httpPost = new HttpPost(url);
httpPost.addHeader("Content-Type","application/json;chartset=utf-8");
httpPost.addHeader("Accept", "application/json");
try{
String token = WechatPayUtils.getToken("POST", new URL(url), body, wxMpProperties);
httpPost.addHeader("Authorization", token);
if(body==null){
throw new IllegalArgumentException("data参数不能为空");
}
StringEntity stringEntity = new StringEntity(body,"utf-8");
httpPost.setEntity(stringEntity);
CloseableHttpResponse httpResponse = httpClient.execute(httpPost);
HttpEntity httpEntity = httpResponse.getEntity();
if(httpResponse.getStatusLine().getStatusCode() == 200){
String jsonResult = EntityUtils.toString(httpEntity);
return JSON.readValue(jsonResult, HashMap.class);
}else{
String s = EntityUtils.toString(httpEntity);
System.err.println("微信支付错误信息"+s);
return JSON.readValue(s, HashMap.class);
}
}catch (Exception e){
e.printStackTrace();
}finally {
try{
httpClient.close();
}catch (Exception e){
e.printStackTrace();
}
}
return null;
}
}
此类用来微信加密解密
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.sensetime.box.pay.web.wechat.common.WxMpProperties;
import com.wechat.pay.contrib.apache.httpclient.util.AesUtil;
import org.springframework.util.Base64Utils;
import javax.crypto.Cipher;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.ByteArrayInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.*;
import java.security.cert.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
/**
* @author BM_hyjw
*
* 微信支付工具
*
*
*/
public class WechatPayUtils {
private static final ObjectMapper JSON=new ObjectMapper();
/**
* 获取私钥
* @param filename 私钥文件路径 (required)
* @return 私钥对象
*/
public static PrivateKey getPrivateKey(String filename) throws IOException {
System.out.println("filename:" + filename);
String content = new String(Files.readAllBytes(Paths.get(filename)), "utf-8");
try {
String privateKey = content.replace("-----BEGIN PRIVATE KEY-----", "")
.replace("-----END PRIVATE KEY-----", "")
.replaceAll("\\s+", "");
KeyFactory kf = KeyFactory.getInstance("RSA");
return kf.generatePrivate(new PKCS8EncodedKeySpec(Base64.getDecoder().decode(privateKey)));
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("当前Java环境不支持RSA", e);
} catch (InvalidKeySpecException e) {
throw new RuntimeException("无效的密钥格式");
}
}
/**
* 用微信V3密钥解密响应体.
*
* @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
* @return the string
* @throws GeneralSecurityException the general security exception
*/
public static String decryptResponseBody(String associatedData, String nonce, String ciphertext) {
try {
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
SecretKeySpec key = new SecretKeySpec(WxpayConfig.v3Key.getBytes(StandardCharsets.UTF_8), "AES");
GCMParameterSpec spec = new GCMParameterSpec(128, nonce.getBytes(StandardCharsets.UTF_8));
cipher.init(Cipher.DECRYPT_MODE, key, spec);
cipher.updateAAD(associatedData.getBytes(StandardCharsets.UTF_8));
byte[] bytes;
try {
bytes = cipher.doFinal(Base64Utils.decodeFromString(ciphertext));
} catch (GeneralSecurityException e) {
throw new IllegalArgumentException(e);
}
return new String(bytes, StandardCharsets.UTF_8);
} catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
throw new IllegalStateException(e);
} catch (InvalidKeyException | InvalidAlgorithmParameterException e) {
throw new IllegalArgumentException(e);
}
}
/**
* 回调验签
* https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay4_1.shtml
* @param wechatpaySerial 回调head头部
* @param wechatpaySignature 回调head头部
* @param wechatpayTimestamp 回调head头部
* @param wechatpayNonce 回调head头部
* @param body 请求数据
* @return
*/
public static boolean responseSignVerify(String wechatpaySerial, String wechatpaySignature, String wechatpayTimestamp, String wechatpayNonce, String body) {
FileInputStream fileInputStream = null;
try {
String signatureStr = buildMessageS(wechatpayTimestamp, wechatpayNonce, body);
Signature signer = Signature.getInstance("SHA256withRSA");
fileInputStream = new FileInputStream(WxpayConfig.KeyPath);
X509Certificate receivedCertificate = loadCertificate(fileInputStream);
signer.initVerify(receivedCertificate);
signer.update(signatureStr.getBytes(StandardCharsets.UTF_8));
return signer.verify(Base64.getDecoder().decode(wechatpaySignature));
} catch (Exception e ) {
e.printStackTrace();
} finally {
if (fileInputStream != null) {
try {
fileInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return false;
}
/**
* 回调验签-加载微信平台证书
* @param inputStream
* @return
*/
public static X509Certificate loadCertificate(InputStream inputStream) {
try {
CertificateFactory cf = CertificateFactory.getInstance("X509");
X509Certificate cert = (X509Certificate) cf.generateCertificate(inputStream);
cert.checkValidity();
return cert;
} catch (CertificateExpiredException e) {
throw new RuntimeException("证书已过期", e);
} catch (CertificateNotYetValidException e) {
throw new RuntimeException("证书尚未生效", e);
} catch (CertificateException e) {
throw new RuntimeException("无效的证书", e);
}
}
/**
* 回调验签-构建签名数据
* @param
* @return
*/
public static String buildMessageS(String wechatpayTimestamp, String wechatpayNonce, String body) {
return wechatpayTimestamp + "\n"
+ wechatpayNonce + "\n"
+ body + "\n";
}
/**
* 生成token 也就是生成签名
*
* @param method
* @param url
* @param body
* @return
* @throws Exception
*/
public static String getToken(String method, URL url, String body, WxMpProperties wxMpProperties) throws Exception {
String nonceStr = getNonceStr();
long timestamp = System.currentTimeMillis() / 1000;
String message = buildMessage(method, url, timestamp, nonceStr, body);
String signature = sign(message.getBytes("utf-8"),wxMpProperties);
return "WECHATPAY2-SHA256-RSA2048 " + "mchid=\"" + wxMpProperties.getMchId() + "\","
+ "nonce_str=\"" + nonceStr + "\","
+ "timestamp=\"" + timestamp + "\","
+ "serial_no=\"" + wxMpProperties.getMchSerialNo() + "\","
+ "signature=\"" + signature + "\"";
}
/**
* 获取平台证书
*
* @return
*/
public static Map<String, X509Certificate> refreshCertificate(WxMpProperties wxMpProperties) throws Exception {
Map<String, X509Certificate> certificateMap = new HashMap();
// 1: 执行get请求
JsonNode jsonNode = JSON.readTree(HttpUtils.doGet(WechatUrlConfig.CERTIFICATESURL,wxMpProperties));
// 2: 获取平台验证的相关参数信息
JsonNode data = jsonNode.get("data");
if (data != null) {
for (int i = 0; i < data.size(); i++) {
JsonNode encrypt_certificate = data.get(i).get("encrypt_certificate");
//对关键信息进行解密
AesUtil aesUtil = new AesUtil(WxpayConfig.v3Key.getBytes());
String associated_data = encrypt_certificate.get("associated_data").toString().replaceAll("\"", "");
String nonce = encrypt_certificate.get("nonce").toString().replaceAll("\"", "");
String ciphertext = encrypt_certificate.get("ciphertext").toString().replaceAll("\"", "");
//证书内容
String certStr = aesUtil.decryptToString(associated_data.getBytes(), nonce.getBytes(), ciphertext);
//证书内容转成证书对象
CertificateFactory cf = CertificateFactory.getInstance("X509");
X509Certificate x509Cert = (X509Certificate) cf.generateCertificate(
new ByteArrayInputStream(certStr.getBytes("utf-8"))
);
String serial_no = data.get(i).get("serial_no").toString().replaceAll("\"", "");
certificateMap.put(serial_no, x509Cert);
}
}
return certificateMap;
}
/**
* 生成签名
*
* @param message
* @return
* @throws Exception
*/
public static String sign(byte[] message,WxMpProperties wxMpProperties) throws Exception {
Signature sign = Signature.getInstance("SHA256withRSA");
sign.initSign(getPrivateKey(wxMpProperties.getKeyPath()));
sign.update(message);
return Base64.getEncoder().encodeToString(sign.sign());
}
/**
* 生成签名串
*
* @param method
* @param url
* @param timestamp
* @param nonceStr
* @param body
* @return
*/
public static String buildMessage(String method, URL url, long timestamp, String nonceStr, String body) {
String canonicalUrl = url.getPath();
if (url.getQuery() != null) {
canonicalUrl += "?" + url.getQuery();
}
return method + "\n"
+ canonicalUrl + "\n"
+ timestamp + "\n"
+ nonceStr + "\n"
+ body + "\n";
}
//
/**
* 验证签名
*
* @param certificate
* @param message
* @param signature
* @return
*/
public static boolean verify(X509Certificate certificate, byte[] message, String signature) {
try {
Signature sign = Signature.getInstance("SHA256withRSA");
sign.initVerify(certificate);
sign.update(message);
return sign.verify(Base64.getDecoder().decode(signature));
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("当前Java环境不支持SHA256withRSA", e);
} catch (SignatureException e) {
throw new RuntimeException("签名验证过程发生了错误", e);
} catch (InvalidKeyException e) {
throw new RuntimeException("无效的证书", e);
}
}
/**
* 生成随机数
*
* @return
*/
public static String getNonceStr() {
return UUID.randomUUID().toString()
.replaceAll("-", "")
.substring(0, 32);
}
/**
* 拼接参数
*
* @return
*/
private static String buildMessageTwo(String appId, long timestamp, String nonceStr, String packag) {
return appId + "\n"
+ timestamp + "\n"
+ nonceStr + "\n"
+ packag + "\n";
}
// /**
// * 生成签名
// *
// * @return
// */
//
// private static String sign(byte[] message) throws NoSuchAlgorithmException, SignatureException, IOException, InvalidKeyException {
// Signature sign = Signature.getInstance("SHA256withRSA"); //SHA256withRSA
// sign.initSign(WechatPayUtils.getPrivateKey(WxpayConfig.KeyPath));
// sign.update(message);
// return Base64.getEncoder().encodeToString(sign.sign());
// }
}
满足微信接口需求
import java.security.SecureRandom;
import java.util.HashMap;
import java.util.Random;
import static com.sensetime.box.pay.web.wechat.config.WechatPayUtils.sign;
public class WeixinchatPayUtils {
private static final String SYMBOLS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
private static final Random RANDOM = new SecureRandom();
/**
* 获取随机字符串 Nonce Str
*
* @return String 随机字符串
*/
public static String getNonceStr() {
char[] nonceChars = new char[32];
for (int index = 0; index < nonceChars.length; ++index) {
nonceChars[index] = SYMBOLS.charAt(RANDOM.nextInt(SYMBOLS.length()));
}
return new String(nonceChars);
}
/**
* 拼接参数
*
* @return
*/
private static String buildMessageTwo(String appId, long timestamp, String nonceStr, String packag) {
return appId + "\n"
+ timestamp + "\n"
+ nonceStr + "\n"
+ packag + "\n";
}
}
这里是本此用到的接口调用地址
public class WechatUrlConfig {
/**
* 获取证书
*/
public static final String CERTIFICATESURL = "https://api.mch.weixin.qq.com/v3/certificates";
/**
* 退款地址
*/
public static final String REFUNDSURL = "https://api.mch.weixin.qq.com/v3/refund/domestic/refunds";
/**
* 创建支付分订单
*/
public static final String SERVICEORDER = "https://api.mch.weixin.qq.com/v3/payscore/serviceorder";
/**
* 取消支付分订单前缀
*/
public static final String ORDERCANCELPREFIX = "https://api.mch.weixin.qq.com/v3/payscore/serviceorder/";
/**
* 取消支付分订单后缀
*/
public static final String ORDERCANCELSUFFIX = "/cancel";
/**
* 完结支付分订单前缀
*/
public static final String SERVICEORDERCOMPLETEPREFIX = "https://api.mch.weixin.qq.com/v3/payscore/serviceorder/";
/**
* 完结支付分订单后缀
*/
public static final String SERVICEORDERCOMPLETESUFFIX = "/complete";
/**
* 获取调用凭证
*/
public static final String GET_WXPAYFACE_AUTHINFO = "https://payapp.weixin.qq.com/face/get_wxpayface_authinfo";
/**
* 查询订单
*/
public static final String FINDBYID = "https://api.mch.weixin.qq.com/v3/payscore/serviceorder";
/**
* 查询退款订单
*/
public static final String FINDREFUND = "https://api.mch.weixin.qq.com/v3/refund/domestic/refunds/";
}
开发调用
V2刷脸授权接口【get_wxpayface_authinfo】
从上面接口分析我们需要硬件提供【rawdata,device_id】这两个参数,其他参数我们都有
返回给硬件端的数据主要是【authinfo】
//微信配置
@Autowired
private WxMpProperties wxMpProperties;
/**
* 这里提供的只是一个调用的类|具体业务需要具体分析
* @param wxFaceAuthInfoReq 硬件提供参数
*/
public Map<String,String> getWxFaceAuthInfoReqMap(WxFaceAuthInfoReq wxFaceAuthInfoReq){
map.put("appid",wxMpProperties.getAppId());
map.put("mch_id",wxMpProperties.getMchId());
//工具类微信随机数
map.put("nonce_str",WXPayUtil.generateNonceStr());
//自己平台中商户ID
map.put("store_id","11111111111");
//中文会导致调用失败
String text = "刷脸一号柜台";
String s = Base64.getEncoder().encodeToString(text.getBytes());
map.put("store_name",s);
map.put("device_id",wxFaceAuthInfoReq.getDeviceUuid());
map.put("rawdata",wxFaceAuthInfoReq.getRawdata());
//这里使用MD5
map.put("sign_type","MD5");
//指定位数的时间戳
long timeStampSec = System.currentTimeMillis()/1000;
String timestamp = String.format("%010d", timeStampSec);
map.put("now",timestamp);
//版本固定
map.put("version","1");
//加密和生成微信v2指定的xml格式
String sign = WXPayUtil.generateSignedXml(map, WxpayConfig.KEYAPI, SignType.MD5);
log.info("构建微信获取刷脸授权XML参数:【{}】",sign);
HttpHeaders headers = new HttpHeaders();
HttpEntity<String> stringHttpEntity = new HttpEntity<String>(sign,headers);
RestTemplate restTemplate = new RestTemplate();
//发起http调用
ResponseEntity<String> exchange = restTemplate.exchange("https://payapp.weixin.qq.com/face/get_wxpayface_authinfo",
HttpMethod.POST,
stringHttpEntity,
String.class);
//转成map方便取值
Map<String, String> stringMap = WXPayUtil.xmlToMap(exchange.getBody());
return stringMap;
}
V3支付相关接口【接口太多部分案例】
创建支付分订单
从文档中得知V3接口而且是同步接口,回调地址只是完成支付和确认订单的时候才会回调,所以我们这个接口使用【用户免确认模式】,还有订单风险金分为好多模式,这个具体看具体需求,风险金额上限是200元【找客服确认过】具体填多少这个还是要看具体业务。
入参:
订单号(自己平台的订单号)
微信用户openID(硬件端刷脸后会获得用户的openID)
具体操作步骤:
1、封装调用微信接口的数据
2、调用微信接口
3、同步获得微信数据
4、记录状态封装入库
/**
* @param wxPayPointsCreReq 订单号|微信用户openID
* 封装数据
*
* @return
*/
private Map<String, Object> getOrderCreateMap(
WxPayPointsCreReq wxPayPointsCreReq) {
//!!!!!!!!!!!!幂等校验等操作
Map<String, Object> map = new HashMap();
//商户订单号
map.put("out_order_no", wxPayPointsCreReq.getOrderCode());
//商户APPID
map.put("appid", wxMpProperties.getAppId());
//服务ID
map.put("service_id", wxMpProperties.getServiceId());
//服务信息建议写配置表中
map.put("service_introduction", "微信刷脸支付");
//服务时间段
Map<String, Object> timeRangeMap = new HashMap<>();
//开始时间
Calendar calStart = Calendar.getInstance();
calStart.setTime(new Date());
//这个是时间限制
calStart.add(Calendar.SECOND, Integer.valueOf("30"));
timeRangeMap.put("start_time",DateUtils.dateToString(DateUtils.YMDHMS,calStart.getTime()));
//开始时间备注
timeRangeMap.put("start_time_remark","交易开始");
//结束时间
Calendar calEnd = Calendar.getInstance();
calEnd.setTime(new Date());
calEnd.add(Calendar.SECOND, Integer.valueOf("120"));
timeRangeMap.put("end_time",DateUtils.dateToString(DateUtils.YMDHMS,calEnd.getTime()));
//开始时间备注
timeRangeMap.put("end_time_remark","交易结束");
map.put("time_range", timeRangeMap);
//订单风险金信息
Map<String, Object> riskFundMap = new HashMap<>();
/**
* 根据具体业务来
* DEPOSIT:押金
* ADVANCE:预付款
* CASH_DEPOSIT:保证金
* 【先享模式】(评估不通过不可使用服务)可填名称为
* ESTIMATE_ORDER_COST:预估订单费用
*/
riskFundMap.put("name","");
//最大值200元
BigDecimal risk_fund_amount = new BigDecimal("").multiply(new BigDecimal(100));
riskFundMap.put("amount",risk_fund_amount.intValue());
riskFundMap.put("description","商品的预估费用");
map.put("risk_fund", riskFundMap);
//用户确认订单和付款成功回调通知的地址
map.put("notify_url", "http:****************");
//openId
map.put("openid", wxPayPointsCreReq.getOpenId());
//是否需要用户确认 0 免确认
map.put("need_user_confirm", "1".equals("0"))?true:false);
log.info("创建微信支付订单map:【{}】",map);
ObjectMapper objectMapper = new ObjectMapper();
String body = objectMapper.writeValueAsString(map);
Map<String, Object> stringObjectMap = null;
WxPayPointsCreDto wxPayPointsCre = new WxPayPointsCreDto();
try {
stringObjectMap = HttpUtils.doPostWexin(
WechatUrlConfig.SERVICEORDER,
body,
wxMpProperties);
log.info("创建支付分订单得到参数---------stringObjectMap--------:{}",stringObjectMap);
//返回错误信息
if(!Objects.isNull(stringObjectMap) && !Objects.isNull(stringObjectMap.get("message"))){
log.info("创建支付分订单失败---------获得微信错误信息--------:{}",ResultRes.error(stringObjectMap.get("message").toString()));
return ResultRes.error(ResEnum.PAYMENT_IS_NO_WECHAT_END.getValue(),stringObjectMap.get("message").toString());
}
//!!!!!!!!!!!!!!!!!!!!!!!!!!!
//自己的业务入库等操作
} catch (Exception ex) {
ex.printStackTrace();
return ResultRes.error();
}
return map;
}
查询支付分订单
/**
* 查询支付分订单API
* @param out_order_no 创建订单时的订单号
*/
public Map<String,Object> findById(String out_order_no) throws Exception{
try {
String findById = HttpUtils.doGet(
WechatUrlConfig.FINDBYID +
"?service_id=" + wxMpProperties.getServiceId()+
"&out_order_no=" + out_order_no +
"&appid=" + wxMpProperties.getAppId(), wxMpProperties);
ObjectMapper JSON=new ObjectMapper();
HashMap<String,Object> hashMap = JSON.readValue(findById, HashMap.class);
log.info("查询支付分订单API,状态:{},数据集:【{}】",hashMap.get("state").toString(),hashMap);
return hashMap;
} catch (Exception ex) {
}
return null;
}
退款
从文档得知需要【transaction_id,商家退款单号,退款金额,这笔订单的订单金额】然后退款需要回调才知道结果
创建退款
private Map<String, Object> getRefundMap(ReFundedReq reFundedReq,
String out_order_no) {
Map<String, Object> byId = this.findById(out_order_no);
Map<String,Object> collection = (Map<String,Object>)byId.get("collection");
Object transaction_id= ((Map<String,Object>)((List<Map<String,Object>>)collection.get("details")).get(0))
.get("transaction_id");
Map<String, Object> map = new HashMap<>();
map.put("transaction_id",transaction_id.toString());
map.put("out_refund_no",reFundedReq.getOrderRefundUuId());
Map<String, Object> mapAmount = new HashMap<>();
mapAmount.put("refund",reFundedReq.getRefundAmount());
mapAmount.put("total",
new BigDecimal(reFundedReq.getAmount()).multiply(new BigDecimal(100)).intValue());
mapAmount.put("currency", "CNY");
map.put("notify_url", "http://IP:POST/vx/refundCallBack");
map.put("amount",mapAmount);
ObjectMapper objectMapper = new ObjectMapper();
String body = objectMapper.writeValueAsString(map);
try {
stringObjectMap = HttpUtils.doPostWexin(
WechatUrlConfig.REFUNDSURL,
body,wxMpProperties);
log.info("微信退款调用完成ID:{}",stringObjectMap );
//退款成功逻辑
} catch (Exception ex) {
//退款错误逻辑!!!!!!!!!!!!!
}
return map;
}
退款回调
controller
@RequestMapping(value = "/refundCallBack",produces = MediaType.APPLICATION_JSON_VALUE)
@ApiOperation(value = "微信退款回调", nickname = "refundCallBack")
public String refundCallBack(HttpServletRequest request){
return wxPayService.refundCallBack(request);
}
回调工具方法
public static Map<String, X509Certificate> certificateMap = new ConcurrentHashMap<>(); // 定义全局容器 保存微信平台证书公钥
/**
* 验证微信签名
* @param request
* @param body
* @return
* @throws Exception
*/
private boolean verifiedSign(HttpServletRequest request,String body) throws Exception {
//微信返回的证书序列号
String serialNo = request.getHeader("Wechatpay-Serial");
//微信返回的随机字符串
String nonceStr = request.getHeader("Wechatpay-Nonce");
//微信返回的时间戳
String timestamp = request.getHeader("Wechatpay-Timestamp");
//微信返回的签名
String wechatSign = request.getHeader("Wechatpay-Signature");
//组装签名字符串
String signStr = Stream.of(timestamp, nonceStr, body)
.collect(Collectors.joining("\n", "", "\n"));
//当证书容器为空 或者 响应提供的证书序列号不在容器中时 就应该刷新了
if (certificateMap.isEmpty() || !certificateMap.containsKey(serialNo)) {
certificateMap= WechatPayUtils.refreshCertificate(wxMpProperties);
}
//根据序列号获取平台证书
X509Certificate certificate = certificateMap.get(serialNo);
//获取失败 验证失败
if (certificate == null){
return false;
}
//SHA256withRSA签名
Signature signature = Signature.getInstance("SHA256withRSA");
signature.initVerify(certificate);
signature.update(signStr.getBytes());
//返回验签结果
return signature.verify(Base64Utils.decodeFromString(wechatSign));
}
/**
* 获取请求文体
* @param request
* @return
* @throws IOException
*/
public static String getRequestBody(HttpServletRequest request) throws IOException {
ServletInputStream stream = null;
BufferedReader reader = null;
StringBuffer sb = new StringBuffer();
try {
stream = request.getInputStream();
// 获取响应
reader = new BufferedReader(new InputStreamReader(stream));
String line;
while ((line = reader.readLine()) != null) {
sb.append(line);
}
} catch (IOException e) {
throw new IOException("读取返回支付接口数据流出现异常!");
} finally {
reader.close();
}
return sb.toString();
}
退款
@Override
public String refundCallBack(HttpServletRequest request) {
Map<String,String> map = new HashMap<>(2);
try {
//微信返回的请求体
String body = getRequestBody(request);
//如果验证签名序列号通过
if (verifiedSign(request,body)){
//微信支付通知实体类
HashMap hashMap = JSONObject.parseObject(body, HashMap.class);
//成功 此参数是支付创建退款订单的订单号
String out_refund_no = (String)hashMap .get("out_refund_no");
//!!!!!!!!!!!!!!自己的业务
//通知微信正常接收到消息,否则微信会轮询该接口
map.put("code","SUCCESS");
map.put("message","成功");
return JSONObject.toJSONString(map);
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}