zoukankan      html  css  js  c++  java
  • 微信支付java版(含视频讲解)

    1.背景

    实际开发中用到微信支付的概率非常大,

    至于为什么这里不必要我多少......

    微信支付大体需要对接的核心接口有

    其实大部分支付都是这些,就像上一节我们讲的支付宝支付一样

    这里以常用的H5支付为例,其他的都是差不多的....

    值得注意的时候,微信支付最常用的就是H5支付和JSAPI支付,这两者的主要区别在于

    H5支付必须在非微信浏览器打开;

    JSAPI支付只能在微信的浏览器打开;

    微信支付官方文档:https://pay.weixin.qq.com/wiki/doc/api/index.html

    1.统一下单
    2.查询订单
    3.支付结果通知
    4.申请退款
    5.查询退款
    6.下载对账单

    2.需要准备的环境

    微信支付目前还没有沙箱测试环境,要开开发支付功能必须要有企业资质申请公众号和商户号才可以

    主要的是:

    公众号appid

    商户号

    商户号的apiSercet密码

    商户号的证书

    商户号上绑定好的回调域名

    3.开发步骤

    3.1.统一下单

    代码如下:

    package com.ldp.user.service.impl;
    
    import cn.hutool.core.date.DateUtil;
    import cn.hutool.core.util.NumberUtil;
    import cn.hutool.core.util.RandomUtil;
    import cn.hutool.core.util.StrUtil;
    import cn.hutool.http.HttpUtil;
    import com.alibaba.fastjson.JSON;
    import com.ldp.user.common.constant.PayConstant;
    import com.ldp.user.common.constant.PayEnum;
    import com.ldp.user.common.constant.TestData;
    import com.ldp.user.common.constant.WeChatConstant;
    import com.ldp.user.common.exception.ParamException;
    import com.ldp.user.common.exception.WeChatException;
    import com.ldp.user.common.util.wechat.WXPayUtil;
    import com.ldp.user.entity.bo.PayBO;
    import com.ldp.user.entity.vo.PayVO;
    import com.ldp.user.service.IPayService;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.stereotype.Service;
    
    import java.util.Date;
    import java.util.HashMap;
    import java.util.Map;
    
    /**
     * @Copyright (C) XXXXXX科技股份技有限公司
     * @Author: lidongping
     * @Date: 2021-01-07 18:47
     * @Description:
     */
    @Service
    @Slf4j
    public class WeChatPayService implements IPayService {
        @Override
        public String getPayType() {
            return PayConstant.WECHAT;
        }
    
        @Override
        public PayVO getPayInfo(PayBO payBO) throws Exception {
            // 根据订单号查询订单数据 和 支付账号数据
            // 这里测试 直接将数据写在代码里
            payBO.setAppId(TestData.WX_APP_ID)
                    .setMerchantId(TestData.WX_MERCHANT_ID)
                    .setApiSecretKey(TestData.WX_SECRET)
                    .setPayTitle("测试支付")
                    .setPayMoney(0.01)
                    .setProductNo("P001")
                    .setAsynchronousNotifyUrl("http://lidongping.free.idcfengye.com/api/async/wechat/payment")
                    .setOpenId("ozwLvwah3xtA93csvZTWr0wON_IU");
            // 以下是封装微信支付数据
            Map dic = new HashMap<String, String>();
            dic.put("appid", payBO.getAppId());
            dic.put("mch_id", payBO.getMerchantId());
            String nonceStr = RandomUtil.randomString(30);
            dic.put("nonce_str", nonceStr);
            dic.put("body", payBO.getPayTitle());
            dic.put("out_trade_no", payBO.getOrderNo());
            String amount = NumberUtil.roundStr(payBO.getPayMoney() * 100, 0);
            dic.put("total_fee", amount);
            dic.put("spbill_create_ip", payBO.getUserIp());
            dic.put("notify_url", payBO.getAsynchronousNotifyUrl());
            // 生成的支付信息一小时内有效
            dic.put("time_expire", DateUtil.format(DateUtil.offsetHour(new Date(), 1), "yyyyMMddHHmmss"));
            // 根据类型请求支付
            PayEnum payType = PayEnum.valueOf(payBO.getPayType());
            switch (payType) {
                case WECHAT_QR:
                    dic.put("trade_type", "NATIVE");
                    dic.put("product_id", payBO.getProductNo());
                    break;
                case WECHAT_JSAPI:
                    dic.put("trade_type", "JSAPI");
                    if (StrUtil.isEmpty(payBO.getOpenId())) {
                        throw new ParamException("openid 为空");
                    }
                    dic.put("openid", payBO.getOpenId());
                    break;
                case WECHAT_H5:
                    dic.put("trade_type", "MWEB");
                    break;
                default:
                    throw new ParamException("没有对应的支付方式");
            }
            log.info("签名前:" + JSON.toJSONString(dic));
            dic.put("sign", WXPayUtil.generateSignature(dic, payBO.getApiSecretKey()));
            String dataXml = WXPayUtil.mapToXml(dic);
            log.info("微信请求下单url:" + WeChatConstant.PAY_URL);
            log.info("微信请求下单参数:" + dataXml);
            String resp = HttpUtil.post(WeChatConstant.PAY_URL, dataXml, 60 * 1000);
            log.info("微信请求下单返回:" + resp);
            Map<String, String> resMap = WXPayUtil.xmlToMap(resp);
            // 请求失败
            if (!resMap.get("return_code").equalsIgnoreCase(WeChatConstant.SUCCESS)) {
                throw new WeChatException(resMap.get("return_msg"));
            }
            // 下单失败
            if (!resMap.get("result_code").equalsIgnoreCase(WeChatConstant.SUCCESS)) {
                throw new WeChatException(resMap.get("err_code_des"));
            }
            // 校验支付返回结果签名
            if (!checkSign(resMap, payBO.getApiSecretKey())) {
                throw new WeChatException("验证签名失败");
            }
            // 处理返回结果
            PayVO payVO = new PayVO();
            payVO.setOrderNo(payBO.getOrderNo());
            switch (payType) {
                case WECHAT_QR:
                    payVO.setPayInfo(resMap.get("code_url"));
                    break;
                case WECHAT_JSAPI:
                    Map jsapi = new HashMap<String, String>();
                    jsapi.put("appId", payBO.getAppId());
                    // 当前时间秒
                    jsapi.put("timeStamp", System.currentTimeMillis() / 1000 + "");
                    jsapi.put("nonceStr", nonceStr);
                    jsapi.put("package", "prepay_id=" + resMap.get("prepay_id"));
                    jsapi.put("signType", "MD5");
                    jsapi.put("paySign", WXPayUtil.generateSignature(jsapi, payBO.getApiSecretKey()));
                    payVO.setPayInfo(jsapi);
                    break;
                default:
                    payVO.setPayInfo(resMap.get("mweb_url"));
            }
            return payVO;
        }
    
        /**
         * 微信响应数据签名检查
         *
         * @param map
         * @param key
         * @return
         * @throws Exception
         */
        private static boolean checkSign(Map<String, String> map, String key) throws Exception {
            String sign = map.get("sign");
            map.remove("sign");
            String orign = WXPayUtil.generateSignature(map, key);
            return orign.equalsIgnoreCase(sign);
        }
    }
    View Code

    3.2.异步通知

    代码如下:

    package com.ldp.user.controller;
    
    import cn.hutool.extra.servlet.ServletUtil;
    import com.alibaba.fastjson.JSON;
    import com.ldp.user.common.constant.TestData;
    import com.ldp.user.common.constant.WeChatConstant;
    import com.ldp.user.common.util.RedisUtil;
    import com.ldp.user.common.util.wechat.WXPayUtil;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    import javax.servlet.http.HttpServletRequest;
    import java.util.Map;
    
    /**
     * @author 姿势帝-博客园
     * @address https://www.cnblogs.com/newAndHui/
     * @WeChat 851298348
     * @create 01/10 11:18
     * @description
     */
    @RestController
    @Slf4j
    public class WeChatController {
        /**
         * 注意:
         * 1.这里是只支付宝微信xml的参数接收,微信最近也提供了json的方式,本质上都是一样的
         * 2.xml参数回调文档:https://pay.weixin.qq.com/wiki/doc/api/H5.php?chapter=9_7&index=8
         *
         * @param request
         * @return
         * @throws Exception
         */
        @PostMapping("/async/wechat/payment")
        public String notifyPaymentResult(HttpServletRequest request) throws Exception {
            log.info("wechat异步回调....");
            String paramXml = ServletUtil.getBody(request);
            Map<String, String> paramsMap = WXPayUtil.xmlToMap(paramXml);
            //解析参数
            String returnCode = paramsMap.get("return_code");
            String tradeStatus = paramsMap.get("result_code");
            String appid = paramsMap.get("appid");
            String totalFee = paramsMap.get("total_fee");
            String orderNo = paramsMap.get("out_trade_no");
            if (!WeChatConstant.SUCCESS.equals(returnCode)) {
                log.error("回调状态错误:returnCode={}", returnCode);
                return WeChatConstant.RETURN_FAIL;
            }
            // TODO 这里实际开发时根据订单号获取apiSecret秘钥
            String apiSecret = TestData.WX_SECRET;
            // 验签
            if (!WXPayUtil.isSignatureValid(paramsMap, apiSecret)) {
                log.error("验签失败");
                return WeChatConstant.RETURN_FAIL;
            }
            if (!WeChatConstant.SUCCESS.equals(tradeStatus)) {
                log.error("支付状态失败:tradeStatus={}", tradeStatus);
                return WeChatConstant.RETURN_FAIL;
            }
            // TODO 更新数据库订单数据和缓存 ... 这里略
            // 这里只是模拟方放入缓存,便于前端查询订单  60 * 60 * 2L 表示缓存2小时
            RedisUtil.set(orderNo, JSON.toJSON(paramsMap), 60 * 60 * 2L);
            log.info("异步处理成功.............");
            return WeChatConstant.RETURN_SUCCESS;
        }
    }
    View Code

    3.3.测试

    获取微信支付信息

    @Test
        void getPayInfo() {
            String url = urlLocal + "/userOrder/payInfo";
            System.out.println("请求地址:" + url);
            HttpRequest request = HttpUtil.createRequest(Method.GET, url);
            Map<String, Object> map = new HashMap<>();
            map.put("orderNo", "NO" + System.currentTimeMillis());
            // 微信支付 wechat  支付宝 alipay
            map.put("payCategory", "wechat");
            // 201 jsapi支付 , 202 微信H5  101支付宝H5
            map.put("payType", "202");
            request.form(map);
            System.out.println("请求参数:" + map);
            request.header("X-Real_IP", "192.168.5.195");
            request.setConnectionTimeout(60 * 1000);
            String respone = request.execute().body();
            System.out.println("响应结果:" + respone);
        }
    View Code

    测试结果

    请求地址:http://localhost:8080/api/userOrder/payInfo
    请求参数:{payCategory=wechat, orderNo=NO1610250837438, payType=202}
    响应结果:{"message":"success","code":100,"data":{"orderNo":"NO1610250837438","payInfo":"https://wx.tenpay.com/cgi-bin/mmpayweb-bin/checkmweb?prepay_id=wx10115509234051b333adbe01d032b80000&package=2821154835"}}

    从响应的结果看,我们已经拿到了payInfo,也就是跳转到微信的url

    正常情况下,只要在非微信浏览器打开就可以了,但是微信那边对请求来源的域名做了校验,我们这里测试不是很方便,就不继续演示下去了

    因为实际开发中只要走到这一步,对于开发人员来说代码就已经合格了,如果只会支付不了,那多半是微信商户号上的配置有问题....

    学习我们就到这里,如果在实际开发中有问题可以咨询我,共同交流技术!

    4.总结

    1.这里只是给大家演示了统一下单和支付回调的代码,也是微信支付的核心代码,相信只要这调通了,其他的接口类似!

    2.为了更好让大家快速掌握微信支付,这篇博客已经做了配套的视频讲解,大家可以结合视频讲解学习,或者单独问我!

    完美!

  • 相关阅读:
    xftp无法用root账号登录问题
    jenkins上gradle打包
    jenkins登录后页面显示为空的问题
    sql 修改oracle数据库中某个字段的部分内容
    redis安装及报错处理
    Centos7 firewall-cmd not found
    ftp connect: No route to host 解决方案
    反向代理负载均衡之Apache
    centos7 openldap双主部署
    apache安装以及报错处理
  • 原文地址:https://www.cnblogs.com/newAndHui/p/14243012.html
Copyright © 2011-2022 走看看