支付宝支付

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


开发前准备

文档

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

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

java 刷卡 java刷脸支付_经验分享


第一个接口:提供给硬件调用的接口和此接口需要加密

java 刷卡 java刷脸支付_微信_02


返回给硬件什么参数?


java 刷卡 java刷脸支付_java_03


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


java 刷卡 java刷脸支付_ci_04

开发准备

微信开发大致流程首先找到要调用接口 -> 构建参数 -> 发起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版解压,然后放入项目中

java 刷卡 java刷脸支付_java 刷卡_05

微信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;
    }