【Java】微信小程序V3支付(后台)

这篇具有很好参考价值的文章主要介绍了【Java】微信小程序V3支付(后台)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

目录

        相关官网文档

        1.需要的参数

        2.引入库

        3.用到的工具类

        4.支付下单实现

        5.支付回调


相关官网文档

接入前准备-小程序支付 | 微信支付商户平台文档中心

微信支付-JSAPI下单

获取平台证书列表-文档中心-微信支付商户平台

微信支付-支付通知API

1.需要的参数

# appId

wechat.appid=${WECHAT_APPID}

# 商户号

wechat.mchid=${WECHAT_MCHID}

# 证书序列号

wechat.mch.certno=${WECHAT_CERTNO}

# APIv3密钥

wechat.pay.api-v3-key=${WECHAT_V3KEY}

证书下载,apiclient_cert.p12,放到resource目录下

【Java】微信小程序V3支付(后台),微信小程序,java,小程序

需要的参数和证书,根据接入前准备官方文档获取。

2.引入库

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

3.用到的工具类

import com.wechat.pay.contrib.apache.httpclient.auth.AutoUpdateCertificatesVerifier;
import com.wechat.pay.contrib.apache.httpclient.auth.PrivateKeySigner;
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Credentials;
import lombok.SneakyThrows;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.ClassPathResource;
import org.springframework.stereotype.Component;
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.nio.charset.StandardCharsets;
import java.security.*;
import java.security.cert.X509Certificate;
import java.util.Date;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * @author Grace
 */
@Component
public class KeyPairFactory {
    @Value("${wechat.mchid}")
    private String MCHID;

    @Value("${wechat.mch.certno}")
    private String CERTNO;

    @Value("${wechat.pay.api-v3-key}")
    private String V3KEY;
    
    private KeyStore store;
    private static final String SYMBOLS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
    private static final Random RANDOM = new SecureRandom();
    private final Object lock = new Object();
    
    private static final Map<String, Object> VERIFIER_MAP = new ConcurrentHashMap<>();
    
    /**
     * 获取公私钥.
     *
     * @param keyPath  API证书apiclient_cert.p12的classpath路径
     * @param keyAlias the key alias
     * @param keyPass  password
     * @return the key pair
     */
    public KeyPair createPKCS12(String keyPath, String keyAlias, String keyPass) {
        ClassPathResource resource = new ClassPathResource(keyPath);
        char[] pem = keyPass.toCharArray();
        try {
            synchronized (lock) {
                if (store == null) {
                    synchronized (lock) {
                        store = KeyStore.getInstance("PKCS12");
                        store.load(resource.getInputStream(), pem);
                    }
                }
            }
            X509Certificate certificate = (X509Certificate) store.getCertificate(keyAlias);
            certificate.checkValidity();
            // 证书的序列号
//            String serialNumber = certificate.getSerialNumber().toString(16).toUpperCase();
            // 证书的公钥
            PublicKey publicKey = certificate.getPublicKey();
            // 证书的私钥
            PrivateKey storeKey = (PrivateKey) store.getKey(keyAlias, pem);

            return new KeyPair(publicKey, storeKey);
        } catch (Exception e) {
            throw new IllegalStateException("Cannot load keys from store: " + resource, e);
        }
    }

    /**
     * V3  SHA256withRSA 签名.
     *
     * @param method       请求方法  GET  POST PUT DELETE 等
     * @param canonicalUrl 例如  https://api.mch.weixin.qq.com/v3/pay/transactions/app?version=1 ——> /v3/pay/transactions/app?version=1
     * @param timestamp    当前时间戳   因为要配置到TOKEN 中所以 签名中的要跟TOKEN 保持一致
     * @param nonceStr     随机字符串  要和TOKEN中的保持一致
     * @param body         请求体 GET 为 "" POST 为JSON
     * @param keyPair      商户API 证书解析的密钥对  实际使用的是其中的私钥
     * @return the string
     */
    @SneakyThrows
    public String requestSign(String method, String canonicalUrl, long timestamp, String nonceStr, String body, KeyPair keyPair)  {
        String signatureStr = Stream.of(method, canonicalUrl, String.valueOf(timestamp), nonceStr, body)
                .collect(Collectors.joining("\n", "", "\n"));
        Signature sign = Signature.getInstance("SHA256withRSA");
        sign.initSign(keyPair.getPrivate());
        sign.update(signatureStr.getBytes(StandardCharsets.UTF_8));
        return Base64Utils.encodeToString(sign.sign());
    }

    @SneakyThrows
    public String awakenPaySign(String appid, long timestamp, String nonceStr, String body, KeyPair keyPair)  {
        String signatureStr = Stream.of(appid,String.valueOf(timestamp),  nonceStr, body)
                .collect(Collectors.joining("\n", "", "\n"));
        Signature sign = Signature.getInstance("SHA256withRSA");
        sign.initSign(keyPair.getPrivate());
        sign.update(signatureStr.getBytes(StandardCharsets.UTF_8));
        return Base64Utils.encodeToString(sign.sign());
    }

    /**
     * 生成Token.
     *
     * @param mchId 商户号
     * @param nonceStr   随机字符串 
     * @param timestamp  时间戳
     * @param serialNo   证书序列号
     * @param signature  签名
     * @return the string
     */
    public String token(String mchId, String nonceStr, long timestamp, String serialNo, String signature) {
        final String TOKEN_PATTERN = "WECHATPAY2-SHA256-RSA2048 mchid=\"%s\",nonce_str=\"%s\","
                + "timestamp=\"%d\",serial_no=\"%s\",signature=\"%s\"";
        // 生成token
        return String.format(TOKEN_PATTERN,
                mchId,
                nonceStr, timestamp, serialNo, signature);
    }

    public String generateNonceStr() {
        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);
    }

    /**
     * 解密响应体.
     *
     * @param apiV3Key       API V3 KEY  API v3密钥 商户平台设置的32位字符串
     * @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 String decryptResponseBody(String apiV3Key, String associatedData, String nonce, String ciphertext) {
        try {
            Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
            SecretKeySpec key = new SecretKeySpec(apiV3Key.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);
        }
    }

    /**
     * 构造验签名串.
     *
     * @param wechatpayTimestamp HTTP头 Wechatpay-Timestamp 中的应答时间戳。
     * @param wechatpayNonce     HTTP头 Wechatpay-Nonce 中的应答随机串
     * @param body               响应体
     * @return the string
     */
    public String responseSign(String wechatpayTimestamp, String wechatpayNonce, String body) {
        return Stream.of(wechatpayTimestamp, wechatpayNonce, body)
                .collect(Collectors.joining("\n", "", "\n"));
    }

    public AutoUpdateCertificatesVerifier getVerifier() {
        AutoUpdateCertificatesVerifier verifier = (AutoUpdateCertificatesVerifier) VERIFIER_MAP.get("verifier");
        Date notAfter = (Date) VERIFIER_MAP.get("notAfter");
        if(null == verifier || null == notAfter || notAfter.before(new Date())){
            // 加载证书
            KeyPair keyPair = createPKCS12("/apiclient_cert.p12",
                    "Tenpay Certificate", MCHID);
            // 加载官方自动更新证书
            verifier = new AutoUpdateCertificatesVerifier(new WechatPay2Credentials(MCHID,
                    new PrivateKeySigner(CERTNO, keyPair.getPrivate())),
                    V3KEY.getBytes(StandardCharsets.UTF_8));
            VERIFIER_MAP.put("verifier", verifier);
            VERIFIER_MAP.put("notAfter", verifier.getValidCertificate().getNotAfter());
        }
        return verifier;
    }
    
}

官方证书验证工具放到了全局变量中,

4.支付下单实现

    @ApiOperation(value = "支付", httpMethod = "POST")
    @RequestMapping(value = "/api/pay/order", method = RequestMethod.POST)
    @Transactional
    public PrePayResponse wxToPay(@Valid @RequestBody PayOrderRequest dto, HttpServletRequest request, HttpServletResponse response) throws Exception {
        PrePayResponse res = new PrePayResponse();
        // 用户登录校验
        ......
        // 获取用户(数据库中的实体类)
        WechatUser user = xxx.getUser();

        int fee = 0;
        // 得到小程序传过来的价格,注意这里的价格必须为整数,1代表1分,所以传过来的值必须*100;
        if (null != dto.getPrice()) {
            double price = dto.getPrice() * 100;
            fee = (int) price;

        }
        // 生成订单号,商户系统内部订单号,只能是数字、大小写字母_-*且在同一个商户号下唯一
        String OutTradeNo = DateUtil.getOrderNum() + String.valueOf(System.currentTimeMillis()).substring(4) + new Random().nextInt(999999999);

        // 保存订单信息
        PayOrder order = new PayOrder();
        order.setUser(user);
        order.setCreatedAt(new Date());
        order.setPrice(dto.getPrice());
        order.setTradeNo(OutTradeNo);
        ......
        payOrderRepository.save(order);

        // 异步接收微信支付结果通知的回调地址,通知url必须为外网可访问的url,不能携带参数。 公网域名必须为https,如果是走专线接入,使用专线NAT IP或者私有回调域名可使用http
        String basePath  = request.getScheme() + "://" + request.getServerName() + ":" +
                request.getServerPort()  + request.getContextPath();
        String notify_url = basePath+"/api/anon/pay/notify";

        //请求参数
        cn.hutool.json.JSONObject json = new cn.hutool.json.JSONObject();
        json.set("appid", APPID);
        json.set("mchid", MCHID); // 商户号
        json.set("description", "test"); // 商品描述,必填
        json.set("out_trade_no", OutTradeNo);
        json.set("attach", "自定义参数"); // 附加数据,在查询API和支付通知中原样返回,可作为自定义参数使用
        json.set("notify_url", notify_url); // 回调地址
        cn.hutool.json.JSONObject amount = new cn.hutool.json.JSONObject();
        amount.set("total", fee);
        amount.set("currency", "CNY");
        json.set("amount",amount); // 订单金额
        cn.hutool.json.JSONObject payer = new cn.hutool.json.JSONObject();
        payer.set("openid", user.getOpenId()); // 用户openId
        json.set("payer",payer); // 支付者

        //获取token
        String nonceStr = keyPairFactory.generateNonceStr();
        long timestamp = System.currentTimeMillis()/1000;
        res.setNonceStr(nonceStr);
        res.setTimeStamp(timestamp);
        String wechatToken = this.getToken(JSON.toJSONString(json), "POST",  "/v3/pay/transactions/jsapi", nonceStr, timestamp);

        Map<String,String> headers = new HashMap<String,String>();
        headers.put("Accept","application/json");
        headers.put("Content-Type","application/json; charset=utf-8");
        headers.put("Authorization",wechatToken);

        HttpResponse httpResponse = HttpRequest.post("https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi")
                .headerMap(headers, false)
                .body(String.valueOf(json))
                .timeout(5 * 60 * 1000)
                .execute();
        String resultBody = httpResponse.body();

        com.alibaba.fastjson.JSONObject jsonObject = com.alibaba.fastjson.JSONObject.parseObject(resultBody);
        String prepayId = jsonObject.getString("prepay_id");
        if(StringUtils.isEmpty(prepayId)){
            order.setStatus(-1);
            res.setCode(Constants.SC_MSG);
            res.setMessage("支付失败");
            return res;
        }
        res.setPrepayId(prepayId);
        res.setSignType("RSA");
        // 加载证书
        KeyPair keyPair = keyPairFactory.createPKCS12("/apiclient_cert.p12",
                "Tenpay Certificate", MCHID);
        String paySign = keyPairFactory.awakenPaySign(APPID, timestamp, nonceStr, "prepay_id="+prepayId, keyPair);
        res.setPaySign(paySign);
        res.setCode(Constants.SC_OK);
        return res;
    }


    private String getToken(String body,String method,String url, String nonceStr, Long timestamp) {
	    //1.加载证书
	    KeyPair keyPair = keyPairFactory.createPKCS12("/apiclient_cert.p12", "Tenpay Certificate", MCHID);
	    //2.获取签名
	    String sign = keyPairFactory.requestSign(method, url, timestamp, nonceStr, body,keyPair);
	    //3.封装token
	    String token = keyPairFactory.token(MCHID, nonceStr, timestamp, CERTNO, sign);
	    return token;
    }

5.支付回调

    @ApiOperation(value = "支付回调地址")
    @RequestMapping(value = "/api/anon/pay/notify", produces = MediaType.APPLICATION_JSON_VALUE)
    @ResponseBody
    @Transactional
    public ResultResponse notify(HttpServletRequest request, HttpServletResponse response) throws Exception {
        ResultResponse res = new ResultResponse();

        // 从请求头获取验签字段
        String Timestamp = request.getHeader("Wechatpay-Timestamp");
        String Nonce = request.getHeader("Wechatpay-Nonce");
        String Signature = request.getHeader("Wechatpay-Signature");
        String Serial = request.getHeader("Wechatpay-Serial");

        // 获取官方验签工具
        AutoUpdateCertificatesVerifier verifier = keyPairFactory.getVerifier();

        // 读取请求体的信息
        ServletInputStream inputStream = request.getInputStream();
        StringBuffer stringBuffer = new StringBuffer();
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
        String s;
        // 读取回调请求体
        while ((s = bufferedReader.readLine()) != null) {
            stringBuffer.append(s);
        }
        String s1 = stringBuffer.toString();
        Map requestMap = (Map) JSON.parse(s1);
        String resource = String.valueOf(requestMap.get("resource"));
        Map requestMap2 = (Map) JSON.parse(resource);
        String associated_data = requestMap2.get("associated_data").toString();
        String nonce = requestMap2.get("nonce").toString();
        String ciphertext = requestMap2.get("ciphertext").toString();

        //按照文档要求拼接验签串
        String VerifySignature = Timestamp + "\n" + Nonce + "\n" + s1 + "\n";
//        System.out.println("拼接后的验签串=" + VerifySignature);

        //使用官方验签工具进行验签
        boolean verify = verifier.verify(Serial, VerifySignature.getBytes(), Signature);

        //判断验签的结果
//        System.out.println("验签结果:"+verify);

        // 验签成功
//        System.out.println("验签成功后,开始进行解密");
        com.wechat.pay.contrib.apache.httpclient.util.AesUtil aesUtil = new AesUtil(V3KEY.getBytes());
        String aes = aesUtil.decryptToString(associated_data.getBytes(), nonce.getBytes(), ciphertext);

        com.alibaba.fastjson.JSONObject jsonObject = com.alibaba.fastjson.JSONObject.parseObject(aes);
        if ("SUCCESS".equals(jsonObject.getString("trade_state"))) {
            String out_trade_no = jsonObject.getString("out_trade_no");
            // 订单不为空
            if (!StringUtils.isEmpty(out_trade_no)) {
                //支付成功后的业务处理
                PayOrder payOrder = payOrderRepository.findByTradeNo(out_trade_no);
                payOrder.setStatus(1);
                ......
                res.setCode(Constants.SC_OK);
                return res;
            }
        } else {
            // 支付失败后的操作
        }
        res.setCode(Constants.SC_OK);
        return res;
    }

注意:回调接口不要被拦截文章来源地址https://www.toymoban.com/news/detail-521399.html

到了这里,关于【Java】微信小程序V3支付(后台)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • SpringBoot整合微信小程序支付V3(支付、退款)

    微信支付开发前,需要先获取商家信息,包括商户号、AppId、证书和密钥。 获取商户号 微信商户平台 申请成为商户 = 提交资料 = 签署协议 = 获取商户号 获取AppID 微信公众平台 注册服务号 = 服务号认证 = 获取APPID = 绑定商户号 申请商户证书 登录商户平台 = 选择 账户中心 = 安

    2024年02月08日
    浏览(44)
  • 微信小程序支付V3版本接口实现

    特别说明:遇到 java.security.InvalidKeyException: Illegal key size ******* getValidator的错误 参考添加链接描述 JDK7的下载地址 JDK8的下载地址: 下载后解压,可以看到local_policy.jar和US_export_policy.jar以及readme.txt 如果安装了JRE,将两个jar文件放到%JRE_HOME%libsecurity目录下覆盖原来的文件 如果安

    2024年02月09日
    浏览(51)
  • SpringBoot 整合微信小程序微信支付V3 jsapi (支付、退款)

    最近的一个微信小程序项目里有用到微信支付,网上找的资料都是特别乱,看起来特别懵,结合了好多文章的内容,终于做了出来,可能我的这个博文看起来也是特别乱,但是是可以直接C走简单改一改就可以用的。(支付成功回调,和退款回调因为昨天刚在阿里申请的域名还

    2024年04月25日
    浏览(50)
  • springboot实现微信小程序V3微信支付功能

    appId:小程序appid appSecret:小程序的secret mchId:商户号 keyPath:商户私钥路径(apiclient_key.pem) certPath:证书路径(apiclient_cert.pem) platFormPath:平台证书(cert.pem) 注 : 需要通过写程序生成平台证书(见v3Get()方法) apiKey3:apiv3密钥 serialnumber:商户证书序列号 notifyUrl:回调地

    2024年02月12日
    浏览(61)
  • springboot整合IJPay实现微信支付-V3---微信小程序

    微信支付适用于许多场合,如小程序、网页支付、但微信支付相对于其他支付方式略显麻烦,我们使用IJpay框架进行整合 JPay 让支付触手可及, 封装了微信支付、支付宝支付、银联支付常用的支付方式以及各种常用的接口。不依赖任何第三方 mvc 框架,仅仅作为工具使用简单

    2024年02月02日
    浏览(70)
  • java微信支付v3系列——5.微信支付成功回调

    java微信支付v3系列——1.微信支付准备工作 java微信支付v3系列——2.微信支付基本配置 java微信支付v3系列——3.订单创建准备操作 java微信支付v3系列——4.创建订单的封装及使用 java微信支付v3系列——5.微信支付成功回调 java微信支付v3系列——6.微信支付查询订单API java微信支

    2024年01月17日
    浏览(44)
  • Java实现微信支付v3的支付回调

    以前都是自己手搓api的, 现在有轮子了, 尝试记录一下如何使用 我的做法是首先打开v3的代码仓库, 直接进去看看他们的文档, 可以看到这么一坨东西 开发前准备 2. 先引入maven 初始化商户配置 先从请求头中获取构建RequestParam需要的参数 初始化解析器 进行验签, 解密并转换成

    2024年02月12日
    浏览(52)
  • 微信小程序支付-java对接微信

     一共是两个方法: 一个方法后台生成预支付订单,得到预支付交易会话标识prepay_id,传给前端,让前端调起小程序支付; 一个是支付回调 目录 一、生成预支付订单  注意: 二、 支付回调         封装参数向微信发送生成预支付交易单请求,微信会返回一个prepay_id,再将

    2024年02月12日
    浏览(49)
  • java微信支付v3系列——6.微信支付查询订单API

    java微信支付v3系列——1.微信支付准备工作 java微信支付v3系列——2.微信支付基本配置 java微信支付v3系列——3.订单创建准备操作 java微信支付v3系列——4.创建订单的封装及使用 java微信支付v3系列——5.微信支付成功回调 java微信支付v3系列——6.微信支付查询订单API java微信支

    2023年04月08日
    浏览(41)
  • 【微信小程序】Java实现微信支付(小程序支付JSAPI-V3)java-sdk工具包

          对于一个没有写过支付的小白,打开微信支付官方文档时彻底懵逼 ,因为 微信支付文档太过详细, 导致我无从下手,所以写此文章,帮助第一次写支付的小伙伴梳理一下。 一、流程分为三个接口:(这是前言,先看一遍,保持印象,方便理解代码) 1、第一个接口:

    2024年02月03日
    浏览(58)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包