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

2023-11-05

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();
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Java实现微信支付(微信公众号JSAPI支付) 的相关文章

  • 如何在测试套件中定义 JUnit 方法规则?

    我有一个类 它是 JUnit 测试类的 JUnit 套件 我想定义一个规则on the suite 这是可以做到的 但需要做一些工作 您还需要定义自己的 Suite 运行程序和测试运行程序 然后在测试运行程序中重写 runChild 使用以
  • 如何查看Pocketsphinx词典中是否存在该单词?

    我只是想看看字典文件中是否存在字符串 字典文件位于问题底部 我想检查语音识别器是否可以识别单词 例如 识别器将无法识别字符串ahdfojakdlfafiop 因为字典中没有定义 所以 我可以检查某个单词是否在 pocktsphinx 词典中
  • Spring控制器是线程安全的吗

    我遇到了这个控制器示例 想知道它是否是线程安全的 我特别想知道 gson 实例变量 import org springframework stereotype Controller import org springframework we
  • MP3:一种以毫秒为单位获取任何给定字节位置的位置的方法?

    我创建了一个 servlet 它返回从客户端请求的任何给定字节位置开始的流 来自 MP3 文件 这允许客户端在任何给定字节位置立即开始播放 而无需进行任何本地查找 现在 我有一个滑块可以直观地显示进度 我正在使用当前字节位置来更新滑块 但是
  • 如何在 Eclipse 中用阿拉伯语读写

    我在 eclipse 中编写了这段代码来获取一些阿拉伯语单词 然后打印它们 public class getString public static void main String args throws Exception PrintS
  • 检查双精度值的等于和不等于条件

    我在比较两者时遇到困难double values using and 我创建了 6 个双变量并尝试进行比较If健康 状况 double a b c d e f if a b c d e f My code here in case of t
  • Java 创建浮雕(红/蓝图像)

    我正在编写一个 Java 游戏引擎 http victoryengine org http victoryengine org 并且我一直在尝试生成具有深度的 3D 图像 您可以使用那些红色 蓝色眼镜看到 我正在使用 Java2D 进行图形
  • MI设备中即使应用程序被杀死,如何运行后台服务

    您好 我正在使用 alaram 管理器运行后台服务 它工作正常 但对于某些 mi 设备 后台服务无法工作 我使用了服务 但它无法工作 如何在 mi 中运行我的后台服务 MI UI有自己的安全选项 所以你需要的不仅仅是上面提到的粘性服务 你需
  • spring - 强制 @Autowired 字段的 cglib 代理

    我有混合堆栈 EJB 和 Spring 为了将 Spring 自动装配到 EJB 我使用SpringBeanAutowiringInterceptor 不确定这是否会影响我遇到的问题 在尝试通过以下方式自动装配 bean 时 Scope p
  • 使用 java 按电子邮件发送日历邀请

    我正在尝试使用 java 发送每封电子邮件的日历邀请 收件人收到电子邮件 但不会显示接受或拒绝的邀请 而是将该事件自动添加到他的日历中 我正在使用 ical4j jar 构建活动 邀请 private Calendar getInvite
  • Install4j:如何在安装结束时执行命令行 java -jar filename.jar

    在 Intall4j 中 在安装结束时 我只想通过执行如下命令行来初始化某些内容 java jar filename jar 我怎样才能归档这个任务install4j Thanks 将 运行可执行文件或批处理文件 操作添加到 安装屏幕 并设
  • tomcat 过滤所有 web 应用程序

    问题 我想对所有网络应用程序进行过滤 我创建了一个过滤器来监视对 apache tomcat 服务器的请求 举例来说 它称为 MyFilter 我在 netbeans 中创建了它 它创建了 2 个独立的目录 webpages contain
  • jmap - 组织和堆操作会给 jvm 带来开销吗?

    正如标题所述 需要多少开销jmap histo and jmap heap分别带到jvm 如果一个内存敏感的 Java 进程处于OutOfMemory 例如 大约 96 的堆已满 并且无法通过 full gc 清除 其中一项操作是否有可能将
  • 从 Java 日历迁移到 Joda 日期时间

    以前 当我第一次设计股票应用相关软件时 我决定使用java util Date表示股票的日期 时间信息 后来我体会到了大部分方法java util Date已弃用 因此 很快 我重构了所有代码以利用java util Calendar 然而
  • 让JScrollPane控制多个组件

    对于我的应用程序 我正在设计一个脚本编辑器 目前我有一个JPanel其中包含另一个JPanel保存行号 位于左侧 以及JTextArea用于允许用户输入代码 位于右侧 目前 我已经实施了JScrollPane on the JTextAre
  • Apache Commons CLI:替代已弃用的 OptionBuilder?

    IntelliJ 显示此示例代码中不推荐使用 OptionBuilderhttp commons apache org proper commons cli usage html http commons apache org proper
  • 如何在android sdk上使用PowerMock

    我想为我的 android 项目编写一些单元测试和仪器测试 然而 我遇到了一个困扰我一段时间的问题 我需要模拟静态方法并伪造返回值来测试项目 经过一些论坛的调查 唯一的方法是使用PowerMock来模拟静态方法 这是我的 gradle 的一
  • 来自客户端的超时 Web 服务调用

    我正在使用 RestEasy 客户端调用网络服务 一项要求是 如果调用运行时间超过 5 秒 则中止 超时调用 我如何使用 RestEasy 客户端实现这一目标 我只看到服务器端超时 即如果在一定时间内未完成请求 Rest Easy 网络服务
  • 为什么 BufferedWriter 不写入文件?

    我有这个代码 String strings Hi You He They Tetrabenzene Caaorine Calorine File file new File G words txt FileWriter fWriter Bu
  • Java中有类似分支/跳转表的东西吗?

    Java有类似分支表或跳转表的东西吗 分支表或跳转表是 根据维基百科 http en wikipedia org wiki Branch table 用于描述使用分支指令表将程序控制 分支 转移到程序的另一部分 或可能已动态加载的不同程序

随机推荐

  • 静态类型推导

    前面说泛型的时候 提到了C 模板的实现方式是动态特性静态化 在实际情况中 这是一个提高效率的好办法 动态性的好处是灵活 开发简便 静态性的特性是效率高 编译期检查较好 因此很自然地就有一个问题 能不能各取所长 达到两全其美 应该说 在一定程
  • Apache Tika入门

    文章目录 1 基本介绍 2 Tika使用 2 1 解析器接口 The Parser interface 2 1 1 自定义Parser类 2 2 检测器接口 2 3 Tika配置 1 基本介绍 Apache Tika 文本分析工具包 能够检
  • Python可视化——3D绘图解决方案pyecharts、matplotlib、openpyxl

    Python可视化 3D绘图解决方案pyecharts matplotlib openpyxl 1 pyecharts 2 matplotlib 3 openpyxl 这篇博客将介绍python中可视化比较棒的3D绘图包 pyecharts
  • Java Thread.Sleep()具有什么功能呢?

    转自 Java Thread Sleep 具有什么功能呢 下文笔者讲述Thread Sleep 方法的功能简介说明 如下所示 Thread Sleep 方法的功能 暂停当前线程 当线程停止后 会通知线程调度器在当前时间周期内将其状态设置为w
  • qt5中QString输出变量的值

    概述 QString类中有两种实现输出字符串中含有变量值的方式 这里做下记录 示例 方法一 使用QString的函数asprintf int m age 12 QString asprintf 年龄是 d m age 方法二 使用arg Q
  • 对数器

    记录下笔记 对数器的概念和作用 对数器主要用来测试自己写的程序是否完全正确 该方法通过大量的随机数据进行验证 有时候做算法题可能无法短时间内 或者很难推导出正确的数学式子 比如贪心算法 来验证自己算法的正确 这时候就需要大量的随机样本进行测
  • vue-quill-editor编辑器的安装与配置(包含字号大小,图片缩放)

    1 安装vue quill editor npm install vue quill editor save 2 main js全局引用 import VueQuillEditor from vue quill editor 一定要引入这三
  • jquery完成商品列表按价格升序、降序排序

    实现思路 商品列表按价格的升序 降序的实现主要思路就是获取到所有的商品节点 然后都存到数组里面 数组就按照商品价格进行冒泡排序 将数组里的商品进行降序或升序的排序 最后清空在html下原本的所有商品 然后把数组里的商品按排序后的顺序重新添加
  • mysql html,将HTML存储到MySQL数据库中

    I m trying to store a String which contains HTML in a MySQL database using Longtext data type But it always says You hav
  • 【NLP】信息检索变得简单、不同类型及其工作原理

    大家好 我是Sonhhxg 柒 希望你看完之后 能对你有所帮助 不足请指正 共同学习交流 个人主页 Sonhhxg 柒的博客 CSDN博客 欢迎各位 点赞 收藏 留言 系列专栏 机器学习 ML 自然语言处理 NLP 深度学习 DL fore
  • 第三章 分组-练习题

    第三章 练习题 import numpy as np import pandas as pd Ex1 汽车数据集 现有一份汽车数据集 其中Brand Disp HP分别代表汽车品牌 发动机蓄量 发动机输出 df pd read csv da
  • zabbix监控实战示例

    1 监控TCP 连接数 1 创建conf文件引用脚本 vim usr local zabbix etc zabbix agentd conf d all conf 或者 vim usr local zabbix etc zabbix age
  • JAVA回调机制(CallBack)详解

    序言 最近学习java 接触到了回调机制 CallBack 初识时感觉比较混乱 而且在网上搜索到的相关的讲解 要么一言带过 要么说的比较单纯的像是给CallBack做了一个定义 当然了 我在理解了回调之后 再去看网上的各种讲解 确实没什么问
  • [架构之路-30]:目标系统 - 系统软件 - Linux OS根文件系统rootfs的概念、组成、制作以及用busybox制作根文件系统

    目录 前言 第1章 什么是根文件系统 1 1 什么是文件 1 2 什么是文件系统 1 3 文件系统组织文件的方式 树形结构 1 4 统一的虚拟文件系统 1 5 物理存储介质与物理文件系统类型 1 5 什么是根文件系统 第2章 根文件系统的标
  • Matlab License Manage Error -103问题——远程桌面调用Matlab出错

    操作系统 Windows 10 软件版本 Matlab R2016a R2017a 报错 License Manage Error 103 因为新型冠状病毒疫情的原因 开学继续推迟 从2月份开始 在家办公已经1个月了 前几天处于某些原因需要
  • Docker安装好后默认路径

    Docker安装好后默认路径为 var lib docker 其下的containers文件夹为容器文件夹 image为镜像文件夹
  • linux--shell错误:syntax error near unexpected token ‘('

    这几天编写了几个简单的shell程序 然后都出现了syntax error near unexpected token 的错误 然后实在是检查不出错误 后面百度了才找到的原因 之前错误的程序片段如下 usr whoami dr pwd 提示
  • 数字化时代-22:入住CSDN 3个月进展报告

    每周文章变化 每周访问量变化 每周访问总重量变化 每周粉丝总计变化 总积分变化
  • texlive包的添加与更新

    texlive包的添加与更新 修改完包之后 要用texhash命令进行位置的更新 texhash需要管理员权限 通过用户权限执行texhash后 发现texhash更新的是下面这些目录 texhash usr share texlive t
  • Java实现微信支付(微信公众号JSAPI支付)

    Java实现微信支付 微信公众号JSAPI支付 第一步 开发环境准备 在接入微信支付之前 需要现在微信支付商户平台入驻 成为商家 才能继续后续的开发 微信支付商户平台网址 https pay weixin qq com 不过 个人用户是无法