萤石微信支付对接(小程序版)

1.萤石支付简介及流程

萤石支付,是对第三方支付平台的二次封装。考虑到的目的可能有:1.合资公司的金钱交易需要走总部财务 2.二次封装对接更加容易,与第三方支付sdk的交互都由萤石平台封装完成了。

1.1 支付流程

  1. 通过调用萤石创建订单接口,创建萤石订单,并拿到订单id。
  2. 用上一步获取的订单id调用萤石的拉起支付的接口,获取微信小程序拉起支付界面所需要的参数。
  3. 前端获取上一步参数拉起微信支付界面,输入支付密码并完成支付。这一步支付完成后,小程序api与微信支付做交互。
  4. 提供一个支付结果回调的通知接口给萤石相关人员,该接口地址需要外网可以访问,前端支付完成后,萤石会回调该接口通知此次支付是否成功支付。

*支付流程归纳总结:创建萤石订单——>拉起支付——>前端完成支付——>支付回调——>更新系统订单信息

1.2 前置准备工作

  • 联系相关产品,创建对应的小程序号、商户号。(这一步需产品完成与开发无关)
  • 产品需提供给开发小程序对应的appid和secret秘钥。
  • 若是对接萤石支付,需要萤石支付相关人员提供app(应用唯一标识)、ssid(租户id)、token(签名会用到)。
  • 获取萤石支付文档(可能只能拿到相关接口的api截图),主要需要的api接口有:创建订单接口、调起支付接口、查询订单接口(自测可能会用到)。
  • 提供给萤石相关人员支付成功回调的接口地址(注意:该回调地址必须外网可以访问到)。

1.3 签名

调用萤石支付或者微信支付的接口都会用到签名,不同的平台会有自己的签名规约。

  • 萤石的签名规则如下图所示:
    萤石微信支付对接(小程序版)_第1张图片

1.4 微信授权登录

微信授权使用的是OAuth2.0授权的方式。通过微信授权,可以获取微信用户在本小程序下对应的openid(在支付的时候会用到),也可以结合自己的登录接口使我们系统与微信账号做一个绑定,后续就不需要重复授权,直接登录就行。

1.4.1 微信授权登录流程

  1. 首先通过前端小程序的wx.login方法获取jscode(这一步需要前端同事操作)
  2. 调用后台提供的授权登录接口,需要传参手机号和上一步获取的code编码。
  3. 调用微信提供的获取openid的接口,api接口地址为:https://api.weixin.qq.com/sns/jscode2session?,请求方式为get
  4. openid以及手机号与本系统的用户账号绑定,具体业务具体分析。

2.支付具体代码实现

2.1 签名方法

@Value("${wechat.pay.token:5dfe2115674e7115}")
    public void setWechatPayToken(String str){
        HmacSHA256Util.wechatPayToken = str;
    }

    /***
     * 利用Apache的工具类实现SHA-256加密
     * @param parameterMap 加密参数
     * @return
     */
    public static String encryptSHA256Str(Map<String, Object> parameterMap) {
        if(parameterMap.isEmpty()){
            return null;
        }
        String originalText = getAlphabeticalSort(parameterMap);
        MessageDigest messageDigest;
        String encdeStr = "";
        try {
            messageDigest = MessageDigest.getInstance("SHA-256");
            byte[] hash = messageDigest.digest(originalText.getBytes(StandardCharsets.UTF_8));
            encdeStr = Hex.encodeHexString(hash);
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        return encdeStr;
    }

    /**
     * @description 根据map首字母进行排序
     * @date 2022/12/27 15:47
     * @user mengwei6
     * @param parameterMap 请求参数
     * @return
    */
    private static String getAlphabeticalSort(Map<String, Object> parameterMap){
        // 排序
        Map<String, Object> sortedMap = parameterMap.entrySet().stream().sorted(Map.Entry.comparingByKey()).collect(
                Collectors.toMap(
                        Map.Entry::getKey,
                        Map.Entry::getValue,
                        (oldVal, newVal) -> oldVal,
                        LinkedHashMap::new
                )
        );
        // 封装
        StringBuilder sb = new StringBuilder();
        sortedMap.entrySet().forEach(obj->{ sb.append(obj.getKey()).append(obj.getValue());});
        return sb+wechatPayToken;
    }

2.2 创建萤石支付订单

2.2.1 接口api文档

萤石微信支付对接(小程序版)_第2张图片

2.2.2 代码实现

/**
     * 创建萤石萤石订单,并返回订单号
     * @param timestamp 时间戳
     * @param mobile 下单人的手机号
     * @return 预支付订单id
     */
    private String getPreOrderId(Long timestamp,String mobile){
        log.warn("开始创建萤石萤石订单...");

        // 封装参数
        Map<String,Object> paramMap = new HashMap<>();
        paramMap.put("app",app);
        paramMap.put("user_name","ezmall");
        paramMap.put("mobile",mobile);
        paramMap.put("ssid",ssid);
        // 这个商品是萤石创建租户的时候默认上架的一个商品,非本系统交易的商品,此商品可以信息可以问萤石相关人员获取
        paramMap.put("products","[{\"bn\":\"包月套餐1\",\"num\":1,\"price\":\"0.01\"}]");
        paramMap.put("timestamp",timestamp);
        // 签名(上面签名方法生成)
        String sign = HmacSHA256Util.encryptSHA256Str(paramMap);
        paramMap.put("sign",sign);
        String response = HttpUtil.post(orderCreateUrl, paramMap);
        log.warn("获取预支付订单号:{}",response);
        OpenResultBO<String> result = JSON.parseObject(response, new TypeReference<OpenResultBO<String>>(){});
        if (PortalConstant.SUCCESS_RETURN.equals(result.getCode()) && StringUtils.isNotEmpty(result.getData())){
            Map map = JSON.parseObject(result.getData(), Map.class);
            log.warn("创建萤石订单id为{}",map.get("order_id").toString());
            return map.get("order_id").toString();
        }else {
            log.error("状态码:{},错误信息:{}",result.getCode(),result.getMsg());
            throw new BaseRuntimeException(PortalResultCode.CREATE_YS_ORDER_FAIL);
        }
    }

2.3 拉起支付

2.3.1 接口api文档

萤石微信支付对接(小程序版)_第3张图片

2.3.2 代码实现

/**
     * 调用萤石接口拉起支付
     * @param preOrderId 萤石订单id
     * @param amount 金额
     * @param openId 微信openid
     * @return
     */
    private String getPay(String preOrderId,String amount,String openId){
        log.warn("开始拉起萤石支付接口...");
        // 封装参数
        Map<String,Object> paramMap = new HashMap<>();
        paramMap.put("ssid",ssid);
        paramMap.put("app",app);
        YsOrderParam ysOrderParam = YsOrderParam.builder().order_no(preOrderId).amount(amount).total_amount(amount).build();
        List<YsOrderParam> ysOrderList = Collections.singletonList(ysOrderParam);
        String orderParam = JSONObject.toJSONString(ysOrderList);
        paramMap.put("order",orderParam);
        paramMap.put("pm_code","miniapppay");
        ExtParam extParam = ExtParam.builder().open_id(openId).build();
        paramMap.put("ext",JSONObject.toJSON(extParam));
        // 签名
        String sign = HmacSHA256Util.encryptSHA256Str(paramMap);
        paramMap.put("sign",sign);
        String response = HttpUtil.post(getPayUrl, paramMap);
        log.warn("拉起萤石支付接口获取参数:{}",response);
        OpenResultBO<String> result = JSON.parseObject(response, new TypeReference<OpenResultBO<String>>(){});
        if (PortalConstant.SUCCESS_RETURN.equals(result.getCode()) && ObjectUtil.isNotEmpty(result.getData())){
            log.warn("拉起萤石支付接口调用成功,获取参数为{}",result.getData());
            return result.getData();
        }else {
            log.error("状态码:{},错误信息:{}",result.getCode(),result.getMsg());
            throw new BaseRuntimeException(PortalResultCode.CALL_YS_PAY);
        }
    }

2.4 查询订单状态

2.4.1 接口api文档

萤石微信支付对接(小程序版)_第4张图片

2.4.2 代码实现

/**
     * 查询订单状态是否已支付
     * @param order 萤石支付订单号
     * @return
     */
    private boolean checkIsPay(String order){
        // 封装参数
        Map<String,Object> paramMap = new HashMap<>();
        paramMap.put("app",app);
        paramMap.put("ssid",ssid);
        paramMap.put("order",order);
        // 签名
        String sign = HmacSHA256Util.encryptSHA256Str(paramMap);
        paramMap.put("sign",sign);
        String response = HttpUtil.get(queryOrderStatus, paramMap);
        OpenResultBO<String> result = JSON.parseObject(response, new TypeReference<OpenResultBO<String>>(){});
        if (PortalConstant.SUCCESS_RETURN.equals(result.getCode()) && StringUtils.isNotEmpty(result.getData())){
            List<Map> mapList = JSON.parseArray(result.getData(), Map.class);
            if (CollectionUtils.isEmpty(mapList)){
                throw new BaseRuntimeException(PortalResultCode.ORDER_NOT_FOUND);
            }
            Map map = mapList.get(PortalConstant.ZERO);
            return Integer.valueOf(String.valueOf(map.get("pay_status"))).equals(PortalConstant.ORDER_PAY_SUCCESS);
        }else {
            log.error("状态码:{},错误信息:{}",result.getCode(),result.getMsg());
            throw new BaseRuntimeException(PortalResultCode.QUERY_ORDER_FAIL);
        }
    }

2.5 支付回调接口

2.5.1 接口api文档

萤石微信支付对接(小程序版)_第5张图片

2.5.2 代码实现

	@ApiOperation(value = "订单通知-1.3", notes = "支付回调接口")
    @PostMapping("/orderNotify")
    public Map<String,Object> orderNotify(@Validated OrderNotifyParam param) {
        log.warn("================开始获取支付回调==================");
        Map<String,Object> resultMap = new HashMap<>();
        if (param == null){
            log.warn("未获取到回调参数");
            resultMap.put("code",-1);
            resultMap.put("data",Collections.emptyList());
            resultMap.put("message","未获取到回调参数");
            return resultMap;
        }
        log.warn("支付回调接收到的参数:{}",param.toString());
        Map<String, Object> map = JSON.parseObject(JSON.toJSONString(param), new TypeReference<Map<String, Object>>() {
        });
        if (!Objects.isNull(map)){
            log.warn("================开始校验支付回调签名==================");
            map.remove("sign");
            String sign = HmacSHA256Util.encryptSHA256Str(map);
            if (StringUtils.isNotEmpty(sign) && sign.equals(param.getSign())){
                if (param.getPay_status() != null && param.getOrder_id() != null){
                    if (param.getPay_status().equals(1)){
                        // 回调更新订单状态,次数更新
                        wechatUserOrderService.paySuccess(param.getOrder_id());
                        log.warn("================获取支付回调结束==================");
                        // 返回调用方
                        resultMap.put("code",200);
                        resultMap.put("data",Collections.emptyList());
                        return resultMap;
                    }
                }
            }else {
                resultMap.put("code",PortalResultCode.SIGN_CHECK_ERROR.getCode());
                resultMap.put("data",Collections.emptyList());
                resultMap.put("message",PortalResultCode.SIGN_CHECK_ERROR.getMessage());
                log.warn(PortalResultCode.SIGN_CHECK_ERROR.getMessage());
                return resultMap;
            }
        }
        resultMap.put("code",-1);
        resultMap.put("data",Collections.emptyList());
        resultMap.put("message","回调参数异常");
        log.warn("回调参数异常");
        return resultMap;
    }

3.前端相关

3.1 微信授权登录

  1. 授权调用wx.login方法获取jscode
  2. 请求后端的微信授权登录接口

3.2 微信拉起支付界面

  1. 调用后台购买支付接口获取拉起支付的参数
  2. 根据拉起支付参数调起微信支付界面
  3. 支付完成之后发送请求给微信支付后端

3.3 支付完成验证是否支付成功

  1. 支付界面输完密码支付成功后,轮询调用后端订单状态的接口。
  2. 正常情况下都可实时获取支付成功,前端应给个支付查询订单状态的loading,并做友好处理。

你可能感兴趣的:(java对接,小程序,微信,微信小程序,java)