【微信支付】java-微信小程序支付-V3接口

一、对接前准备

最开始需要在微信支付的官网注册一个商户;
在管理页面中申请关联小程序,通过小程序的 appid 进行关联;商户号和appid之间是多对多的关系
进入微信公众平台,功能-微信支付中确认关联

具体流程请浏览官方文档:接入前准备-小程序支付 | 微信支付商户平台文档中心

流程走完之后,需要获取以下参数:

1,商户 id: mchId,

2,小程序id:appId
3,商户证书序列号: 这个商户证书序列号在申请完证书之后就可以看到
4, 商户APIV3密钥, 我对接的是v3 接口 所以用APIV3密钥

当你按照文档下载商户证书zip,解压得到4个文件,一定要保存好。不能泄露

二、开始写代码

1.pom引入微信库

<dependency>
    <groupId>com.github.wechatpay-apiv3</groupId>
    <artifactId>wechatpay-apache-httpclient</artifactId>
    <version>0.4.7</version>
</dependency>

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.80</version>
</dependency>

2.yml文件写入配置

wxpay:
  #应用编号
  appId: xxxxxxxxxxxxx

  #商户号
  mchId: xxxxxxxxxxxxx

  # APIv3密钥
  apiV3Key: xxxxxxxxxxxxx

  # 支付通知回调, 本地测试内网穿透地址
  notifyUrl: http://405c3382p5.goho.co:25325/wenbo-pay/notify/payNotify

  # 退款通知回调,  本地测试内网穿透地址
  refundNotifyUrl: http://405c3382p5.goho.co:25325/wenbo-pay/notify/refundNotify

  # 密钥路径,resources根目录下
  keyPemPath: apiclient_key.pem

  # 商户证书序列号
  serialNo: xxxxxxxxxxxxx

  # 小程序密钥
  appSecret: xxxxxxxxxxxxx

3.商户API私钥 放入resources根目录下  能获取到就行

编写配置类获取yml配置

package com.example.pay.config;

import com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder;
import com.wechat.pay.contrib.apache.httpclient.auth.PrivateKeySigner;
import com.wechat.pay.contrib.apache.httpclient.auth.Verifier;
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Credentials;
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Validator;
import com.wechat.pay.contrib.apache.httpclient.cert.CertificatesManager;
import com.wechat.pay.contrib.apache.httpclient.exception.HttpCodeException;
import com.wechat.pay.contrib.apache.httpclient.exception.NotFoundException;
import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.impl.client.CloseableHttpClient;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.security.PrivateKey;

/**
 * @ClassName: WechatPayConfig
 * @author: tang
 * @createTime 2023-08-10
 * 小程序支付配置类
 */
@Component
@Data
@Slf4j
@ConfigurationProperties(prefix = "wxpay")
public class WechatPayConfig {

    /**
     * 应用编号
     */
    private String appId;
    /**
     * 商户号
     */
    private String mchId;

    /**
     * APIv3密钥
     */
    private String apiV3Key;
    /**
     * 支付通知回调地址
     */
    private String notifyUrl;
    /**
     * 退款回调地址
     */
    private String refundNotifyUrl;

    /**
     * API 证书中的 key.pem
     */
    private String keyPemPath;

    /**
     * 商户序列号
     */
    private String serialNo;


    /**
     * 获取商户的私钥文件
     *
     * @param keyPemPath
     * @return
     */
    public PrivateKey getPrivateKey(String keyPemPath) {

        InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream(keyPemPath);
        if (inputStream == null) {
            throw new RuntimeException("私钥文件不存在");
        }
        return PemUtil.loadPrivateKey(inputStream);
    }

    /**
     * 获取证书管理器实例
     *
     * @return
     */
    @Bean
    public Verifier getVerifier() throws GeneralSecurityException, IOException, HttpCodeException, NotFoundException {

        log.info("获取证书管理器实例");

        //获取商户私钥
        PrivateKey privateKey = getPrivateKey(keyPemPath);

        //私钥签名对象
        PrivateKeySigner privateKeySigner = new PrivateKeySigner(serialNo, privateKey);

        //身份认证对象
        WechatPay2Credentials wechatPay2Credentials = new WechatPay2Credentials(mchId, privateKeySigner);

        // 使用定时更新的签名验证器,不需要传入证书
        CertificatesManager certificatesManager = CertificatesManager.getInstance();
        certificatesManager.putMerchant(mchId, wechatPay2Credentials, apiV3Key.getBytes(StandardCharsets.UTF_8));

        return certificatesManager.getVerifier(mchId);
    }


    /**
     * 获取支付http请求对象
     *
     * @param verifier
     * @return
     */
    @Bean(name = "wxPayClient")
    public CloseableHttpClient getWxPayClient(Verifier verifier) {

        //获取商户私钥
        PrivateKey privateKey = getPrivateKey(keyPemPath);

        WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
                .withMerchant(mchId, serialNo, privateKey)
                .withValidator(new WechatPay2Validator(verifier));

        // 通过WechatPayHttpClientBuilder构造的HttpClient,会自动的处理签名和验签,并进行证书自动更新
        return builder.build();
    }
    
}

封装 支付-退款 请求API

package com.example.pay.config;

import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpEntity;
import org.apache.http.HttpStatus;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;

/**
 * @ClassName: WechatPayRequest
 * @author: tang
 * @createTime 2023-08-10
 * 封装公共的请求API,用于在业务请求时的统一处理。
 */
@Component
@Slf4j
public class WechatPayRequest {


    @Resource
    private CloseableHttpClient wxPayClient;

    /**
     * 支付请求
     *
     * @param url
     * @param paramsStr
     * @return
     */
    public String wechatHttpOrderPost(String url, String paramsStr) {
        try {
            HttpPost httpPost = new HttpPost(url);
            StringEntity stringEntity = new StringEntity(paramsStr, "utf-8");
            stringEntity.setContentType("application/json");
            httpPost.setEntity(stringEntity);
            httpPost.setHeader("Accept", "application/json");

            CloseableHttpResponse response = wxPayClient.execute(httpPost);
            //响应体
            HttpEntity entity = response.getEntity();
            String body = entity == null ? "" : EntityUtils.toString(entity);
            //响应状态码
            int statusCode = response.getStatusLine().getStatusCode();
            //处理成功,204是,关闭订单时微信返回的正常状态码
            if (statusCode == HttpStatus.SC_OK || statusCode == HttpStatus.SC_NO_CONTENT) {
                log.info("成功, 返回结果 = " + body);
            } else {
                String msg = "微信支付请求失败,响应码 = " + statusCode + ",返回结果 = " + body;
                log.info("支付模块-生成订单 = " + msg);
                throw new RuntimeException(msg);
            }
            return body;
        } catch (Exception e) {
            throw new RuntimeException(e.getMessage());
        }
    }

    /**
     * 退款请求
     *
     * @param url
     * @param paramsStr
     * @return
     */
    public String wechatHttpPost(String url, String paramsStr) {
        try {
            HttpPost httpPost = new HttpPost(url);
            StringEntity stringEntity = new StringEntity(paramsStr, "utf-8");
            stringEntity.setContentType("application/json");
            httpPost.setEntity(stringEntity);
            httpPost.setHeader("Accept", "application/json");

            CloseableHttpResponse response = wxPayClient.execute(httpPost);
            //响应体
            HttpEntity entity = response.getEntity();
            String body = entity == null ? "" : EntityUtils.toString(entity);
            //响应状态码
            int statusCode = response.getStatusLine().getStatusCode();

            //处理成功,204是,关闭订单时微信返回的正常状态码
            if (statusCode == HttpStatus.SC_OK || statusCode == HttpStatus.SC_NO_CONTENT) {
                log.info("成功, 返回结果 = " + body);
                // 请求成功或已处理成功,返回成功的响应
                return "退款处理中";
            } else if (statusCode == HttpStatus.SC_BAD_REQUEST || statusCode == HttpStatus.SC_INTERNAL_SERVER_ERROR) {
                // 请求参数错误或系统错误,返回失败的响应
                JSONObject json = JSONObject.parseObject(body);
                return json.getString("message");
            } else if (statusCode == HttpStatus.SC_FORBIDDEN) {
                // 权限问题,没有退款权限
                return "没有退款权限";
            } else if (statusCode == HttpStatus.SC_NOT_FOUND) {
                // 订单号不存在
                return "订单号不存在";
            } else if (statusCode == 429) {
                // 频率限制
                return "退款请求频率过高,请稍后重试";
            } else if (statusCode == HttpStatus.SC_PAYMENT_REQUIRED) {
                // 余额不足
                return "商户余额不足,请充值后重试";
            } else {
                // 其他状态码,返回通用的失败响应
                return "退款失败,请稍后重试";
            }
        } catch (Exception e) {
            log.info("支付模块-退款失败 = " + e.getMessage());
            JSONObject json = JSONObject.parseObject(e.getMessage());
            return json.getString("message");
        }
    }

}

支付回调解密处理

package com.example.pay.config;

import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import com.wechat.pay.contrib.apache.httpclient.auth.Verifier;
import com.wechat.pay.contrib.apache.httpclient.util.AesUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.util.EntityUtils;

import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.time.DateTimeException;
import java.time.Duration;
import java.time.Instant;
import java.util.Map;

import static com.wechat.pay.contrib.apache.httpclient.constant.WechatPayHttpHeaders.*;

/**
 * @ClassName: WechatPayValidator
 * @author: tang
 * @createTime 2023-08-10
 * 回调校验器
 * 用于对微信支付成功后的回调数据进行签名验证,保证数据的安全性与真实性。
 */
@Slf4j
public class WechatPayValidator {

    /**
     * 应答超时时间,单位为分钟
     */
    private static final long RESPONSE_EXPIRED_MINUTES = 5;
    private final Verifier verifier;
    private final String requestId;
    private final String body;


    public WechatPayValidator(Verifier verifier, String requestId, String body) {
        this.verifier = verifier;
        this.requestId = requestId;
        this.body = body;
    }

    protected static IllegalArgumentException parameterError(String message, Object... args) {
        message = String.format(message, args);
        return new IllegalArgumentException("parameter error: " + message);
    }

    protected static IllegalArgumentException verifyFail(String message, Object... args) {
        message = String.format(message, args);
        return new IllegalArgumentException("signature verify fail: " + message);
    }

    public final boolean validate(HttpServletRequest request) {
        try {
            //处理请求参数
            validateParameters(request);

            //构造验签名串
            String message = buildMessage(request);

            String serial = request.getHeader(WECHAT_PAY_SERIAL);
            String signature = request.getHeader(WECHAT_PAY_SIGNATURE);

            //验签
            if (!verifier.verify(serial, message.getBytes(StandardCharsets.UTF_8), signature)) {
                throw verifyFail("serial=[%s] message=[%s] sign=[%s], request-id=[%s]",
                        serial, message, signature, requestId);
            }
        } catch (IllegalArgumentException e) {
            log.warn(e.getMessage());
            return false;
        }

        return true;
    }

    private void validateParameters(HttpServletRequest request) {

        // NOTE: ensure HEADER_WECHAT_PAY_TIMESTAMP at last
        String[] headers = {WECHAT_PAY_SERIAL, WECHAT_PAY_SIGNATURE, WECHAT_PAY_NONCE, WECHAT_PAY_TIMESTAMP};

        String header = null;
        for (String headerName : headers) {
            header = request.getHeader(headerName);
            if (header == null) {
                throw parameterError("empty [%s], request-id=[%s]", headerName, requestId);
            }
        }

        //判断请求是否过期
        String timestampStr = header;
        try {
            Instant responseTime = Instant.ofEpochSecond(Long.parseLong(timestampStr));
            // 拒绝过期请求
            if (Duration.between(responseTime, Instant.now()).abs().toMinutes() >= RESPONSE_EXPIRED_MINUTES) {
                throw parameterError("timestamp=[%s] expires, request-id=[%s]", timestampStr, requestId);
            }
        } catch (DateTimeException | NumberFormatException e) {
            throw parameterError("invalid timestamp=[%s], request-id=[%s]", timestampStr, requestId);
        }
    }

    private String buildMessage(HttpServletRequest request) {
        String timestamp = request.getHeader(WECHAT_PAY_TIMESTAMP);
        String nonce = request.getHeader(WECHAT_PAY_NONCE);
        return timestamp + "\n"
                + nonce + "\n"
                + body + "\n";
    }

    private String getResponseBody(CloseableHttpResponse response) throws IOException {
        HttpEntity entity = response.getEntity();
        return (entity != null && entity.isRepeatable()) ? EntityUtils.toString(entity) : "";
    }

    /**
     * 对称解密,异步通知的加密数据
     *
     * @param resource 加密数据
     * @param apiV3Key apiV3密钥
     * @param type     1-支付,2-退款
     * @return
     */
    public static Map<String, Object> decryptFromResource(String resource, String apiV3Key, Integer type) {

        String msg = type == 1 ? "支付成功" : "退款成功";
        log.info(msg + ",回调通知,密文解密");
        try {
            //通知数据
            Map<String, String> resourceMap = JSONObject.parseObject(resource, new TypeReference<Map<String, String>>() {
            });
            //数据密文
            String ciphertext = resourceMap.get("ciphertext");
            //随机串
            String nonce = resourceMap.get("nonce");
            //附加数据
            String associatedData = resourceMap.get("associated_data");

            log.info("密文: {}", ciphertext);
            AesUtil aesUtil = new AesUtil(apiV3Key.getBytes(StandardCharsets.UTF_8));
            String resourceStr = aesUtil.decryptToString(associatedData.getBytes(StandardCharsets.UTF_8),
                    nonce.getBytes(StandardCharsets.UTF_8),
                    ciphertext);

            log.info(msg + ",回调通知,解密结果 : {}", resourceStr);
            return JSONObject.parseObject(resourceStr, new TypeReference<Map<String, Object>>() {
            });
        } catch (Exception e) {
            throw new RuntimeException("回调参数,解密失败!");
        }
    }


    /**
     * 将通知参数转化为字符串
     *
     * @param request
     * @return
     */
    public static String readData(HttpServletRequest request) {
        BufferedReader br = null;
        try {
            StringBuilder result = new StringBuilder();
            br = request.getReader();
            for (String line; (line = br.readLine()) != null; ) {
                if (result.length() > 0) {
                    result.append("\n");
                }
                result.append(line);
            }
            return result.toString();
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            if (br != null) {
                try {
                    br.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

编写下单 退款 controller    这里注意 微信用户openid   

package com.example.pay.controller;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import com.example.pay.config.WechatPayConfig;
import com.example.pay.config.WechatPayRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import java.nio.charset.StandardCharsets;
import java.security.PrivateKey;
import java.security.Signature;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;

/**
 * @ClassName: pay
 * @author: tang
 * @createTime 2023-08-16
 */
@Slf4j
@RestController
@RequestMapping("/pay")
public class PayController {


    @Resource
    private WechatPayConfig wechatPayConfig;

    @Resource
    private WechatPayRequest wechatPayRequest;

    /**
     * 预支付订单生成入口
     */
    @GetMapping("/transactions")
    public Map<String, Object> transactions() {
      
        // 统一参数封装
        Map<String, Object> params = new HashMap<>(10);
        // 1,appid:公众号或移动应用的唯一标识符。
        params.put("appid", wechatPayConfig.getAppId());
        // 2,mch_id:商户号,由微信支付分配。
        params.put("mchid", wechatPayConfig.getMchId());
        // 3.description body:商品描述。
        params.put("description", "奥迪a4l 2023-限量款");
        // 4.out_trade_no:商户订单号,由商户自定义。
        params.put("out_trade_no", "we56f45waf4w6a5fwa");
        // 5.notify_url:接收微信支付异步通知回调地址。
        params.put("notify_url", wechatPayConfig.getNotifyUrl());
        // 6.total_fee:订单总金额,单位为分。
        Map<String, Object> amountMap = new HashMap<>(4);
        // 金额单位为分
        amountMap.put("total", 999999);
        amountMap.put("currency", "CNY");
        params.put("amount", amountMap);


        // 7.openid:用户在商户appid下的唯一标识。
        Map<String, Object> payerMap = new HashMap<>(4);
        // openid  需要前端小程序通过用户code 请求微信接口获取用户唯一openid  不懂的看官方文档:https://developers.weixin.qq.com/doc/aispeech/miniprogram/quickuse.html
        payerMap.put("openid", "xxxxxxxxxxxxxxxxxxxx");
        params.put("payer", payerMap);

        String paramsStr = JSON.toJSONString(params);
        log.info("请求参数 ===> {}" + paramsStr);

        // 微信预支付下单接口路径
        String payUrl = "https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi";
        // 获取支付 prepay_id参数
        String resStr = wechatPayRequest.wechatHttpOrderPost(payUrl, paramsStr);


        Map<String, Object> resMap = JSONObject.parseObject(resStr, new TypeReference<Map<String, Object>>() {
        });
        Object prepayId = resMap.get("prepay_id");

        // 得到当前系统时间搓
        String timeStamp = String.valueOf(System.currentTimeMillis() / 1000);

        // 获取签名
        String paySign;
        try {
            StringBuilder sb = new StringBuilder();
            // 应用id
            sb.append(wechatPayConfig.getAppId()).append("\n");
            // 支付签名时间戳
            sb.append(timeStamp).append("\n");
            // 随机字符串
            sb.append("5w7er7wa4fwa5e").append("\n");
            // 预支付交易会话ID  这个要注意 key = "prepay_id=xxxxxx"
            sb.append("prepay_id=").append(prepayId).append("\n");
            // 签名
            Signature sign = Signature.getInstance("SHA256withRSA");
            // 获取商户私钥并进行签名
            PrivateKey privateKey = wechatPayConfig.getPrivateKey(wechatPayConfig.getKeyPemPath());
            sign.initSign(privateKey);
            sign.update(sb.toString().getBytes(StandardCharsets.UTF_8));
            // 得到签名
            paySign = Base64.getEncoder().encodeToString(sign.sign());
        } catch (Exception e) {
            log.error("支付模块_生成交易签名失败!" + e);
            return new HashMap<>();
        }


        // 将签名时数据和签名一起返回前端用于前端吊起支付
        Map<String, Object> map = new HashMap<>();
        // 小程序id
        map.put("appId", wechatPayConfig.getAppId());
        // 时间戳
        map.put("timeStamp", timeStamp);
        // 随机字符串
        map.put("nonceStr", "56523268632356");
        // 预支付交易会话ID
        map.put("package", "prepay_id=" + prepayId);
        // 签名方式
        map.put("signType", "RSA");
        // 签名
        map.put("paySign", paySign);
        return map;
    }


    /**
     * 申请退款
     */
    @GetMapping("/refundOrder")
    public String refundOrder() {

        log.info("根据订单号申请退款,订单号: {}", "要退款的订单号  这里写死");
        // 退款请求路径
        String url = "https://api.mch.weixin.qq.com/v3/refund/domestic/refunds";
        // 设置参数
        Map<String, Object> params = new HashMap<>(2);
        // 要退款的订单编号订单编号
        params.put("out_trade_no", "57984wera64");
        // 商户自定义退款记录单号 用于退款记录的单号 跟退款订单号不是一样的
        int outRefundNo = new Random().nextInt(999999999);
        log.info("退款申请号:{}", outRefundNo);
        params.put("out_refund_no", outRefundNo + "");
        // 退款原因
        params.put("reason", "申请退款");
        // 退款通知回调地址
        params.put("notify_url", wechatPayConfig.getRefundNotifyUrl());

        Map<String, Object> amountMap = new HashMap<>();
        //退款金额,单位:分
        amountMap.put("refund", 999999);
        //原订单金额,单位:分
        amountMap.put("total", 99999);
        //退款币种
        amountMap.put("currency", "CNY");
        params.put("amount", amountMap);
        String paramsStr = JSON.toJSONString(params);
        // todo 插入一条退款记录到数据库
        log.info("请求参数 ===> {}" + paramsStr);
        String res = wechatPayRequest.wechatHttpPost(url, paramsStr);
        log.info("退款结果:{}", res);
        return res;
    }


}

支付 退款 回调controller

package com.example.pay.controller;

import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import com.example.pay.config.WechatPayConfig;
import com.example.pay.config.WechatPayValidator;
import com.wechat.pay.contrib.apache.httpclient.auth.Verifier;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @ClassName: NotifyController
 * @author: tang
 * @createTime 2023-08-10
 */

@RestController
@Slf4j
@RequestMapping("/callback")
public class CallbackController {

    @Resource
    private WechatPayConfig wechatPayConfig;

    @Resource
    private Verifier verifier;

    private final ReentrantLock lock = new ReentrantLock();


    /**
     * 支付回调处理
     *
     * @param request
     * @param response
     * @return
     */
    @PostMapping("/payNotify")
    public Map<String, String> payNotify(HttpServletRequest request, HttpServletResponse response) {
        log.info("支付回调");

        // 处理通知参数
        Map<String, Object> bodyMap = getNotifyBody(request);
        if (bodyMap == null) {
            return falseMsg(response);
        }

        log.warn("=========== 在对业务数据进行状态检查和处理之前,要采用数据锁进行并发控制,以避免函数重入造成的数据混乱 ===========");
        if (lock.tryLock()) {
            try {
                // 解密resource中的通知数据
                String resource = bodyMap.get("resource").toString();
                Map<String, Object> resourceMap = WechatPayValidator.decryptFromResource(resource, wechatPayConfig.getApiV3Key(), 1);
                String orderNo = resourceMap.get("out_trade_no").toString();
                // String transactionId = resourceMap.get("transaction_id").toString();
                // 更改状态 获取订单号  修改订单状态为已支付

                // TODO 根据订单号,做幂等处理,并且在对业务数据进行状态检查和处理之前,要采用数据锁进行并发控制,以避免函数重入造成的数据混乱
                log.warn("=========== 根据订单号,做幂等处理 ===========");
            } finally {
                //要主动释放锁
                lock.unlock();
            }
        }

        //成功应答
        return trueMsg(response);
    }

    /**
     * 退款回调处理
     *
     * @param request
     * @param response
     * @return
     */
    @PostMapping("/refundNotify")
    public Map<String, String> refundNotify(HttpServletRequest request, HttpServletResponse response) {
        log.info("退款回调");

        // 处理通知参数
        Map<String, Object> bodyMap = getNotifyBody(request);
        if (bodyMap == null) {
            return falseMsg(response);
        }

        log.warn("=========== 在对业务数据进行状态检查和处理之前,要采用数据锁进行并发控制,以避免函数重入造成的数据混乱 ===========");
        if (lock.tryLock()) {
            try {
                // 解密resource中的通知数据
                String resource = bodyMap.get("resource").toString();
                Map<String, Object> resourceMap = WechatPayValidator.decryptFromResource(resource, wechatPayConfig.getApiV3Key(), 2);
                String orderNo = resourceMap.get("out_trade_no").toString();
//                String transactionId = resourceMap.get("transaction_id").toString();
                log.info("退款所有参数" + resourceMap);

                // TODO 根据订单号,做幂等处理,并且在对业务数据进行状态检查和处理之前,要采用数据锁进行并发控制,以避免函数重入造成的数据混乱
                // 更改订单状态为已退款
                log.warn("=========== 根据订单号,做幂等处理 ===========");
            } finally {
                //要主动释放锁
                lock.unlock();
            }
        }

        //成功应答
        return trueMsg(response);
    }

    private Map<String, Object> getNotifyBody(HttpServletRequest request) {
        //处理通知参数
        String body = WechatPayValidator.readData(request);
        log.info("退款回调参数:{}", body);

        // 转换为Map
        Map<String, Object> bodyMap = JSONObject.parseObject(body, new TypeReference<Map<String, Object>>() {
        });
        // 微信的通知ID(通知的唯一ID)
        String notifyId = bodyMap.get("id").toString();
        // 验证签名信息
        WechatPayValidator wechatPayValidator
                = new WechatPayValidator(verifier, notifyId, body);
        if (!wechatPayValidator.validate(request)) {

            log.error("通知验签失败");
            return null;
        }
        log.info("通知验签成功");
        return bodyMap;
    }

    private Map<String, String> falseMsg(HttpServletResponse response) {
        Map<String, String> resMap = new HashMap<>(8);
        //失败应答
        response.setStatus(500);
        resMap.put("code", "ERROR");
        resMap.put("message", "通知验签失败");
        return resMap;
    }

    private Map<String, String> trueMsg(HttpServletResponse response) {
        Map<String, String> resMap = new HashMap<>(8);
        //成功应答
        response.setStatus(200);
        resMap.put("code", "SUCCESS");
        resMap.put("message", "成功");
        return resMap;
    }
}

整体项目结构

请求预支付订单 得到预支付订单参数 小程序拿着这些参数拉起支付页面进行支付

支付完成后 微信会回调我们预留的回调接口 

这里使用的是花生壳内网穿透进行回调测试的 

退款业务就不一一截图了  图片太多显得文章拉胯


总结

  • 8
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 25
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 25
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值