Java实现微信支付(微信公众号JSAPI支付)

Java实现微信支付(微信公众号JSAPI支付)

第一步 开发环境准备

在接入微信支付之前,需要现在微信支付商户平台入驻,成为商家,才能继续后续的开发。

微信支付商户平台网址:https://pay.weixin.qq.com

不过,个人用户是无法成为商家的,只有以下几种用户类型才可以成为商家。
在这里插入图片描述
成为商家之后,需要完成证书申请、秘钥配置、产品申请等操作,具体如下。

1.1 申请证书与秘钥的配置

进入微信支付商户平台,在账号中心→API安全页面,完成API证书的申请,和API秘钥的配置,如下图所示。
在这里插入图片描述
API证书申请的过程稍微复杂,但是官方有详细的申请教程,点击右侧的查看指引按钮,根据教程一步一步来操作即可。
两种API秘钥则是自定义的32个字符的字符串,任意填写并记住即可。

1.2 申请产品

进入微信支付商户平台,在产品中→我的产品页面,可以查看当前商户已开通未开通的产品,根据项目需求,字形申请开通即可。
在这里插入图片描述

1.3 开发配置信息填写

进入微信支付商户平台,在产品中心→开发配置页面,记下本商户的商户号,并填写已申请支付产品的各项授权目录回调链接等信息。
在这里插入图片描述

1.4 APPID账号管理

进入微信支付商户平台,在产品中心→APPID账号管理页面,关联诸如服务号、订阅号、小程序、企业微信、移动应用、网站应用等的APPID,如下图所示。
在这里插入图片描述
至此,微信支付商户的基本信息配置完毕,总结下来,以下五项信息时必须的:

  • 商户号
  • AppID
  • API证书(3个文件)
  • APIv2秘钥
  • APIv3秘钥
  • 回调链接

第二步 项目创建与依赖导入

2.1 创建SpringBoot项目

创建SpringBoot项目的教程太多太多了…比如:https://cxhit.blog.csdn.net/article/details/113782979,所以这里不再赘述。
项目结构如下图所示。
在这里插入图片描述

2.2 导入依赖

在pom.xml文件中,导入微信支付的第三方SDK依赖:

<!-- 微信支付的核心依赖 -->
<!-- https://search.maven.org/artifact/com.github.binarywang/weixin-java-pay -->
<!-- https://github.com/Wechat-Group/WxJava -->
<dependency>
   <groupId>com.github.binarywang</groupId>
   <artifactId>weixin-java-pay</artifactId>
   <version>4.3.0</version>
</dependency>

其中最新版本可前往Maven官方仓库查看。

代码部分

1、 获取openid

第一步:用户同意授权,获取code
前台请求自己写的接口,然后在后台此接口中调用微信授权接口,调用此接口需要有三个注意的地方
1. APPID:公众号的appid
2. REDIRECT_URI:这个地址是调用微信授权后的回调接口,在回调接口中我们可以拿到用户的信息,特别注意:redirect_uri地址需要使用 urlEncode 对链接进行处理
3. SCOPE:此处填snsapi_userinfo,需要用户点授权

/**
 * 微信授权接口
 * @param response
 * @throws IOException
 */
@RequestMapping("wxLogin")
public void wxLogin(HttpServletResponse response) throws IOException {
	//域名
    String sym = "www.xxx.com";
    //这里是回调的url
    String redirectUri = URLEncoder.encode(sym+"/weChat/callBack","UTF-8");
    String url = "https://open.weixin.qq.com/connect/oauth2/authorize?" +
            "appid=wx63d9c9b609ed6579" +
            "&redirect_uri=" + redirectUri +
            "&response_type=code" +
            "&scope=snsapi_userinfo" +
            "&state=STATE#wechat_redirect";
    response.sendRedirect(url.replace("APPID","wx63d9c9b609ed6579").replace("REDIRECT_URI",redirectUri).replace("SCOPE","snsapi_base"));
}

接下来就是微信授权的回调接口
1、获取code
2、根据code获取openid

     /**
     * 微信授权回调接口
     * @param request
     * @return
     */
    @RequestMapping("callBack")
    public String callBack(HttpServletRequest request){
        //获取回调地址中的code
        String code = request.getParameter("code");
        String appId = "xxxxxxxxxxx";
        String secret = "xxxxxxxxxxxxxxxxxxxxxxxx";
        String url = "https://api.weixin.qq.com/sns/oauth2/access_token?" +
                "appid=" + appId +
                "&secret=" + secret +
                "&code=" + code +
                "&grant_type=authorization_code";
        //获取openId
        String data = HttpUtil.doGet(url);
        AccessToken accessToken = JSONObject.parseObject(data, AccessToken.class);
        return accessToken.getOpenid();
    }

补上刚刚用的实体类AccessToken

@Data
public class AccessToken {
    private String accessToken;
    private String expiresIn;
    private String refreshToken;
    private String openid;
    private String scope;
}

2、创建配置实体类

import lombok.Data;

import java.io.Serializable;

/**
 * 微信支付配置信息
 * @author Administrator
 */
@Data
public class WeChatPayEntity implements Serializable {
    private static final long serialVersionUID = 1L;

    /**
     * 必填:微信支付商户号
     */
    private String mchId;

    /**
     * 必填:商户绑定的微信公众号、小程序、开放平台等的appid
     */
    private String appId;

    /**
     * 必填:APIv2密钥(调用v2版本的API时,需用APIv2密钥生成签名)
     */
    private String mchKey;

    /**
     * 必填:APIv3密钥(调用APIv3的下载平台证书接口、处理回调通知中报文时,要通过该密钥来解密信息)
     */
    private String apiV3Key;

    /**
     * 必填:apiclient_cert.p12证书文件的绝对路径,或者以classpath:开头的类路径。
     */
    private String keyPath;

    /**
     * 必填:apiclient_key.pem证书文件的绝对路径,或者以classpath:开头的类路径。
     */
    private String privateKeyPath;

    /**
     * 必填:apiclient_cert.pem证书文件的绝对路径,或者以classpath:开头的类路径。
     */
    private String privateCertPath;

    /**
     * 必填:微信支付异步回调通知地址。通知url必须以https开头(SSL协议),外网可访问,不能携带参数。
     */
    private String notifyUrl;
}

注意:为了减少代码篇幅,此代码引入了lombok。如果没有使用lombok,请自行生成Get和Set方法。

3、实现支付服务类

以下代码实现了基本的配置和支付、退款。查询功能,有详细的注解。
另外注意:以下代码中使用了Hutool组件的Id生成工具(用于生成订单号),请在pom文件中自行添加hutool的依赖

import com.cxhit.pay.wechat.entity.WeChatPayEntity;
import com.github.binarywang.wxpay.bean.request.WxPayRefundV3Request;
import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderV3Request;
import com.github.binarywang.wxpay.bean.result.WxPayOrderQueryV3Result;
import com.github.binarywang.wxpay.bean.result.WxPayRefundV3Result;
import com.github.binarywang.wxpay.bean.result.enums.TradeTypeEnum;
import com.github.binarywang.wxpay.config.WxPayConfig;
import com.github.binarywang.wxpay.exception.WxPayException;
import com.github.binarywang.wxpay.service.WxPayService;
import com.github.binarywang.wxpay.service.impl.WxPayServiceImpl;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;


/**
 * 微信支付服务类(只实现V3接口)
 *
 * @author
 * @since
 */
@Service
public class WeChatPayService {

    // 是否启用沙箱环境。微信支付的沙箱环境贼垃圾...好多年不维护...千万不要用。。。
    private static final Boolean SAND_BOX_ENV = false;
    // 支付结果回调地址
    private static final String NOTIFY_URL = "https://open.zxdmy.com/api/wx/pay/notify";


    /**
     * 获取微信支付相关接口服务(后续的几个服务方法,实现了基本的实例)
     * (此接口也可以直接在controller中使用)
     *
     * @return 微信支付服务接口
     */
    public WxPayService getWxPayService() {
        // TODO 此处可以从数据库读取微信支付的相关秘钥、证书等配置信息。但我们这里就直接写入静态数据进行演示
        WeChatPayEntity weChatPayEntity = new WeChatPayEntity();
        // 1. 填充基本信息(商户号与APPID)
        weChatPayEntity.setMchId("15333333333");
            weChatPayEntity.setAppId("wx123456789101112");
        // 2. 填充秘钥信息
        weChatPayEntity.setMchKey("abcdefghabcdefghabcdefghabcdefgh");
        weChatPayEntity.setApiV3Key("abcdefghabcdefghabcdefghabcdefgh");
        // 3. 填充证书路径信息
        weChatPayEntity.setKeyPath("E:\\微信支付\\Cert\\apiclient_cert.p12");
        weChatPayEntity.setPrivateKeyPath("E:\\微信支付\\Cert\\apiclient_key.pem");
        weChatPayEntity.setPrivateCertPath("E:\\微信支付\\Cert\\apiclient_cert.pem");
        // 4. 填充回调URL
        weChatPayEntity.setNotifyUrl(NOTIFY_URL);

        // 以下代码无需修改
        // 生成配置
        WxPayConfig payConfig = new WxPayConfig();
        // 填充基本配置信息
        payConfig.setAppId(StringUtils.trimToNull(weChatPayEntity.getAppId()));
        payConfig.setMchId(StringUtils.trimToNull(weChatPayEntity.getMchId()));
        payConfig.setMchKey(StringUtils.trimToNull(weChatPayEntity.getMchKey()));
        payConfig.setApiV3Key(StringUtils.trimToNull(weChatPayEntity.getApiV3Key()));
        payConfig.setKeyPath(StringUtils.trimToNull(weChatPayEntity.getKeyPath()));
        payConfig.setPrivateCertPath(StringUtils.trimToNull(weChatPayEntity.getPrivateCertPath()));
        payConfig.setPrivateKeyPath(StringUtils.trimToNull(weChatPayEntity.getPrivateKeyPath()));
        payConfig.setNotifyUrl(StringUtils.trimToNull(weChatPayEntity.getNotifyUrl()));
        // 创建配置服务
        WxPayService wxPayService = new WxPayServiceImpl();
        wxPayService.setConfig(payConfig);
        // 可以指定是否使用沙箱环境
        payConfig.setUseSandboxEnv(SAND_BOX_ENV);
        if (SAND_BOX_ENV) {
            try {
                payConfig.setMchKey(wxPayService.getSandboxSignKey());
                wxPayService.setConfig(payConfig);
            } catch (WxPayException e) {
                throw new RuntimeException(e.getMessage());
            }
        }
        // 返回结果
        return wxPayService;
    }

    /**
     * 下单接口(只设置了必填信息)(V3版本)
     *
     * @param tradeType   必填:交易类型:jsapi(含小程序)、app、h5、native
     * @param description 必填:商品描述(商品标题)
     * @param outTradeNo  必填:商家订单号
     * @param total       必填:商品金额(单位:分)
     * @param openId      特殊必填:支付用户的OpenId,JSAPI支付时必填。
     * @return 支付返回结果:{0:Y|N,1:支付结果} <br>
     * 关于支付结果: <br>
     * APP支付、JSAPI支付为[预支付交易会话标识] <br>
     * Native支付为[二维码链接] <br>
     * H5支付为[支付跳转链接]
     */
    public String[] pay(String tradeType, String description, String outTradeNo, Integer total, String openId) {
        // 构建统一下单请求参数对象
        WxPayUnifiedOrderV3Request wxPayUnifiedOrderV3Request = new WxPayUnifiedOrderV3Request();
        // 对象中写入数据
        wxPayUnifiedOrderV3Request
                // 【1】必填信息
                // 商品描述:必填
                .setDescription(description)
                // 商户订单号:必填,同一个商户号下唯一
                .setOutTradeNo(outTradeNo)
                // 通知地址:必填,公网域名必须为https,外网可访问。可不填,通过配置信息读取(但这个组件没写...)
                .setNotifyUrl(NOTIFY_URL)
                // 订单金额:单位(分)
                .setAmount(new WxPayUnifiedOrderV3Request.Amount().setTotal(total))
                // 【2】选填信息
                // 附加信息
                .setAttach("附加信息")
                // 订单优惠标记
                // ...
                .setGoodsTag("ABCD");

        try {
            // 根据请求类型,返回指定类型,其中包含:【3】条件必填信息
            switch (tradeType.toLowerCase()) {
                // Native支付
                case "native":
                    return new String[]{
                            "Y", this.getWxPayService().unifiedOrderV3(TradeTypeEnum.NATIVE, wxPayUnifiedOrderV3Request).getCodeUrl()
                    };
                // JSAPI支付
                case "jsapi":
                    // 用户在直连商户appid下的唯一标识。 下单前需获取到用户的Openid
                    wxPayUnifiedOrderV3Request.setPayer(new WxPayUnifiedOrderV3Request.Payer().setOpenid(openId));
                    return new String[]{
                            "Y", this.getWxPayService().unifiedOrderV3(TradeTypeEnum.JSAPI, wxPayUnifiedOrderV3Request).getPrepayId()
                    };
                // H5支付
                case "h5":
                    wxPayUnifiedOrderV3Request.setSceneInfo(
                            new WxPayUnifiedOrderV3Request.SceneInfo()
                                    // 用户终端IP
                                    .setPayerClientIp("12.34.56.78")
                                    .setH5Info(
                                            new WxPayUnifiedOrderV3Request.H5Info()
                                                    // 场景类型
                                                    .setType("wechat")
                                    )
                    );
                    return new String[]{
                            "Y", this.getWxPayService().unifiedOrderV3(TradeTypeEnum.H5, wxPayUnifiedOrderV3Request).getH5Url()
                    };
                // APP支付
                case "app":
                    return new String[]{
                            "Y", this.getWxPayService().unifiedOrderV3(TradeTypeEnum.APP, wxPayUnifiedOrderV3Request).getPrepayId()
                    };
                default:
                    // throw new RuntimeException("输入的[" + tradeType + "]不合法,只能为native、jsapi、h5、app其一,请核实!");
                    return new String[]{
                            "N", "输入的[" + tradeType + "]不合法,只能为native、jsapi、h5、app其一,请核实!"
                    };
            }
        } catch (WxPayException e) {
            // throw new RuntimeException(e.getMessage());
            return new String[]{
                    "N", e.getMessage()
            };
        }
    }

    /**
     * 订单查询接口(新版V3)
     *
     * @param transactionId 微信订单号
     * @param outTradeNo    商户系统内部的订单号,当没提供微信订单号(transactionId)时需要传
     * @return 订单成功(SUCCESS):{0:Y,1:商户单号,2:微信单号,3:订单金额(分),4:交易时间,5:交易状态,6:交易描述}
     * 订单异常:{0:N,1:订单状态,2:订单描述}
     * 查询错误:{0:E,1:错误代码,2:错误描述}
     */
    public String[] query(String transactionId, String outTradeNo) {
        // 商家单号和微信单号不能同时为空
        if (null == transactionId && null == outTradeNo) {
            return new String[]{
                    "E",
                    "ERROR",
                    "微信单号和商户单号不能同时为空,请检查!"
            };
        }
        try {
            // 执行查询并返回查询结果
            WxPayOrderQueryV3Result wxPayOrderQueryV3Result = this.getWxPayService().queryOrderV3(transactionId, outTradeNo);
            // 如果交易成功,或者在退款中
            if ("SUCCESS".equals(wxPayOrderQueryV3Result.getTradeState()) || "REFUND".equals(wxPayOrderQueryV3Result.getTradeState())) {
                return new String[]{
                        "Y",
                        wxPayOrderQueryV3Result.getOutTradeNo(),
                        wxPayOrderQueryV3Result.getTransactionId(),
                        String.valueOf(wxPayOrderQueryV3Result.getAmount().getTotal()),
                        wxPayOrderQueryV3Result.getSuccessTime(),
                        wxPayOrderQueryV3Result.getTradeState(),
                        wxPayOrderQueryV3Result.getTradeStateDesc()
                };
            } else {
                return new String[]{
                        "N",
                        wxPayOrderQueryV3Result.getTradeState(),
                        wxPayOrderQueryV3Result.getTradeStateDesc()
                };
            }
        } catch (WxPayException e) {
            // throw new RuntimeException(e.getMessage());
            return new String[]{
                    "E",
                    e.getErrCode(),
                    e.getErrCodeDes()
            };
        }
    }


    /**
     * 退款接口(新版V3)
     *
     * @param outTradeNo  商户订单号
     * @param outRefundNo 商户退款单号
     * @param total       订单总金额(单位:分)
     * @param refund      退款金额(单位:分)
     * @return 退款成功或退款处理中:{0:Y,1:商户单号,2:微信单号,3:退款单号,4:订单金额(分),5:退款金额(分),6:退款时间}<br>
     * 订单异常:{0:N,1:订单状态,2:订单描述}
     * 退款错误:{0:E,1:错误代码,2:错误描述}
     */
    public String[] refund(String outTradeNo, String outRefundNo, Integer total, Integer refund) {
        // 几个参数不能为空
        if (null == outTradeNo || null == outRefundNo || null == total || null == refund) {
            return new String[]{
                    "E",
                    "ERROR",
                    "商户单号、退款单号、订单金额、退款金额均不能为空,请检查!"
            };
        }
        // 构造请求参数
        WxPayRefundV3Request wxPayRefundV3Request = new WxPayRefundV3Request();
        wxPayRefundV3Request
                .setOutTradeNo(outTradeNo)
                .setOutRefundNo(outRefundNo)
                .setAmount(new WxPayRefundV3Request.Amount()
                        .setTotal(total)
                        .setRefund(refund)
                        .setCurrency("CNY")
                );
        try {
            // 执行请求并返回信息
            WxPayRefundV3Result wxPayRefundV3Result = this.getWxPayService().refundV3(wxPayRefundV3Request);
            // 退款处理中 || 退款成功
            if ("PROCESSING".equals(wxPayRefundV3Result.getStatus()) || "SUCCESS".equals(wxPayRefundV3Result.getStatus())) {
                return new String[]{
                        "Y",
                        wxPayRefundV3Result.getOutTradeNo(),
                        wxPayRefundV3Result.getTransactionId(),
                        wxPayRefundV3Result.getOutRefundNo(),
                        String.valueOf(wxPayRefundV3Result.getAmount().getTotal()),
                        String.valueOf(wxPayRefundV3Result.getAmount().getRefund()),
                        wxPayRefundV3Result.getCreateTime()
                };
            } else {
                return new String[]{
                        "N",
                        wxPayRefundV3Result.getStatus(),
                        "退款失败"
                };
            }

        } catch (WxPayException e) {
            // throw new RuntimeException(e.getMessage());
            return new String[]{
                    "E",
                    e.getErrCode(),
                    e.getErrCodeDes()
            };
        }
    }
}
3.1 服务类的补充说明

一般来说,我们将配置信息放在yaml文件中。这种操作是没有问题的。
但本文中的支付服务类的实现方案,可以实现将微信支付的配置信息,经过加密后,存储在数据库中。
当需要发起支付的时候,从数据库中读取信息后,经过解密,再写入到微信的支付配置类中。
对于本演示项目,其配置信息就直接写在代码里了,如下图所示。
在这里插入图片描述

4、实现控制类

代码中有详细注释,不过多解释

import cn.hutool.core.lang.Dict;
import cn.hutool.core.util.IdUtil;
import com.cxhit.pay.wechat.service.WeChatPayService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

/**
 * 微信支付控制类
 *
 * @author
 * @since
 */
@RestController
@RequestMapping("weChat")
public class WeChatPayController {

    @Autowired
    private WeChatPayService weChatPayService;

    /**
     * 微信支付接口
     *
     * @param title 商品名称
     * @param price 商品价格
     * @return 返回结果
     */
    @PostMapping(value = "/pay")
    @ResponseBody
    public Dict pay(String title, String price,String openId) {
        // 生成商家单号
        String outTradeNo = IdUtil.simpleUUID();
        // 支付宝价格转换成浮点数后,乘100,再取整,得以分为单位的价格
        Integer total = (int) (Float.parseFloat(price) * 100);
        // 发起支付请求
        String[] result = weChatPayService.pay("jsapi", title, outTradeNo, total, openId);
        // 返回结果:下单成功
        if ("Y".equals(result[0])) {
            return Dict.create().set("code", 200).set("qrcode", result[1]).set("outTradeNo", outTradeNo);
        }
        // 下单失败
        else {
            return Dict.create().set("code", 500).set("msg", result[1]);
        }
    }

    /**
     * 退款接口
     *
     * @param outTradeNo 商家单号
     * @param amount     退款金额(不能大于总金额)
     * @return 退款结果
     */
    @PostMapping(value = "/refund")
    @ResponseBody
    public Dict refund(String outTradeNo, String amount) {
        // 生成商家退款单号
        String outRefundNo = IdUtil.simpleUUID();
        // 查询订单金额
        String[] query = weChatPayService.query(null, outTradeNo);
        // 查询成功
        if (query[0].equals("Y")) {
            int total = Integer.parseInt(query[3]);
            // 支付宝价格转换成浮点数后,乘100,再取整,得以分为单位的价格
            int refund = (int) (Float.parseFloat(amount) * 100);
            if (refund > total) {
                return Dict.create().set("code", 500).set("msg", "退款错误:退款金额不能大于支付金额!");
            }
            // 发起退款
            String[] result = weChatPayService.refund(outTradeNo, outRefundNo, total, refund);
            // 退款成功
            if (result[0].equals("Y")) {
                return Dict.create().set("code", 200).set("msg", "退款进行中,稍后到账!" +
                        " <br>商户单号:" + result[1] +
                        " <br>退款单号:" + result[3] +
                        " <br>订单金额:" + result[4] + "分" +
                        " <br>退款金额:" + result[5] + "分" +
                        " <br>退款时间:" + result[6]
                );
            }
            // 退款失败
            else if (result[0].equals("N")) {
                return Dict.create().set("code", 500).set("msg", "退款失败:" + result[1] + result[2]);
            }
            // 退款发生错误
            else {
                return Dict.create().set("code", 500).set("msg", "退款错误:" + result[1] + result[2]);
            }
        }
        // 查询失败
        else {
            return Dict.create().set("code", 500).set("msg", "退款错误:" + query[1] + query[2]);
        }
    }

    /**
     * 查询接口
     *
     * @param outTradeNo 商家订单号
     * @return 结果
     */
    @PostMapping(value = "/query")
    @ResponseBody
    public Dict query(String outTradeNo) {
        // 查询订单
        String[] query = weChatPayService.query(null, outTradeNo);
        // 查询成功
        if (query[0].equals("Y")) {
            return Dict.create().set("code", 200).set("msg", "查询成功!" +
                    " <br>商户单号:" + query[1] +
                    " <br>微信单号:" + query[2] +
                    " <br>订单金额:" + query[3] + "分" +
                    " <br>交易时间:" + query[4] +
                    " <br>交易状态:" + query[5] +
                    " <br>交易描述:" + query[6]
            );
        }
        // 查询失败
        else if (query[0].equals("N")) {
            return Dict.create().set("code", 500).set("msg", "查询结果:" + query[1] + query[2]);
        }
        // 查询发送异常
        else {
            return Dict.create().set("code", 500).set("msg", "查询失败:" + query[1] + query[2]);
        }
    }
}

到此为止,我们千辛万苦费劲拿到的范围值,仅仅是为了一个prepay_id,这个prepay_id是在微信支付接口返回的qrcode就是prepay_id

5、调起支付

看看前台都需接收哪些值吧
https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_1_4.shtml
在这里插入图片描述
Java代码展示:

import cn.hutool.core.io.file.FileReader;
import com.alibaba.fastjson.JSONObject;
import org.apache.commons.lang.RandomStringUtils;
import org.apache.poi.util.IOUtils;
import org.junit.Test;
import org.springframework.stereotype.Repository;
import org.springframework.util.ResourceUtils;

import java.io.IOException;
import java.io.InputStream;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.Signature;
import java.security.SignatureException;
import java.util.Base64;

@Repository
public class CreateSign {
    public String getToken(String appid,String prepay_id) throws IOException, SignatureException, NoSuchAlgorithmException, InvalidKeyException {
        String randomOnce = RandomStringUtils.randomAlphanumeric(32);
        //随机字符串
        String nonceStr = randomOnce;//真!随机字符串
        //时间戳
        long timestamp = System.currentTimeMillis() / 1000;
        //从下往上依次生成
        String message = buildMessage(appid, timestamp, nonceStr, prepay_id);
        //签名
        String signature = sign(message.getBytes("utf-8"));

        JSONObject param = new JSONObject();
        param.put("appId",appid);
        param.put("timeStamp",timestamp);
        param.put("nonceStr",randomOnce);
        param.put("package",prepay_id);
        param.put("signType","RSA");
        param.put("paySign",signature);

        return  param.toString() ;
    }
    public String sign(byte[] message) throws NoSuchAlgorithmException, SignatureException, IOException, InvalidKeyException {
        //签名方式
        Signature sign = Signature.getInstance("SHA256withRSA");
        //在本地环境运行使用 私钥,通过MyPrivateKey来获取,这是个静态类可以接调用方法 ,需要的是_key.pem文件的绝对路径配上文件名
		sign.initSign(MyPrivatekey.getPrivateKey("E:\\微信支付\\Cert\\apiclient_key.pem"));
		//在服务器中使用这种方式
        //FileReader fileReader = new FileReader("\\usr\\local\\WXCertUtil\\cert\\1621641850_20220614_cert\\apiclient_key.pem");
        sign.initSign(MyPrivatekey.getPrivateKey(fileReader.readString()));
        sign.update(message);

        return Base64.getEncoder().encodeToString(sign.sign());
    }

    /**
     *  按照前端签名文档规范进行排序,\n是换行
     * @param appid
     * @param timestamp
     * @param nonceStr
     * @param prepay_id
     * @return
     */
    public String buildMessage(String appid, long timestamp,String nonceStr,String prepay_id) {
        return appid + "\n"
                + timestamp + "\n"
                + nonceStr + "\n"
                + "prepay_id="+prepay_id + "\n";
    }
}

补充上面代码用到的MyPrivateKey工具类

import org.springframework.stereotype.Repository;

import java.io.IOException;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Base64;

@Repository
public class MyPrivatekey {
    /**
     * 获取私钥。
     *
     * @param filename 私钥文件路径  (required)
     * @return 私钥对象
     */
    public static PrivateKey getPrivateKey(String filename) throws IOException {

        String content = new String(Files.readAllBytes(Paths.get(filename)), "utf-8");
        try {
            String privateKey = filename.replace("-----BEGIN PRIVATE KEY-----", "")
                    .replace("-----END PRIVATE KEY-----", "")
                    .replaceAll("\\s+", "");

            KeyFactory kf = KeyFactory.getInstance("RSA");
            return kf.generatePrivate(
                    new PKCS8EncodedKeySpec(Base64.getMimeDecoder().decode(privateKey)));
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException("当前Java环境不支持RSA", e);
        } catch (InvalidKeySpecException e) {
            throw new RuntimeException("无效的密钥格式");
        }
    }
}

Service层代码:

@Override
public String rsaPaySign(String prepayId) {
   String appId = "XXXXXXX";
   try {
       String getToken = createSign.getToken(appId, prepayId);
       return getToken;
   } catch (IOException e) {
       e.printStackTrace();
   } catch (SignatureException e) {
       e.printStackTrace();
   } catch (NoSuchAlgorithmException e) {
       e.printStackTrace();
   } catch (InvalidKeyException e) {
       e.printStackTrace();
   }
   return "签名错误";
}

前端控制器Controller层代码:

     /**
     * 调起支付对sign加密
     * @param prepayId
     * @return
     */
    @GetMapping("rsaPaySign")
    public String rsaPaySign(@RequestParam("prepayId")String prepayId){
        System.out.println("prepayId:"+prepayId);
        String rsaPaySign = weChatPayService.rsaPaySign(prepayId);
        return rsaPaySign;
    }

6、前端调起支付部分代码(Vue)

function onBridgeReady() {
	WeixinJSBridge.invoke('getBrandWCPayRequest', {
		"appId":value.appId,     //公众号ID,由商户传入     
		"timeStamp": value.timeStamp+"",     //时间戳,自1970年以来的秒数     
		"nonceStr": value.nonceStr,      //随机串     
		"package": "prepay_id="+value.package,
		"signType": value.signType,     //微信签名方式:     
		"paySign": value.paySign
	},
	function(res) {
		if (res.err_msg == "get_brand_wcpay_request:ok") {
			//支付成功							
		} else if (res.err_msg == "get_brand_wcpay_request:cancel") {
										
		} else if (res.err_msg == "get_brand_wcpay_request:fail") {
										
		} 
		// 使用以上方式判断前端返回,微信团队郑重提示:res.err_msg将在用户支付成功后返回ok,但并不保证它绝对可靠。
		});
}
if (typeof WeixinJSBridge == "undefined") {
	if (document.addEventListener) {
		document.addEventListener('WeixinJSBridgeReady', onBridgeReady, false);
	} else if (document.attachEvent) {
		document.attachEvent('WeixinJSBridgeReady', onBridgeReady);
		document.attachEvent('onWeixinJSBridgeReady', onBridgeReady);
	}
} else {
	onBridgeReady();
}
  • 13
    点赞
  • 64
    收藏
    觉得还不错? 一键收藏
  • 29
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值