支付宝支付

  • 开发前准备
    • 文档
    • 开发准备
      • maven
      • 微信配置类
      • 微信V2-SDK
      • 微信V3工具
    • 开发调用
      • V2刷脸授权接口【get_wxpayface_authinfo】
      • V3支付相关接口【接口太多部分案例】
        • 创建支付分订单
        • 查询支付分订单
        • 退款
          • 创建退款
          • 退款回调

开发前准备

文档

链接: 微信支付流程介绍V2.
链接: 微信扫码支付接口文档V2-SDK.
链接: 微信V3-官方提供github.
链接: 微信支付指引文档.

本文以刷脸支付分为例指引开发微信支付,因为这里用到了V2的接口和V3的接口
链接: 本文主要按照微信刷脸支付分支付:流程.
先明确一下份工,一下画框的地方是后端要解决的问题

第一个接口:提供给硬件调用的接口和此接口需要加密
返回给硬件什么参数?

剩下的接口就是V3的这时候就需要上面第四个连接提供的指引文档了

开发准备

微信开发大致流程首先找到要调用接口 -> 构建参数 -> 发起http调用 -> 同步接收数据 -> 存在回调接口接收回调【解密、幂等校验和回复成功】

//启动类注入http调用Bean
@Beanpublic 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密钥keyv3Key: 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());//自己平台中商户IDmap.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());//这里使用MD5map.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());//商户APPIDmap.put("appid", wxMpProperties.getAppId());//服务IDmap.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:****************");//openIdmap.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();}

退款

 @Overridepublic 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;}

JAVA接入微信刷脸支付分支付【V2、V3两种接入都有提供】相关推荐

  1. 微信公众号页面支付接口java,[Java教程]微信公众号支付(三):页面调用微信支付JS并完成支付...

    [Java教程]微信公众号支付(三):页面调用微信支付JS并完成支付 0 2015-09-15 15:00:30 一.调用微信的JS文件 1.首先要绑定[JS接口安全域名],"公众号设置&q ...

  2. ASP.NET Core Web 支付功能接入 微信-扫码支付篇

    // 随着版本更迭,新版本可能无法完全适用,请参考仓库内的示例. 这篇文章将介绍ASP.NET Core中使用 开源项目 Payment(https://github.com/Essensoft/Pa ...

  3. 前端与后端,顶象设备指纹的两种接入方式

    在如今的移动互联网时代,用户上网的设备多元化.连接互联网的渠道多样化.接入服务的地点任意化,用户的操作行为个性化,用户设备更加难以被识别和跟踪,由此给广大开展数字化业务的企业,尤其互联网企业带来全新的 ...

  4. java web ip_详解Java Web如何限制访问的IP的两种方法

    前一阵子因为在做项目时碰到了这个功能,现在好好总结一下,至于为什么要限制IP访问,我就不多说了.然后百度了一下,现在主要有两种方式去限制IP访问,第一种是最简单的方便的,第二种是通过过滤器来限制访问. ...

  5. 无监督方法实现C++、Java、Python 代码转换,程序员:出了bug怎么办,两种语言都要看吗?...

    点击上方"视学算法",选择加"星标" 重磅干货,第一时间送达 本文转载自:机器之心  |  参与:魔王 Facebook 提出的无监督代码转换方法 TransC ...

  6. java 生成二维码 QRCode、zxing 两种方式

    版权声明:本文为 testcs_dn(微wx笑) 原创文章,非商用自由转载-保持署名-注明出处,谢谢. https://blog.csdn.net/testcs_dn/article/details/ ...

  7. java 读取css文件_java文件读取的两种方式

    JAVA中读取文件(二进制,字符)内容的几种方 JAVA中读取文件内容的方法有很多,比如按字节读取文件内容,按字符读取文件内容,按行读取文件内容,随机读取文件内容等方法,本文就以上方法的具体实现给出代 ...

  8. Java去除掉HTML里面所有标签的两种方法——开源jar包和自己写正则表达式

    Java去除掉HTML里面所有标签,主要就两种,要么用开源的jar处理,要么就自己写正则表达式.自己写的话,可能处理不全一些自定义的标签.企业应用基本都是能找开源就找开源,实在不行才自己写-- 1,开 ...

  9. 19、Java并发编程:线程间协作的两种方式:wait、notify、notifyAll和Condition

    Java并发编程:线程间协作的两种方式:wait.notify.notifyAll和Condition 在前面我们将了很多关于同步的问题,然而在现实中,需要线程之间的协作.比如说最经典的生产者-消费者 ...

最新文章

  1. android中如何执行java命令
  2. mysql修改字段null为空字符串
  3. 从门外汉到 Go 圈网红技术博主的五年历程
  4. git patch 应用
  5. web前端开发,自学的流程可以怎样?
  6. Windows核心编程_组件透明
  7. 数据库 读锁(共享锁)、 写锁(排他锁)
  8. 全网首发:JDK绘制文字:五、字体上下文产生流程
  9. 设计oa系统mysql数据库设计_企业办公自动化OA系统的设计与实现(MyEclipse,MySQL)
  10. 视频理解综述:动作识别、时序动作定位、视频Embedding
  11. 使用Python GDAL库对高分三号全极化SAR影像进行RPC几何校正(PolSARpro格式)
  12. iOS打包静态库的姿势
  13. 苹果电子邮件怎么注册_无需购买 iPhone,可以使用哪些苹果的优质服务?
  14. JS中的window对象和document对象是什么?有什么区别?
  15. 关于游戏程序员的职业规划
  16. wstmart系统研究日志二
  17. 内网渗透代理知识以及渗透某CTF三层靶机实例
  18. 推荐一个软件分享资源站
  19. 人脸识别0-03:insightFace-测试集数据制作-史上最全
  20. win7产生大量evtx文件_Win7退役:用户还在坚守,为何微软却执意放弃?

热门文章

  1. 2021会宁三中高考成绩查询,2019中考分数线
  2. 5.大型电商项目之创建前端展示模板并调用
  3. Android 手机上获取手机当前上网IP地址(手机网关给手机号分配的IP)
  4. 【linux命令】文件管理(上)
  5. 公众号数据全面分析解读(上篇)
  6. 【观察】美达电器:以数字化重塑质量管理体系,构筑车企新“护城河”
  7. 记一次Maven发布Jar包中文乱码解决方法
  8. 类似安卓的点9图片,气泡图片调成自己需要的
  9. 夯实云端协同平台建设,橙色云CDS助力中小企业转型升级
  10. visio版本要和word匹配吗_office2016各个版本 以及 解决visio搜索任何都提示无匹配项问题...