springBoot整合微信支付V3 jsapi

目录

前端调用后端接口发起微信支付订单

发起成功后返回签名等信息给前端调起支付

支付成功后回调支付结果通知


前端调用后端接口发起微信支付订单

1、引入Maven坐标
<!-- 微信支付api v3 -->
<dependency>
   <groupId>com.github.wechatpay-apiv3</groupId>
   <artifactId>wechatpay-apache-httpclient</artifactId>
   <version>0.4.9</version>
</dependency>
2、配置文件
# 微信支付相关参数
# 商户号
wxpay.mch-id=1558950191
# 商户API证书序列号
wxpay.mch-serial-no=34345964330B66427E0D3D28826C4993C77E631F

# 商户私钥文件
wxpay.private-key-path=apiclient_key.pem
# APIv3密钥
wxpay.api-v3-key=UDuLFDcmy5Eb6o0nTNZdu6ek4DDh4K8B
# APPID
wxpay.appid=wx74862e0dfcf69954
# 微信服务器地址
wxpay.domain=https://api.mch.weixin.qq.com
# 接收结果通知地址
# 回调地址
wxpay.notify-domain=https://500c-219-143-130-12.ngrok.io

# APIv2密钥
wxpay.partnerKey: T6m9iK73b0kn9g5v426MKfHQH7X8rKwb
3、配置类  WxPayConfig
package com.tuimi.zxdt.wxpay.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.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;

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

/**
 * @description: 微信配置
 * @author: HK
 * @since: 2024/9/26 11:46
 */
@Configuration
@PropertySource("classpath:config/wxPay.application") //读取配置文件
@ConfigurationProperties(prefix="wxpay") //读取wxpay节点
@Data //使用set方法将wxpay节点中的值填充到当前类的属性中
@Slf4j
public class WxPayConfig {
    // 商户号
    private String mchId;

    // 商户API证书序列号
    private String mchSerialNo;

    // 商户私钥文件
    private String privateKeyPath;

    // APIv3密钥
    private String apiV3Key;

    // APPID
    private String appid;

    // 微信服务器地址
    private String domain;

    // 接收结果通知地址
    private String notifyDomain;

    // APIv2密钥
    private String partnerKey;

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

        try {
            return PemUtil.loadPrivateKey(new FileInputStream(filename));
        } catch (FileNotFoundException e) {
            throw new RuntimeException("私钥文件不存在", e);
        }
    }

    /**
     * 获取签名验证器
     * @return
     */
    @Bean
    public Verifier getVerifier(){

        log.info("获取签名验证器");

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

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

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

        // 获取证书管理器实例
        CertificatesManager certificatesManager = CertificatesManager.getInstance();

        try {
            // 向证书管理器增加需要自动更新平台证书的商户信息
            certificatesManager.putMerchant(mchId, wechatPay2Credentials, apiV3Key.getBytes(StandardCharsets.UTF_8));
        } catch (IOException | GeneralSecurityException | HttpCodeException e) {
            e.printStackTrace();
        }

        try {
            return certificatesManager.getVerifier(mchId);
        } catch (NotFoundException e) {
            e.printStackTrace();
            throw new RuntimeException("获取签名验证器失败");
        }


        // 使用定时更新的签名验证器,不需要传入证书
//        ScheduledUpdateCertificatesVerifier verifier = new ScheduledUpdateCertificatesVerifier(
//                wechatPay2Credentials,
//                apiV3Key.getBytes(StandardCharsets.UTF_8));
//
//        return verifier;
    }


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

        log.info("获取httpClient");

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

        WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
                .withMerchant(mchId, mchSerialNo, privateKey)
                .withValidator(new WechatPay2Validator(verifier));
        // ... 接下来,你仍然可以通过builder设置各种参数,来配置你的HttpClient

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

        return httpClient;
    }

    /**
     * 获取HttpClient,无需进行应答签名验证,跳过验签的流程
     */
    @Bean(name = "wxPayNoSignClient")
    public CloseableHttpClient getWxPayNoSignClient(){

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

        //用于构造HttpClient
        WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
                //设置商户信息
                .withMerchant(mchId, mchSerialNo, privateKey)
                //无需进行签名验证、通过withValidator((response) -> true)实现
                .withValidator((response) -> true);

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

        log.info("== getWxPayNoSignClient END ==");

        return httpClient;
    }
}
4、支付链接枚举 WxApiType
package com.tuimi.zxdt.wxpay.config;

import lombok.AllArgsConstructor;
import lombok.Getter;

/**
 * @description: 支付链接枚举
 * @author: HK
 * @since: 2024/9/26 15:22
 */
@AllArgsConstructor
@Getter
public enum WxApiType {

    /**
     * 支付
     */
    JSAPI_PAY("/v3/pay/transactions/jsapi"),

    /**
     * 查询订单(商户订单号查询)
     */
    ORDER_QUERY_BY_NO("/v3/pay/transactions/out-trade-no/%s"),

    /**
     * 关闭订单
     */
    CLOSE_ORDER_BY_NO("/v3/pay/transactions/out-trade-no/%s/close"),

    /**
     * 申请退款
     */
    DOMESTIC_REFUNDS("/v3/refund/domestic/refunds"),

    /**
     * 查询单笔退款
     */
    DOMESTIC_REFUNDS_QUERY("/v3/refund/domestic/refunds/%s"),

    /**
     * 申请交易账单
     */
    TRADE_BILLS("/v3/bill/tradebill"),

    /**
     * 申请资金账单
     */
    FUND_FLOW_BILLS("/v3/bill/fundflowbill");


    /**
     * 类型
     */
    private final String type;
}
5、发起请求参数封装 WxPayCommon
package com.tuimi.zxdt.wxpay.config;

import com.google.gson.Gson;
import com.tuimi.zxdt.utils.StringUtils;
import com.tuimi.zxdt.wxpay.domain.WxPay;
import lombok.extern.slf4j.Slf4j;
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 java.io.IOException;
import java.util.HashMap;
import java.util.Map;

/**
 * @description:
 * @author: HK
 * @since: 2024/9/26 15:00
 */
@Slf4j
public class WxPayCommon {
    /**
     * 封装基础通用请求数据
     * @param wxPayConfig 微信的配置文件
     * @param wxPay 微信支付基础请求数据
     * @return 封装后的map对象
     */
    public static Map<String, Object> getBasePayParams(WxPayConfig wxPayConfig, WxPay wxPay) {
        Map<String, Object> paramsMap = new HashMap<>();
        paramsMap.put("appid", wxPayConfig.getAppid());
        paramsMap.put("mchid", wxPayConfig.getMchId());
        // 如果商品名称过长则截取
        String title = wxPay.getGoodsName().length() > 62 ? wxPay.getGoodsName().substring(0, 62) : wxPay.getGoodsName();
        paramsMap.put("description",title);
        paramsMap.put("out_trade_no", wxPay.getOrderNumber());
        paramsMap.put("attach", wxPay.getOrderAttach());
        paramsMap.put("notify_url", wxPayConfig.getNotifyDomain());
        Map<String, Object> amountMap = new HashMap<>();
        amountMap.put("total", wxPay.getOrderMoney());
        amountMap.put("currency", "CNY");
        paramsMap.put("amount", amountMap);
        return paramsMap;
    }

    /**
     * 获取请求对象(Post请求)
     * @param paramsMap 请求参数
     * @return Post请求对象
     */
    public static HttpPost getHttpPost(String type, Map<String, Object> paramsMap) {

        // 1.设置请求地址
        HttpPost httpPost = new HttpPost(type);

        // 2.设置请求数据
        Gson gson = new Gson();
        String jsonParams = gson.toJson(paramsMap);

        // 3.设置请求信息
        StringEntity entity = new StringEntity(jsonParams, "utf-8");
        entity.setContentType("application/json");
        httpPost.setEntity(entity);
        httpPost.setHeader("Accept", "application/json");
        return httpPost;
    }

    /**
     * 解析响应数据
     * @param response 发送请求成功后,返回的数据
     * @return 微信返回的参数
     */
    public static HashMap<String, String> resolverResponse(CloseableHttpResponse response) {
        try {
            // 1.获取请求码
            int statusCode = response.getStatusLine().getStatusCode();
            // 2.获取返回值 String 格式
            final String bodyAsString = EntityUtils.toString(response.getEntity());

            Gson gson = new Gson();
            if (statusCode == 200) {
                // 3.如果请求成功则解析成Map对象返回
                HashMap<String, String> resultMap = gson.fromJson(bodyAsString, HashMap.class);
                return resultMap;
            } else {
                if (StringUtils.isNoneBlank(bodyAsString)) {
                    log.error("微信支付请求失败,提示信息:{}", bodyAsString);
                    // 4.请求码显示失败,则尝试获取提示信息
                    HashMap<String, String> resultMap = gson.fromJson(bodyAsString, HashMap.class);
                    throw new RuntimeException(resultMap.get("message"));
                }
                log.error("微信支付请求失败,未查询到原因,提示信息:{}", response);
                // 其他异常,微信也没有返回数据,这就需要具体排查了
                throw new IOException("request failed");
            }
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException(e.getMessage());
        } finally {
            try {
                response.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }


    /**
     * 创建微信支付订单-jsapi方式
     * @param wxPayConfig 微信配置信息
     * @param openId 基础请求信息,商品标题、商家订单id、订单价格
     * @param openId 通过微信小程序或者公众号获取到用户的openId
     * @param wxPayClient 微信请求客户端()
     * @return 微信支付二维码地址
     */
    public static String wxJsApiPay(WxPayConfig wxPayConfig, WxPay wxPay, String openId, CloseableHttpClient wxPayClient) {

        // 1.获取请求参数的Map格式
        Map<String, Object> paramsMap = getBasePayParams(wxPayConfig, wxPay);

        // 1.1 添加支付者信息
        Map<String,String> payerMap = new HashMap<>();
        payerMap.put("openid",openId);
        paramsMap.put("payer",payerMap);

        // 2.获取请求对象
        HttpPost httpPost = getHttpPost("https://api.mch.weixin.qq.com"+WxApiType.JSAPI_PAY.getType(),paramsMap);

        // 3.完成签名并执行请求
        CloseableHttpResponse response = null;
        try {
            response = wxPayClient.execute(httpPost);
        } catch (IOException e) {
            e.printStackTrace();
            throw new RuntimeException("微信支付请求失败");
        }

        // 4.解析response对象
        HashMap<String, String> resultMap = resolverResponse(response);
        if (resultMap != null) {
            // native请求返回的是二维码链接,前端将链接转换成二维码即可
            return resultMap.get("prepay_id");
        }
        return null;
    }

}
6、订单编号、签名工具类 OrderUtils
package com.tuimi.zxdt.wxpay.utils;

import com.tuimi.zxdt.wxpay.domain.SignDto;
import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;

import java.io.FileInputStream;
import java.io.IOException;
import java.math.BigDecimal;
import java.security.Signature;
import java.text.SimpleDateFormat;
import java.util.Base64;
import java.util.Date;
import java.util.Random;

/**
 * @description:
 * @author: HK
 * @since: 2024/9/26 16:00
 */
public class OrderUtils {
    /**
     * 获取订单编号
     * @return
     */
    public static String getOrderNo() {
        return "ORDER_" + getNo();
    }

    /**
     * 获取退款单编号
     * @return
     */
    public static String getRefundNo() {
        return "REFUND_" + getNo();
    }

    /**
     * 获取编号
     * @return
     */
    public static String getNo() {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
        String newDate = sdf.format(new Date());
        String result = "";
        Random random = new Random();
        for (int i = 0; i < 3; i++) {
            result += random.nextInt(10);
        }
        return newDate + result;
    }

    /**
     * 金额判断
     * @param amount
     * @return
     */
    public static boolean isValidAmount(BigDecimal amount) {
        if (amount == null) {
            throw new IllegalArgumentException("金额不能为空");
        } else if (amount.compareTo(BigDecimal.ZERO) <= 0) {
            return false; // 金额小于等于0的情况视为无效
        } else {
            return true;
        }
    }

    /**
     * 获取签名
     * @param signDto:  appId微信公众号或者小程序等的
     *                  timestamp 时间戳 10位
     *                  nonceStr  随机数
     *                  prepay_id 预支付交易会话ID
     *                  privateKeyPath  商户私钥文件
     * @return String 新签名
     */
    public static String getSign(SignDto signDto) {
        //从下往上依次生成
        String message = buildMessage(signDto);
        //签名
        try {
            return sign(message.getBytes("utf-8"), signDto.getPrivateKeyPath());
        } catch (IOException e) {
            throw new RuntimeException("签名异常,请检查参数或商户私钥");
        }
    }

    private static String sign(byte[] message, String privateKeyPath) {
        try {
            //签名方式
            Signature sign = Signature.getInstance("SHA256withRSA");
            //私钥,通过MyPrivateKey来获取,这是个静态类可以接调用方法 ,需要的是_key.pem文件的绝对路径配上文件名
            sign.initSign(PemUtil.loadPrivateKey(new FileInputStream(privateKeyPath)));
            sign.update(message);
            return Base64.getEncoder().encodeToString(sign.sign());
        } catch (Exception e) {
            throw new RuntimeException("签名异常,请检查参数或商户私钥");
        }
    }

    /**
     * 按照前端签名文档规范进行排序,\n是换行
     * @param signDto:  appId微信公众号或者小程序等的
     *                  timestamp 时间戳 10位
     *                  nonceStr  随机数
     *                  prepay_id 预支付交易会话ID
     * @return String 新签名
     */
    private static String buildMessage(SignDto signDto) {
        return signDto.getAppId() + "\n"
                + signDto.getTimestamp() + "\n"
                + signDto.getNonceStr() + "\n"
                + signDto.getPrepayId() + "\n";
    }


}
7、微信支付回调工具类 WxPayCallbackUtil
package com.tuimi.zxdt.wxpay.utils;

import com.alibaba.fastjson.JSONObject;
import com.tuimi.zxdt.wxpay.config.WxPayConfig;
import com.wechat.pay.contrib.apache.httpclient.auth.Verifier;
import com.wechat.pay.contrib.apache.httpclient.util.AesUtil;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.security.GeneralSecurityException;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * @description: 微信支付回调
 * @author: HK
 * @since: 2024/9/27 14:24
 */
@Component
public class WxPayCallbackUtil {

    @Resource
    private Verifier verifier;

    @Resource
    private WxPayConfig wxPayConfig;

    /**
     * 获取回调数据
     * @param request
     * @param response
     * @return
     */
    public Map<String, String> wxChatPayCallback(HttpServletRequest request, HttpServletResponse response) {
        //获取报文
        String body = getRequestBody(request);

        //随机串
        String nonceStr = request.getHeader("Wechatpay-Nonce");

        //微信传递过来的签名
        String signature = request.getHeader("Wechatpay-Signature");

        //证书序列号(微信平台)
        String serialNo = request.getHeader("Wechatpay-Serial");

        //时间戳
        String timestamp = request.getHeader("Wechatpay-Timestamp");

        //构造签名串  应答时间戳\n,应答随机串\n,应答报文主体\n
        String signStr = Stream.of(timestamp, nonceStr, body).collect(Collectors.joining("\n", "", "\n"));

        Map<String, String> map = new HashMap<>(2);
        try {
            //验证签名是否通过
            boolean result = verifiedSign(serialNo, signStr, signature);
            if(result){
                //解密数据
                String plainBody = decryptBody(body);
                return convertWechatPayMsgToMap(plainBody);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return map;
    }

    /**
     * 读取请求数据流
     * @param request
     * @return
     */
    public String getRequestBody(HttpServletRequest request) {
        StringBuffer sb = new StringBuffer();
        try (ServletInputStream inputStream = request.getInputStream();
             BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
        ) {
            String line;
            while ((line = reader.readLine()) != null) {
                sb.append(line);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return sb.toString();

    }

    /**
     * 签名验证
     * @param serialNo      证书序列号(微信平台)
     * @param signStr       构造签名串  应答时间戳\n,应答随机串\n,应答报文主体\n
     * @param signature     微信传递过来的签名
     * @return
     * @throws UnsupportedEncodingException
     */
    public boolean verifiedSign(String serialNo, String signStr, String signature) throws UnsupportedEncodingException {
        return verifier.verify(serialNo, signStr.getBytes("utf-8"), signature);
    }

    /**
     * 解密body的密文
     * @param body
     * "resource": {
     *         "original_type": "transaction",
     *         "algorithm": "AEAD_AES_256_GCM",
     *         "ciphertext": "",
     *         "associated_data": "",
     *         "nonce": ""
     *  }
     * @return
     * @throws UnsupportedEncodingException
     * @throws GeneralSecurityException
     */
    public String decryptBody(String body) throws UnsupportedEncodingException, GeneralSecurityException {
        AesUtil aesUtil = new AesUtil(wxPayConfig.getApiV3Key().getBytes("utf-8"));
        JSONObject object = JSONObject.parseObject(body);
        JSONObject resource = object.getJSONObject("resource");
        String ciphertext = resource.getString("ciphertext");
        String associatedData = resource.getString("associated_data");
        String nonce = resource.getString("nonce");
        return aesUtil.decryptToString(associatedData.getBytes("utf-8"),nonce.getBytes("utf-8"),ciphertext);
    }

    /**
     * 解析ciphertext密文
     * @param plainBody     密文
     * @return
     */
    public Map<String,String> convertWechatPayMsgToMap(String plainBody){

        Map<String,String> paramsMap = new HashMap<>(5);

        JSONObject jsonObject = JSONObject.parseObject(plainBody);

        // 微信支付订单号
        paramsMap.put("transactionId",jsonObject.getString("transaction_id"));

        //商户订单号
        paramsMap.put("out_trade_no",jsonObject.getString("out_trade_no"));

        //交易状态
        paramsMap.put("trade_state",jsonObject.getString("trade_state"));

        // 支付完成时间
        paramsMap.put("success_time",jsonObject.getString("success_time"));

        //附加数据
        paramsMap.put("attach",jsonObject.getString("attach"));
//        if (jsonObject.getJSONObject("attach") != null && !jsonObject.getJSONObject("attach").equals("")){
//            paramsMap.put("account_no",jsonObject.getJSONObject("attach").getString("accountNo"));
//        }
        return paramsMap;

    }

}
8、实体类签名参数 SignDto
package com.tuimi.zxdt.wxpay.domain;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * @description: 签名参数
 * @author: HK
 * @since: 2024/9/26 16:46
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class SignDto {

    // 随机字符串,不长于32位。
    private String nonceStr;

    // 商户申请的公众号对应的appid,由微信支付生成,可在公众号后台查看
    private String appId;

    // JSAPI下单接口返回的prepay_id参数值
    private String prepayId;

    // 时间戳
    private long timestamp;

    // 商户私钥文件
    private String privateKeyPath;
}
9、微信支付实体 WxPay
package com.tuimi.zxdt.wxpay.domain;

import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

/**
 * @description:
 * @author: HK
 * @since: 2024/9/26 15:04
 */
@Data
public class WxPay {

    @ApiModelProperty(value = "商品名称")
    private String goodsName;

    @ApiModelProperty(value = "订单编号")
    private String orderNumber;

    @ApiModelProperty(value = "订单附加信息,逗号分割(订单类型和订单Id)")
    private String orderAttach;

    @ApiModelProperty(value = "订单金额(单位‘分’)")
    private Integer orderMoney;
}
10、返回前端参数 WxPayDto
package com.tuimi.zxdt.wxpay.domain;

import lombok.Data;

/**
 * @description: 返回前端参数
 * @author: HK
 * @since: 2024/9/26 15:40
 */
@Data
public class WxPayDto {
    /**
     * 需要支付的小程序id
     */
    private String appid;

    /**
     * 时间戳(当前的时间)
     */
    private String timeStamp;

    /**
     * 随机字符串,不长于32位。
     */
    private String nonceStr;

    /**
     * 小程序下单接口返回的prepay_id参数值,提交格式如:prepay_id=***
     */
    private String prepayId;

    /**
     * 签名类型,默认为RSA,仅支持RSA。
     */
    private String signType;

    /**
     * 签名,使用字段appId、timeStamp、nonceStr、package计算得出的签名值
     */
    private String paySign;
}
11、微信支付controller
/**
     * 资料 =》微信支付
     * @param studyMaterialOrder 参数
     * @return 信息提示
     */
    @PostMapping("studyMaterialWxPay")
    public ResponseEntity<Object> studyMaterialWxPay(@RequestBody StudyMaterialOrder studyMaterialOrder, HttpServletRequest request) {
        Integer memberId = (Integer) request.getAttribute("memberId");
        studyMaterialOrder.setMemberId(memberId);
        WxPayDto wxChatPayDto = service.studyMaterialWxPay(studyMaterialOrder);
        return new ResponseEntity<>(wxChatPayDto, HttpStatus.OK);
    }
12、微信支付service
 @Override
    @Transactional
    public WxPayDto studyMaterialWxPay(StudyMaterialOrder studyMaterialOrder) {
        // 根据用户ID查询openId
        Member member = memberService.getById(studyMaterialOrder.getMemberId());
        if (member == null || StringUtils.isEmpty(member.getOpenId())) {
            throw new BadRequestException("用户信息异常!");
        }
        // 根据资料ID查询资料名称
        StudyMaterial studyMaterial = studyMaterialService.getById(studyMaterialOrder.getMaterialId());
        if (studyMaterial == null || StringUtils.isEmpty(studyMaterial.getFileName())) {
            throw new BadRequestException("资料信息异常!");
        }

        if (!OrderUtils.isValidAmount(studyMaterial.getPrice())) {
            throw new BadRequestException("金额异常!");
        }

        // 生成订单编号
        String no = OrderUtils.getNo();
        studyMaterialOrder.setOrderNumber(no);
        // 订单金额
        studyMaterialOrder.setOrderMoney(studyMaterial.getPrice());
        // 支付状态
        studyMaterialOrder.setPayStatus("0");
        studyMaterialOrderService.save(studyMaterialOrder);

        // 调起微信支付
        WxPay wxPay = new WxPay();
        wxPay.setGoodsName(studyMaterial.getFileName());

        BigDecimal price = studyMaterial.getPrice();
        BigDecimal multiply = price.multiply(new BigDecimal(100));
        int value = multiply.intValue();
        wxPay.setOrderMoney(value);

        wxPay.setOrderNumber(no);
        wxPay.setOrderAttach("2,"+studyMaterialOrder.getId());
        String prepayId = WxPayCommon.wxJsApiPay(wxPayConfig, wxPay, member.getOpenId(), wxPayClient);

        String nonceStr = UUID.randomUUID().toString().replaceAll("-", "");
        String timeStamp = String.valueOf(System.currentTimeMillis() / 1000);
        WxPayDto wxPayDto = new WxPayDto();
        wxPayDto.setAppid(wxPayConfig.getAppid());
        wxPayDto.setTimeStamp(timeStamp);
        wxPayDto.setNonceStr(nonceStr);
        wxPayDto.setPrepayId("prepay_id=" + prepayId);
        wxPayDto.setSignType("RSA");

        // 获取签名
        SignDto signDto = new SignDto(nonceStr,wxPayConfig.getAppid(),prepayId,Long.parseLong(timeStamp), wxPayConfig.getPrivateKeyPath());
        wxPayDto.setPaySign(OrderUtils.getSign(signDto));
        return wxPayDto;
    }

发起成功后返回签名等信息给前端调起支付

WeixinJSBridge内置对象在其他浏览器中无效。

onBridgeReady(value) {
      WeixinJSBridge.invoke(
        'getBrandWCPayRequest',
        {
          'appId': value.appid, // 公众号ID,由商户传入
          'timeStamp': value.timeStamp, // 时间戳,自1970年以来的秒数
          'nonceStr': value.nonceStr, // 随机串
          'package': 'prepay_id=' + value.prepayId,
          'signType': value.signType, // 微信签名方式:
          'paySign': value.paySign // 微信签名
        },
        function(res) {
          if (res.err_msg === 'get_brand_wcpay_request:ok') {
            // 使用以上方式判断前端返回,微信团队郑重提示:
            // res.err_msg将在用户支付成功后返回ok,但并不保证它绝对可靠。
       
          }
        }
      )
    },

支付成功后回调支付结果通知

1、微信回调controller
/**
     * 微信支付【成功回调】
     * @return 信息提示
     */
    @PostMapping("payNotify")
    public ResponseEntity<Object> payNotify(HttpServletRequest request, HttpServletResponse response) {
        Map<String, String> stringMap = service.payNotify(request, response);
        return new ResponseEntity<>(stringMap, HttpStatus.OK);
    }
2、微信回调service
 @Override
    @Transactional
    public Map<String, String> payNotify(HttpServletRequest request, HttpServletResponse response) {
        Map<String, String> map = new HashMap<>(2);
        try {
            Map<String, String> stringMap = wxChatPayCallback.wxChatPayCallback(request, response);
            //支付成功
            if (stringMap.get("trade_state").equals("SUCCESS")){
                // (通知频率为15s/15s/30s/3m/10m/20m/30m/30m/30m/60m/3h/3h/3h/6h/6h - 总计 24h4m)
                // 支付成功和后的逻辑
                // 获取咱们自己生成的订单号 不是微信支付生成的
                String out_trade_no = stringMap.get("out_trade_no");
                String attach = stringMap.get("attach");
                String success_time = stringMap.get("success_time");
                if (StringUtils.isEmpty(attach)){
                    log.warn("微信携带参数失败!");
                    return map;
                }

                String[] split = attach.split(",");
                if ("2".equals(split[0])){
                    // 资料订单
                    StudyMaterialOrder studyMaterialOrder = studyMaterialOrderService.getById(split[1]);
                    if (out_trade_no.equals(studyMaterialOrder.getOrderNumber())) {
                        // 修改订单状态
                        studyMaterialOrder.setPayStatus("1");
                        Date date = simpleDateFormat.parse(success_time);
                        studyMaterialOrder.setPayTime(new Timestamp(date.getTime()));
                        studyMaterialOrderService.updateById(studyMaterialOrder);
                        // 添加学习资料关联会员表
                        StudyMaterialMember studyMaterialMember = new StudyMaterialMember();
                        studyMaterialMember.setMemberId(studyMaterialOrder.getMemberId());
                        studyMaterialMember.setMaterialId(studyMaterialOrder.getMaterialId());
                        studyMaterialMemberService.save(studyMaterialMember);
                    }
                }
            }
            //响应微信
            map.put("code", "SUCCESS");
            map.put("message", "成功");
        } catch (Exception e) {
            e.printStackTrace();
        }
        return map;
    }

其中的一些业务逻辑代码需自己替换

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值