本地内网穿透获取微信支付回调,jsapi支付,获取openId,prepayId,调起支付

微信jsapi支付是通过扫码后,直接进入微信支付页

说明一下,jsapi我采用的是公众号获取,需要在公众号上配置域名才可以回调获取openID,当然你可以直接配置回调在你上面的地址,但是我由于特殊原因不能够使用配置的域名,所以要通过nginx转发

前置条件(申请jsapi ,公共号配置回调域名) 

正常流程是:编写接口->下载并配置ngrok实现内网穿透(获取到公网ip)->接口生成二维码->手机微信扫码,接口配置回调接口(ngrok获取到的公网ip)->回调接口收到通知,通过code获取到openId

我的流程是:编写接口->下载并配置ngrok实现内网穿透(获取到公网ip)->配置nginx将公众号配置的域名转发到我的公网ip上->接口生成二维码->手机微信扫码

如果公众号能直接配置内网穿透的域名就直接配置即可,我也就多了配置nginx这一步

流程:首先写一个接口,这个接口是通过手机微信扫码访问的,必须通过微信扫码,不然会出现请在微信客户端访问链接,访问后微信不会返回东西,需要通过另外一个公网可访问的接口来给微信调用,获取到code,拿到code再去获取openid实现扫码支付

问题:java如果是本地调试是无法拿到微信的回调的,但是部署到服务器又不好调试,所以采用内网穿透的形式我这边使用的ngrok,至于netapp和花生壳好像是收费的而且实名啥的比较麻烦

ngrok安装与配置:

1.首先进入官网:ngrok | Unified Application Delivery Platform for Developers,注册账号到这个页面

2. 点击下载,解压

3.获取授权码

4.拷贝授权码,然后运行刚刚的exe文件,他会放一个yml文件再c盘不用管只要生成成功就没问题
ngrok authtoken 你的授权码
5.运行命令
ngrok http 8080

8080是我网关的端口,根据自己项目自行配置

这样就ok, https://bbdd-240e-398-92-80d0-484c-b30b-166f-846d.ngrok-free.app 是自己的公网ip,之后我们就要想办法把回调转到这里访问

后端配置:

配置手机扫码访问接口,必须手机微信客户端扫码访问接口,不然会报错!!!!

1.编写接口:
2.生成二维码访问:

草料文本二维码生成器

这样直接用微信扫码就可以访问接口

 3.配置回调地址马赛克的地方是自己的公众号配置的域名,我配置了个local是用于nginx转发到自己内网穿透的地址,公众号配置的域名就是内网穿透的地址的话忽略这一步

rewrite ^/local(.*)$ $1 break;意思是删除local不然local也会拼接上去

4.编写回调接口:
    /**  回调地址  **/
    @RequestMapping("/oauth2Callback/{aesData}")
    public void oauth2Callback(@PathVariable("aesData") String aesData,HttpServletRequest request) throws Exception {
        System.out.println("进入回调");
        System.out.println(aesData);
        System.out.println(request.getParameterMap());
        System.out.println(request.getParameter("code"));
    }
5.成功回调 

6.获取 openid,需要有appid,appsecret,code,如下所示
 public String getChannelUserId(JSONObject reqParams, MchAppConfigContext mchAppConfigContext) {
        String code = reqParams.getString("code");
        String redirectUrl = String.format("https://api.weixin.qq.com/sns/oauth2/access_token?appid=%s&secret=%s&code=%s&grant_type=authorization_code",
            mchAppConfigContext.getAppId(), mchAppConfigContext.getChannel().getWxAppSecret(), code);
        try {
            JSONObject jsonObject = JSON.parseObject(restTemplate.getForObject(redirectUrl, String.class));
            return jsonObject.get("openid").toString();
        } catch (NullPointerException e) {
            log.error("获取openid失败=={}",reqParams);
        }
        return null;
    }
7.获取prepayId将相应的参数填入jsapiService,这个是微信官方的demo中的代码,直接拿来改的
    /**
     * JSAPI支付下单
     */
    public PrepayResponse prepay(Map<String, Object> map) {
        String orderNo = map.get(PayConstants.ORDERNO).toString();
        String openId = map.get(PayConstants.OPENID).toString();
        Integer money = (Integer) map.get(PayConstants.AMOUNT);
        String notifyUrl = map.get(PayConstants.NOTIFY_URL).toString();
        PrepayRequest request = new PrepayRequest();
        // 调用request.setXxx(val)设置所需参数,具体参数可见Request定义
        Amount amount = new Amount();
        amount.setTotal(money);
        request.setAmount(amount);
        request.setAppid(wxpayProperties.getAppid());
        request.setMchid(wxpayProperties.getMerchantId());
        request.setDescription("测试商品标题");
        request.setNotifyUrl(notifyUrl);
        request.setOutTradeNo(orderNo);

        //获取openID
        Payer payer = new Payer();
        payer.setOpenid(openId);
        request.setPayer(payer);
        // 调用接口
        return jsapiService.prepay(request);
    }

官方代码如下

package com.wechat.pay.java.service.payments.jsapi;

import com.wechat.pay.java.core.Config;
import com.wechat.pay.java.core.RSAAutoCertificateConfig;
import com.wechat.pay.java.core.exception.HttpException;
import com.wechat.pay.java.core.exception.MalformedMessageException;
import com.wechat.pay.java.core.exception.ServiceException;
import com.wechat.pay.java.service.payments.jsapi.model.CloseOrderRequest;
import com.wechat.pay.java.service.payments.jsapi.model.PrepayRequest;
import com.wechat.pay.java.service.payments.jsapi.model.PrepayResponse;
import com.wechat.pay.java.service.payments.jsapi.model.QueryOrderByIdRequest;
import com.wechat.pay.java.service.payments.jsapi.model.QueryOrderByOutTradeNoRequest;
import com.wechat.pay.java.service.payments.model.Transaction;

/** JsapiService使用示例 */
public class JsapiServiceExample {

  /** 商户号 */
  public static String merchantId = "190000****";
  /** 商户API私钥路径 */
  public static String privateKeyPath = "/Users/yourname/your/path/apiclient_key.pem";
  /** 商户证书序列号 */
  public static String merchantSerialNumber = "5157F09EFDC096DE15EBE81A47057A72********";
  /** 商户APIV3密钥 */
  public static String apiV3Key = "...";

  public static JsapiService service;

  public static void main(String[] args) {
    // 初始化商户配置
    Config config =
        new RSAAutoCertificateConfig.Builder()
            .merchantId(merchantId)
            // 使用 com.wechat.pay.java.core.util 中的函数从本地文件中加载商户私钥,商户私钥会用来生成请求的签名
            .privateKeyFromPath(privateKeyPath)
            .merchantSerialNumber(merchantSerialNumber)
            .apiV3Key(apiV3Key)
            .build();

    // 初始化服务
    service = new JsapiService.Builder().config(config).build();
    // ... 调用接口
    try {
      closeOrder();
    } catch (HttpException e) { // 发送HTTP请求失败
      // 调用e.getHttpRequest()获取请求打印日志或上报监控,更多方法见HttpException定义
    } catch (ServiceException e) { // 服务返回状态小于200或大于等于300,例如500
      // 调用e.getResponseBody()获取返回体打印日志或上报监控,更多方法见ServiceException定义
    } catch (MalformedMessageException e) { // 服务返回成功,返回体类型不合法,或者解析返回体失败
      // 调用e.getMessage()获取信息打印日志或上报监控,更多方法见MalformedMessageException定义
    }
  }
  /** 关闭订单 */
  public static void closeOrder() {

    CloseOrderRequest request = new CloseOrderRequest();
    // 调用request.setXxx(val)设置所需参数,具体参数可见Request定义
    // 调用接口
    service.closeOrder(request);
  }
  /** JSAPI支付下单 */
  public static PrepayResponse prepay() {
    PrepayRequest request = new PrepayRequest();
    // 调用request.setXxx(val)设置所需参数,具体参数可见Request定义
    // 调用接口
    return service.prepay(request);
  }
  /** 微信支付订单号查询订单 */
  public static Transaction queryOrderById() {

    QueryOrderByIdRequest request = new QueryOrderByIdRequest();
    // 调用request.setXxx(val)设置所需参数,具体参数可见Request定义
    // 调用接口
    return service.queryOrderById(request);
  }
  /** 商户订单号查询订单 */
  public static Transaction queryOrderByOutTradeNo() {

    QueryOrderByOutTradeNoRequest request = new QueryOrderByOutTradeNoRequest();
    // 调用request.setXxx(val)设置所需参数,具体参数可见Request定义
    // 调用接口
    return service.queryOrderByOutTradeNo(request);
  }
}
8.获取到prepayId后,整合thymeleaf掉起支付

导入依赖:

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>

在templates下写一个pay.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>支付页面</title>
</head>
<body>

<script th:inline="javascript">
    /*<![CDATA[*/
    function onBridgeReady() {
        WeixinJSBridge.invoke('getBrandWCPayRequest', {
                "appId": /*[[${appId}]]*/,
                "timeStamp": /*[[${timeStamp}]]*/,
                "nonceStr": /*[[${nonce}]]*/,
                "package": "prepay_id=" + /*[[${prepay_id}]]*/,
                "signType": "RSA",
                "paySign": /*[[${base64Signature}]]*/
            },
            function(res) {
                if (res.err_msg == "get_brand_wcpay_request:ok") {
                    // 支付成功后的逻辑
                    console.log("支付成功");
                } else {
                    // 支付失败后的逻辑
                    console.error("支付失败");
                }
            });
    }

    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();
    }
    /*]]>*/
</script>

</body>
</html>
9. 回调接口返回对应页面并将值传过去
    /**
     * 回调地址
     **/
    @RequestMapping("/oauth2Callback/{aesData}")
    public String oauth2Callback(@PathVariable("aesData") String aesData,  Model model) throws Exception, TemplateInputException {
        JSONObject callbackData = JSON.parseObject(JeepayKit.aesDecode(aesData));
        String mchNo = callbackData.getString("mchNo");
        String appId = callbackData.getString("appId");
        String channel = callbackData.getString("channel");
        String notifyUrl = callbackData.getString("notifyUrl");
        String extParam = callbackData.getString("extParam");
        // 获取接口
        String ifCode = "wxpay";
        IChannelUserService channelUserService = SpringBeansUtil.getBean(ifCode + "ChannelUserService", IChannelUserService.class);
        //获取商户配置信息
        MchAppConfigContext mchAppConfigContext = configContextQueryService.queryMchInfoAndAppInfo(mchNo, channel);
        if(channelUserService == null){
            throw new BizException("不支持的客户端");
        }
        //获取到openId
        String openId = channelUserService.getChannelUserId(getReqParamJSON(), mchAppConfigContext);
        Map<String, Object> map = JSONObject.parseObject(extParam,Map.class);
        map.put("openid",openId);
        //去下单获取prepay_id
        String prepayId = wxpayJSApiPayService.gotoJSApiPay(map);
        Map<String, String> stringStringMap = buildPayMap(appId, prepayId);
        model.addAttribute("appId", appId);
        model.addAttribute("timeStamp", stringStringMap.get("timeStamp"));
        model.addAttribute("nonce", stringStringMap.get("nonceStr"));
        model.addAttribute("prepay_id", prepayId);
        model.addAttribute("base64Signature", stringStringMap.get("paySign"));
        return "pay";
    }
 10.签名:

首先将4个参数保存在list当中,构建参数:

    /**
     * 构建参数
     *
     * @param signMessage
     * @return
     */
    public static String buildSignMessage(List<String> signMessage) {
        if (signMessage != null && signMessage.size() > 0) {
            StringBuilder sbf = new StringBuilder();
            Iterator var2 = signMessage.iterator();

            while (var2.hasNext()) {
                String str = (String) var2.next();
                sbf.append(str).append("\n");
            }
            return sbf.toString();
        } else {
            return null;
        }
    }

获取签名:要使用到privateKey(我这边是直接保存到数据库的,如果保存在目录的自行解析)

    public static String encryptByPrivateKey(String data, PrivateKey privateKey) throws Exception {
        Signature signature = Signature.getInstance("SHA256WithRSA");
        signature.initSign(privateKey);
        signature.update(data.getBytes(StandardCharsets.UTF_8));
        byte[] signed = signature.sign();
        return StrUtil.str(Base64.encode(signed));
    }

完整接口代码:

@Controller
@RequestMapping("/api/channelUserId")
@Slf4j
public class ChannelUserIdController extends ApiController {

    @Autowired
    private ConfigContextQueryService configContextQueryService;

    @Autowired
    private IChannelService channelService;

    @Autowired
    private IUserService userService;
    @Autowired
    protected HttpServletResponse response;  //自动注入response
    @Autowired
    private RestTemplate restTemplate;
    @DubboReference
    private WxpayJSApiPayService wxpayJSApiPayService;

    /**
     * 重定向到微信地址
     **/
    @GetMapping("/jump")
    @ResponseBody
    public void jump() throws Exception {
        //获取请求数据

        String ifCode = "wxpay";
        ChannelUserIdRQ rq = getRQByWithMchSign(ChannelUserIdRQ.class);
        // 获取接口
        IChannelUserService channelUserService = SpringBeansUtil.getBean(ifCode + "ChannelUserService", IChannelUserService.class);
        if (channelUserService == null) {
            throw new BizException("不支持的客户端");
        }
        ChannelVo channelVo = channelService.queryByChannelNo(rq.getChannelNo());
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("mchNo",rq.getMchNo());
        jsonObject.put("appId", channelVo.getAppid());
        jsonObject.put("ifCode", ifCode);
        jsonObject.put("channel", rq.getChannelNo());
        jsonObject.put("notifyUrl", rq.getNotifyUrl());
        jsonObject.put("extParam", Base64.decodeStr(rq.getExtParam()));
        log.info("额外参数值===={}", Base64.decodeStr(rq.getExtParam()));
        //回调地址
        String callbackUrl = URLUtil.encodeAll("域名" + "/local/payproc/api/channelUserId/oauth2Callback/" + JeepayKit.aesEncode(jsonObject.toString()));

        //获取商户配置信息
        MchAppConfigContext mchAppConfigContext = configContextQueryService
            .queryMchInfoAndAppInfo(rq.getMchNo(), channelVo.getChannelNo().toString());
        String redirectUrl = channelUserService.buildUserRedirectUrl(callbackUrl, mchAppConfigContext);
        response.sendRedirect(redirectUrl);

    }


    /**
     * 回调地址
     **/
    @RequestMapping("/oauth2Callback/{aesData}")
    public String oauth2Callback(@PathVariable("aesData") String aesData,  Model model) throws Exception, TemplateInputException {
        JSONObject callbackData = JSON.parseObject(JeepayKit.aesDecode(aesData));
        String mchNo = callbackData.getString("mchNo");
        String appId = callbackData.getString("appId");
        String channel = callbackData.getString("channel");
        String notifyUrl = callbackData.getString("notifyUrl");
        String extParam = callbackData.getString("extParam");
        // 获取接口
        String ifCode = "wxpay";
        IChannelUserService channelUserService = SpringBeansUtil.getBean(ifCode + "ChannelUserService", IChannelUserService.class);
        //获取商户配置信息
        MchAppConfigContext mchAppConfigContext = configContextQueryService.queryMchInfoAndAppInfo(mchNo, channel);
        if(channelUserService == null){
            throw new BizException("不支持的客户端");
        }
        //获取到openId
        String openId = channelUserService.getChannelUserId(getReqParamJSON(), mchAppConfigContext);
        Map<String, Object> map = JSONObject.parseObject(extParam,Map.class);
        map.put("openid",openId);
        //去下单获取prepay_id
        String prepayId = wxpayJSApiPayService.gotoJSApiPay(map);
        Map<String, String> stringStringMap = buildPayMap(appId, prepayId);
        model.addAttribute("appId", appId);
        model.addAttribute("timeStamp", stringStringMap.get("timeStamp"));
        model.addAttribute("nonce", stringStringMap.get("nonceStr"));
        model.addAttribute("prepay_id", prepayId);
        model.addAttribute("base64Signature", stringStringMap.get("paySign"));
        return "pay";
    }





    public static Map<String, String> buildPayMap(String appId, String prepayId) throws Exception {
        String timeStamp = String.valueOf(System.currentTimeMillis() / 1000L);
        String nonceStr = String.valueOf(System.currentTimeMillis());
        String packageStr = "prepay_id=" + prepayId;
        Map<String, String> packageParams = new HashMap(6);
        packageParams.put("appId", appId);
        packageParams.put("timeStamp", timeStamp);
        packageParams.put("nonceStr", nonceStr);
        packageParams.put("package", packageStr);
        packageParams.put("signType", "RSA");
        ArrayList<String> list = new ArrayList();
        list.add(appId);
        list.add(timeStamp);
        list.add(nonceStr);
        list.add(packageStr);
        PrivateKey privateKey = convertToPrivateKey("");

        String packageSign = encryptByPrivateKey(buildSignMessage(list), privateKey);
        packageParams.put("paySign", packageSign);
        return packageParams;
    }

    public static String encryptByPrivateKey(String data, PrivateKey privateKey) throws Exception {
        Signature signature = Signature.getInstance("SHA256WithRSA");
        signature.initSign(privateKey);
        signature.update(data.getBytes(StandardCharsets.UTF_8));
        byte[] signed = signature.sign();
        return StrUtil.str(Base64.encode(signed));
    }

    /**
     * 构建参数
     *
     * @param signMessage
     * @return
     */
    public static String buildSignMessage(List<String> signMessage) {
        if (signMessage != null && signMessage.size() > 0) {
            StringBuilder sbf = new StringBuilder();
            Iterator var2 = signMessage.iterator();

            while (var2.hasNext()) {
                String str = (String) var2.next();
                sbf.append(str).append("\n");
            }
            return sbf.toString();
        } else {
            return null;
        }
    }

    public static PrivateKey convertToPrivateKey(String privateKeyString) throws Exception {
        // 移除PEM格式的私钥中的首尾标识,例如 -----BEGIN PRIVATE KEY-----
        privateKeyString = privateKeyString.replaceAll("-----\\w+ PRIVATE KEY-----", "");

        // 去除换行符、空格等字符
        privateKeyString = privateKeyString.replaceAll("\\s", "");

        // 将Base64编码的私钥字符串解码为字节数组
        byte[] privateKeyBytes = Base64.decode(privateKeyString);

        // 构造PKCS8EncodedKeySpec对象
        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privateKeyBytes);

        // 获取RSA密钥工厂
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");

        // 生成私钥对象
        return keyFactory.generatePrivate(keySpec);
    }
}

 注意一定要用@Controller,jump接口一定要写@ResponseBody 不然会报错template might not exist or might not be accessible by any of the configured

虽然代码执行没啥关系,但是报错看着很烦

  • 22
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值