实战版uniapp集成微信支付功能-v2版本

背景:近期公司的app要做一个支付的功能。因此研究了一下微信的支付文档。做完了这个功能。光看文档还是踩了狠多坑。总结一下自己完成这个功能的步骤:
主要是开发的步骤

一、微信支付文档介绍

1、微信支付开发者文档首页
https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pages/index.shtml
简单介绍一下申请账户的流程:
首先要注册微信平台开发者账号,然后登录商户平台,注册商户商户账号
在这里插入图片描述
接入微信,开通商户平台里面的支付功能。需要企业银行账户认证。
关键在于拿到这几个参数:(经理们已经申请好了)
在这里插入图片描述
2、接口文档
https://pay.weixin.qq.com/wiki/doc/apiv3/index.shtml
有以下几种基础的支付功能,我们使用的是app支付
在这里插入图片描述
用户使用支付的整个流程:
步骤一 用户进入我们的APP,选择商品下单、确认购买,进入支付环节,下单,在我们的系统生成订单,然后还需要调用微信的接口(携带的参数包括一个回调的接口),在微信的平台生成一个订单,这个时候就需要后端提供一个在微信平台预下单的接口,app用户调用这个接口,接口去请求微信平台生成预下单信息并且返回预下单的参数。app拿到这些参数调起微信支付。

步骤二 app拿到预下单后返回的参数后,app调出微信支付,用户点击后发起支付操作,进入到微信界面,调起微信支付,出现确认支付界面
在这里插入图片描述

步骤三 用户确认收款方和金额,点击立即支付后出现输入密码界面,可选择零钱或银行卡支付
在这里插入图片描述
步骤四 输入正确密码后,支付完成,用户端微信出现支付详情页面。
这里支付完成,在步骤一中预下单请求微信平台的参数中会携带一个回调的地址,这里支付完会立刻调用这个接口,因此我们的平台还要提供一个回调的接口,这个接口不能有参数,直接通过流获取到微信回调带回来的支付结果的通知数据。根据这些数据更新我们平台的订单状态。

步骤五 回跳到商户APP中,商户APP根据支付结果个性化展示订单处理结果。
在这里插入图片描述
整体的流程就是这样的。
增加知识:
1、目前我们使用的是v2版本的老接口。请求微信接口携带的参数和回调获取的参数都是xml包裹的,需要工具类来进行解析。
新版的V3 的接口是将V2 的接口请求的流程封装起来了。
2、连接V3的接口点击这里:https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay-1.shtml

二、上完整代码

1、添加依赖

<dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>2.7</version>
        </dependency>
        <dependency>
            <groupId>dom4j</groupId>
            <artifactId>dom4j</artifactId>
            <version>1.1</version>
        </dependency>

        <!-- 微信支付 -->
        <dependency>
            <groupId>com.github.wxpay</groupId>
            <artifactId>wxpay-sdk</artifactId>
            <version>0.0.3</version>
        </dependency>

        <dependency>
            <groupId>com.alipay.sdk</groupId>
            <artifactId>alipay-sdk-java</artifactId>
            <version>3.7.4.ALL</version>
        </dependency>

        <dependency>
            <groupId>com.ehe</groupId>
            <artifactId>ehe-pay-pojo</artifactId>
            <version>2.2.2-V3-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.assertj</groupId>
            <artifactId>assertj-core</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.mockito</groupId>
            <artifactId>mockito-core</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>com.squareup.okhttp</groupId>
            <artifactId>okhttp</artifactId>
            <version>2.7.5</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.58</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
        </dependency>

2、直接装微信支付的功能封装成在一个工具类中
预下单的接口:
2-1:contoller

    /**
     * 新增-在线下单
     * @param onlineContributionAdd 新增DTO
     * @return  VO
     */
    @ApiOperation(value = "app支付-在线下单", notes = "在线下单")
    @PostMapping("/")
    ResponseInfo<Map<String,String>> add(@ApiParam(value = "在线下单 新增DTO", required = true) @RequestBody @Validated OnlineContributionAdd onlineContributionAdd);

2-1:service

 @Override
    public synchronized ResponseInfo<Map<String,String>> add(OnlineContributionAdd onlineContributionAdd) {
        //一大堆业务逻辑 
        //创建订单支付失效redis
       // String key = RedisKeyDefinition.ORDER_PAY_STATUS_PERF + //onlineContribution.getOrderCode();
        // redis 保存有效期 30天
        //stringRedisTemplate.opsForValue().set(key, PayStatus.PAYING.getValue(), 7, TimeUnit.DAYS);
        //这里才是下单的入口
        Map<String,String> map= VxPayUtils.onlinePayOrder(onlineContribution.getOrderCode(),onlineContribution.getAmount(),onlineContribution.getSource());
        //try {
        //    saveOnlineContributionFail(map,onlineContribution);
        // }catch (Exception e){
        //     log.error("保存支付参数失败:{}",e.getMessage());
        // }
        return ResponseInfo.ofCreate(map);
    }

2-2: 支付工具类

import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.ehe.elder.config.WxPayConfig;
import com.ehe.elder.enumeration.ElderConstant;
import com.squareup.okhttp.*;
import lombok.extern.slf4j.Slf4j;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;

import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.math.BigDecimal;
import java.net.InetAddress;
import java.net.URLEncoder;
import java.net.UnknownHostException;
import java.util.*;
import java.util.concurrent.TimeUnit;

@Slf4j
public class VxPayUtils {

    private static final MediaType CONTENT_TYPE = MediaType.parse("application/json");

    /**
     * 读超时时间(秒)
     */
    private static final int READ_TIMEOUT = 30;

    /**
     * 写超时时间(秒)
     */
    private static final int WRITE_TIMEOUT = 30;

    /**
     * 连接超时时间(秒)
     */
    private static final int CONN_TIMEOUT = 30;

    /**
     * 重试次数
     */
    private static final int MAX_RETRY = 3;


    /** 预支付订单
     *  <xml>
     *    <appid>wx2421b1c4370ec43b</appid>
     *    <attach>支付测试</attach>
     *    <body>APP支付测试</body>
     *    <mch_id>10000100</mch_id>
     *    <nonce_str>1add1a30ac87aa2db72f57a2375d8fec</nonce_str>
     *    <notify_url>https://wxpay.wxutil.com/pub_v2/pay/notify.v2.php</notify_url>
     *    <out_trade_no>1415659990</out_trade_no>
     *    <spbill_create_ip>14.23.150.211</spbill_create_ip>
     *    <total_fee>1</total_fee>
     *    <trade_type>APP</trade_type>
     *    <sign>0CB01533B8C1EF103065174F50BCA001</sign>
     *  </xml>
     *
     * @param orderCode
     * @param totalFee
     * @return
     */
    public static Map<String, String> onlinePayOrder(String orderCode, BigDecimal totalFee, String description) {
        Map<String, String> reqMap = new HashMap<String, String>();
        reqMap.put("appid", WxPayConfig.getAppID());
        reqMap.put("mch_id", WxPayConfig.getMchID());
        reqMap.put("nonce_str", getRandomString(32));
        reqMap.put("body", description);
        //商户系统内部的订单号,
        reqMap.put("out_trade_no", orderCode);
        //订单总金额,单位为分
        reqMap.put("total_fee", changeToFen(totalFee.doubleValue()));
        //用户端实际ip
        reqMap.put("spbill_create_ip", getHostIp());
        //通知地址
        reqMap.put("notify_url", WxPayConfig.getNotifyUrl());
        //交易类型
        reqMap.put("trade_type", WxPayConfig.TRADE_TYPE.APP);
        reqMap.put("sign", getSign(reqMap));
        String reqStr = creatXml(reqMap);
        System.out.println("请求参数:"+reqStr);
        String retStr = postHtpps(WxPayConfig.getUnifiedorderURL(), reqStr);

        Map<String,String> resultMap = getInfoByXml(retStr);
        //将返回的xml转为map
        String prepayId = resultMap.getOrDefault("prepay_id", "");

        String return_code = resultMap.getOrDefault("return_code", "");
        if(return_code != null && ElderConstant.SUCCESS.equals(return_code)  && prepayId != null && !"".equals(prepayId)){
            log.info(" prepayId  [ {} ]",prepayId);
            //APP端调起支付的参数列表-要返回给app端的支付参数
            Map<String, String> paraMapApp = new HashMap<>();
            //微信开放平台审核通过的应用APPID
            paraMapApp.put("appid",WxPayConfig.getAppID());
            //	微信支付分配的商户号
            paraMapApp.put("partnerid",WxPayConfig.getMchID());
            paraMapApp.put("prepayid",prepayId);
            paraMapApp.put("package",WxPayConfig.getPackage());
            paraMapApp.put("noncestr",getRandomString(32));
            String timeStamp = String.valueOf(getSecondTimestamp(new Date()));
            paraMapApp.put("timestamp",timeStamp);

            String stringSignTempApp = formatUrlMap(paraMapApp,false,false);
            stringSignTempApp = stringSignTempApp + "&key=" + WxPayConfig.getKey();
            log.info("stringSignTempApp [ {} ]",stringSignTempApp);
            //得到app支付签名
            String signApp = MD5Utils.MD5Encoding(stringSignTempApp).toUpperCase();
            paraMapApp.put("sign",signApp);
            log.info("返回给app的参数 [ {} ]",paraMapApp);
            return paraMapApp;
        }else {
            log.info("获取prepay_id失败 [ {} ]",resultMap.getOrDefault("return_msg",""));
        }
        return null;
    }

    /**
     * 方法用途: 对所有传入参数按照字段名的Unicode码从小到大排序(字典序),并且生成url参数串<br>
     * 实现步骤: <br>
     * @param paraMap    要排序的Map对象
     * @param urlEncode  是否需要URLENCODE
     * @param keyToLower 是否需要将Key转换为全小写
     *                   true:key转化成小写,false:不转化
     * @return
     */
    public static String formatUrlMap(Map<String, String> paraMap, boolean urlEncode, boolean keyToLower) {
        String buff = "";
        Map<String, String> tmpMap = paraMap;
        try {
            List<Map.Entry<String, String>> infoIds = new ArrayList<Map.Entry<String, String>>(tmpMap.entrySet());
            // 对所有传入参数按照字段名的 ASCII 码从小到大排序(字典序)
            Collections.sort(infoIds, new Comparator<Map.Entry<String, String>>() {
                @Override
                public int compare(Map.Entry<String, String> o1, Map.Entry<String, String> o2) {
                    return (o1.getKey()).toString().compareTo(o2.getKey());
                }
            });
            // 构造URL 键值对的格式
            StringBuilder buf = new StringBuilder();
            for (Map.Entry<String, String> item : infoIds) {
                if (StringUtils.isNotBlank(item.getKey())) {
                    String key = item.getKey();
                    String val = item.getValue();
                    if (urlEncode) {
                        val = URLEncoder.encode(val, "utf-8");
                    }
                    if (keyToLower) {
                        buf.append(key.toLowerCase() + "=" + val);
                    } else {
                        buf.append(key + "=" + val);
                    }
                    buf.append("&");
                }

            }
            buff = buf.toString();
            if (buff.isEmpty() == false) {
                buff = buff.substring(0, buff.length() - 1);
            }
        } catch (Exception e) {
            return null;
        }
        return buff;
    }


    /**
     * 获取精确到秒的时间戳 10 位数
     *
     * @return
     */
    public static int getSecondTimestamp(Date date) {
        if (null == date) {
            return 0;
        }
        String timestamp = String.valueOf(date.getTime());
        int length = timestamp.length();
        if (length > 3) {
            return Integer.valueOf(timestamp.substring(0, length - 3));
        } else {
            return 0;
        }
    }

    /**
     * 关闭订单
     * @param orderId  商户自己的订单号
     * @return
     */
    public static Map<String, String> closeOrder(String orderId){
        Map<String, String> reqMap = new HashMap<String, String>();
        reqMap.put("appid", WxPayConfig.getAppID());
        reqMap.put("mch_id", WxPayConfig.getMchID());
        reqMap.put("nonce_str", getRandomString(32));
        reqMap.put("out_trade_no", orderId);
        //商户系统内部的订单号,
        reqMap.put("sign", getSign(reqMap));

        String reqStr = creatXml(reqMap);
        String retStr = postHtpps(WxPayConfig.getCloseOrderURL(), reqStr);
        return getInfoByXml(retStr);
    }


    /**
     * 查询订单
     * @param orderId 商户自己的订单号
     * @return
     */
    public static Map<String,String> getOrder(String orderId){
        Map<String, String> reqMap = new HashMap<String, String>();
        reqMap.put("appid", WxPayConfig.getAppID());
        reqMap.put("mch_id", WxPayConfig.getMchID());
        reqMap.put("nonce_str", getRandomString(32));
        //商户系统内部的订单号
        reqMap.put("out_trade_no", orderId);
        reqMap.put("sign", getSign(reqMap));

        String reqStr = creatXml(reqMap);
        String retStr = postHtpps(WxPayConfig.getOrderQueryURL(), reqStr);
        return getInfoByXml(retStr);
    }


    /**
     * 退款:需要商行平台的证书
     * @param orderId  商户订单号
     * @param refundId  退款单号
     * @param totralFee 总金额(分)
     * @param refundFee 退款金额(分)
     * @param opUserId 操作员ID
     * @return
     */
    public static Map<String, String> refundWei(String orderId,String refundId,String totralFee,String refundFee,String opUserId){
        Map<String, String> reqMap = new HashMap<String, String>();
        reqMap.put("appid", WxPayConfig.getAppID());
        reqMap.put("mch_id", WxPayConfig.getMchID());
        reqMap.put("nonce_str", getRandomString(32));
        //商户系统内部的订单号
        reqMap.put("out_trade_no", orderId);
        //商户退款单号
        reqMap.put("out_refund_no", refundId);
        //总金额
        reqMap.put("total_fee", totralFee);
        //退款金额
        reqMap.put("refund_fee", refundFee);
        //操作员
        reqMap.put("op_user_id", opUserId);
        reqMap.put("sign", getSign(reqMap));
        String requestParam = creatXml(reqMap);
        String retStr = "";
        try{
            //retStr = postHttplientNeedSSL(WxPayConfig.getOrderRefundURL(), requestParam, WxPayConfig.refund_file_path, orderRefundURL.MchId);
        }catch(Exception e){
            e.printStackTrace();
            return null;
        }
        return getInfoByXml(retStr);
    }


    /**
     * 退款查询
     * @param refundId  退款单号
     * @return
     */
    public static Map<String, String> getRefundWeiInfo(String refundId){
        Map<String, String> reqMap = new HashMap<String, String>();
        reqMap.put("appid", WxPayConfig.getAppID());
        reqMap.put("mch_id", WxPayConfig.getMchID());
        reqMap.put("nonce_str", getRandomString(32));
        reqMap.put("out_refund_no", refundId); //商户退款单号
        reqMap.put("sign", getSign(reqMap));

        String reqStr = creatXml(reqMap);
        String retStr = postHtpps(WxPayConfig.getOrderRefundQueryURL(), reqStr);
        return getInfoByXml(retStr);
    }

    /**
     * 传入map  生成头为XML的xml字符串,例:<xml><key>123</key></xml>
     * @param requestParamMap
     * @return
     */
    public static String creatXml(Map<String, String> requestParamMap){
        Set<String> set = requestParamMap.keySet();
        StringBuffer stringBuffer = new StringBuffer();
        stringBuffer.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
        stringBuffer.append("<xml>");
        for(String key : set){
            stringBuffer.append("<"+key+">").append(requestParamMap.get(key)).append("</"+key+">");
        }
        stringBuffer.append("</xml>");
        return stringBuffer.toString();
    }

    /**
     * 得到加密值
     * @param map
     * @return
     */
    public static String getSign(Map<String, String> map){
        String[] keys = map.keySet().toArray(new String[0]);
        Arrays.sort(keys);
        StringBuffer reqStr = new StringBuffer();
        for(String key : keys){
            String v = map.get(key);
            if(v != null && ! ElderConstant.NULL.equals(v)){
                reqStr.append(key).append("=").append(v).append("&");
            }
        }
        reqStr.append("key").append("=").append(WxPayConfig.getKey());

        return WeiMd5.encode(reqStr.toString()).toUpperCase();
    }

    /**
     * 得到10 位的时间戳
     * 如果在JAVA上转换为时间要在后面补上三个0
     * @return
     */
    public static String getTenTimes(){
        String t = System.currentTimeMillis()+"";
        t = t.substring(0, t.length()-3);
        return t;
    }

    /**
     * 得到随机字符串
     * @return
     */
    public static String getRandomString(int length){
        length = 32;
        String str = "jklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
        Random random = new Random();
        StringBuffer sb = new StringBuffer();

        for(int i = 0; i < length; ++i){
            int number = random.nextInt(62);//[0,62)
            sb.append(str.charAt(number));
        }
        return sb.toString();
    }

    /**
     * 得到本地机器的IP
     * @return
     */
    private static String getHostIp(){
        String ip = "";
        try{
            ip = InetAddress.getLocalHost().getHostAddress();
        }catch(UnknownHostException e){
            log.error("pay:{}",e.getMessage());
            e.printStackTrace();
        }
        return ip;
    }

    public static Map<String, String> getInfoByXml(String xmlStr){
        try{
            Map<String, String> m = new HashMap<String, String>();
            Document d = DocumentHelper.parseText(xmlStr);
            Element root = d.getRootElement();
            for ( Iterator<?> i = root.elementIterator(); i.hasNext(); ) {
                Element element = (Element) i.next();
                String name = element.getName();
                if(!element.isTextOnly()){
                    //不是字符串 跳过。确定了微信放回的xml只有根目录
                    continue;
                }else{
                    m.put(name, element.getTextTrim());
                }
            }
            //对返回结果做校验.去除sign 字段再去加密
            String retSign = m.get("sign");
            m.remove("sign");
            String rightSing = getSign(m);
            if(rightSing.equals(retSign)){
                return m;
            }
        }catch(DocumentException e){
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 将金额转换成分
     * @param fee 元格式的
     * @return 分
     */
    public static String changeToFen(Double fee){
        String priceStr = "";
        if(fee != null){
            //价格变为分
            int p = (int)(fee * 100);
            priceStr = Integer.toString(p);
        }
        return priceStr;
    }

    public static String postHtpps(String url,String param){
        Request request = new Request.Builder()
                .url(url)
                .post(RequestBody.create(CONTENT_TYPE, param))
                .build();
        Response response = null;
        try {
            response = getHttpClient().newCall(request).execute();
        } catch (IOException e) {
            e.printStackTrace();
        }
        String result = null;
        try {
            result = response.body().string();
            log.info("http请求公共方法接收返回结果:"+result);
        } catch (IOException e) {
            e.printStackTrace();
        }
        Integer code = response.code();
        if (code != 200) {
            //接入帐号异常
            throw new RuntimeException(response.message());
        }
        return result;
    }

    /**
     * 获取 http client
     *
     * @return
     */
    private static OkHttpClient getHttpClient() {
        OkHttpClient client = new OkHttpClient();
        client.setConnectTimeout(CONN_TIMEOUT, TimeUnit.SECONDS);
        client.setReadTimeout(READ_TIMEOUT, TimeUnit.SECONDS);
        client.setWriteTimeout(WRITE_TIMEOUT, TimeUnit.SECONDS);
        return client;
    }
}

2-3:配置类

import lombok.Data;
import org.springframework.context.annotation.Configuration;

import java.io.InputStream;

@Data
@Configuration
public class WxPayConfig{

    /**
     * 公众账号ID
     */
    private static String appid = "-------------------------------";
    /**
     * 商户号
     */
    private static String mchid = "-------------------------------";
    /**
     * 合作key
     */
    private static String key = "----------------------------------";

    /**
     * 支付回调地址-本地环境地址
     */
    private static String notifyURL_local = "http://111.111.111:8080/api/dlele/online/notify";

    /**
     * 支付回调地址-测试环境地址
     */
    private static String notifyURL_dev = "http://111.111.111:8080/api/notify";

    /**
     * 支付回调地址-正式环境地址
     */
    private static String notifyURL_prod = "http://111.111.111:8080/der/onion/notify";


    private static String notifySuccess = "<xml>" + "<return_code><![CDATA[SUCCESS]]></return_code>" + "<return_msg><![CDATA[OK]]></return_msg>" + "</xml> ";

    private static String notifyFailed = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>" + "<return_msg><![CDATA[报文为空]]></return_msg>" + "</xml> ";

    /**
     * 证书绝对路径
     */
    private static String certPath;
    /**
     * 证书密码
     */
    private static String certPassword;
    /**
     * 统一下单接口链接
     */
    private static String unifiedorderURL = "https://api.mch.weixin.qq.com/pay/unifiedorder";
    /**
     * 企业付款接口链接
     */
    private static String enterprisePayURL = "https://api.mch.weixin.qq.com/mmpaymkttransfers/promotion/transfers";
    /**
     * 单笔订单查询接口
     */
    private static String orderQueryURL = "https://api.mch.weixin.qq.com/pay/orderquery";
    /**
     * 申请退款接口
     */
    private static String orderRefundURL = "https://api.mch.weixin.qq.com/secapi/pay/refund";
    /**
     * 退款查询接口
     */
    private static String orderRefundQueryURL = "https://api.mch.weixin.qq.com/pay/refundquery";
    /**
     * 关闭订单接口
     */
    private static String closeOrderURL = "https://api.mch.weixin.qq.com/pay/closeorder";
    /**
     * 转账接口
     */
    private static String transfersURL = "https://api.mch.weixin.qq.com/mmpaymkttransfers/promotion/transfers";
    /**
     * 转账查询接口
     */
    private static String transfersQueryURL = "https://api.mch.weixin.qq.com/mmpaymkttransfers/gettransferinfo";

    /**
     * HTTP(S) 连接超时时间,单位毫秒
     */
    private int httpConnectTimeoutMs = 8000;

    /**
     * HTTP(S) 读数据超时时间,单位毫秒
     */
    private int httpReadTimeoutMs = 10000;

    private static String PACKAGE = "Sign=WXPay";

    public static String getPackage(){
        return PACKAGE;
    }

    public static String getOrderRefundQueryURL(){
        return orderRefundQueryURL;
    }

    public static String getNotifyUrl(){
        return notifyURL_local;
    }

    public static String getOrderQueryURL(){
        return orderQueryURL;
    }

    public static String getUnifiedorderURL() {
        return unifiedorderURL;
    }

    public static String getCloseOrderURL() {
        return closeOrderURL;
    }

    public static String getOrderRefundURL(){
        return orderRefundURL;
    }

    public static String getAppID() {
        return appid = appid;
    }

    public static String getMchID() {
        return mchid = mchid;
    }

    public static String getKey() {
        return key = key;
    }


    public InputStream getCertStream() {
        InputStream certStream  =getClass().getClassLoader().getResourceAsStream(certPath);
        return certStream;
    }

    public int getHttpConnectTimeoutMs() {
        return httpConnectTimeoutMs;
    }

    public int getHttpReadTimeoutMs() {
        return httpReadTimeoutMs;
    }


    public interface TRADE_TYPE {
        String APP = "APP";
    }

}

2-4:md5加密工具

import java.security.MessageDigest;

public class MD5Utils {

    private static final char HEX_DIGITS[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};

    /*
     * 先转为utf-8
     * */
    public static String MD5Encoding(String s) {
        byte[] btInput = null;
        try {
            btInput = s.getBytes("UTF-8");
        }catch (Exception e){
        }
        return MD5(btInput, 32);
    }

    public static String MD5(String s) {
        byte[] btInput = s.getBytes();
        return MD5(btInput, 32);
    }

    public static String MD5_16(String str) {
        byte[] btInput = str.getBytes();
        return MD5(btInput, 16);
    }

    private static String MD5(byte[] btInput, int length) {
        try {
            // 获得MD5摘要算法的 MessageDigest 对象
            MessageDigest mdInst = MessageDigest.getInstance("MD5");
            // MessageDigest mdInst = MessageDigest.getInstance("SHA-1");
            // 使用指定的字节更新摘要
            mdInst.update(btInput);
            // 获得密文
            byte[] md = mdInst.digest();
            // 把密文转换成十六进制的字符串形式
            int j = md.length;
            char str[] = new char[j * 2];
            int k = 0;
            for (byte byte0 : md) {
                str[k++] = HEX_DIGITS[byte0 >>> 4 & 0xf];
                str[k++] = HEX_DIGITS[byte0 & 0xf];
            }
            String result = new String(str);
            return length == 16 ? result.substring(8, 24) : result;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
}

import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

public class WeiMd5 {

    // 全局数组
    private final static String[] STR_DIGITS = { "0", "1", "2", "3", "4", "5",
            "6", "7", "8", "9", "a", "b", "c", "d", "e", "f" };

    // 返回形式为数字跟字符串
    private static String byteToArrayString(byte bByte) {
        int iRet = bByte;
        // System.out.println("iRet="+iRet);
        if (iRet < 0) {
            iRet += 256;
        }
        int iD1 = iRet / 16;
        int iD2 = iRet % 16;
        return STR_DIGITS[iD1] + STR_DIGITS[iD2];
    }


    // 转换字节数组为16进制字串
    private static String byteToString(byte[] bByte) {
        StringBuffer sBuffer = new StringBuffer();
        for (int i = 0; i < bByte.length; i++) {
            sBuffer.append(byteToArrayString(bByte[i]));
        }
        return sBuffer.toString();
    }

    public static String encode(String strObj) {
        String resultString = null;
        try {
            resultString = new String(strObj);
            MessageDigest md = MessageDigest.getInstance("MD5");
            // md.digest() 该函数返回值为存放哈希值结果的byte数组
            try{
                resultString = byteToString(md.digest(strObj.getBytes("UTF-8")));
            }catch(UnsupportedEncodingException e){
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        } catch (NoSuchAlgorithmException ex) {
            ex.printStackTrace();
        }
        return resultString;
    }

3、查询订单的接口(不需要证书)
3-1:controller

    /**
     * orderCode 查询
     * @param orderCode
     * @return VO
     */
    @ApiOperation(value = "根据订单号查询微信平台订单详情", notes = "orderCode 查询")
    @GetMapping("/findOrderStatus/{orderCode}")
    Map<String,String> findOrderStatus(@ApiParam(value = "orderCode", required = true) @PathVariable String orderCode);

3-2:service:

   /**
     * 根据订单号获取微信端订单状态
     * @param orderCode
     * @return
     */
    @Override
    public Map<String,String> findOrderStatus(String orderCode) {
        return VxPayUtils.getOrder(orderCode);
    }

3-3:返回的参数
返回支付成功的数据

{
  "transaction_id": "4200001136202108121973910876",
  "nonce_str": "vezB3i2l2WKStdbS",
  "trade_state": "SUCCESS",
  "bank_type": "OTHERS",
  "openid": "oj7EI66VJi7gXabPQ3Rby91S5SdQ",
  "return_msg": "OK",
  "fee_type": "CNY",
  "mch_id": "1609852809",
  "cash_fee": "1",
  "out_trade_no": "2021081200000000000000000006",
  "cash_fee_type": "CNY",
  "appid": "wx2e4196f713eac31a",
  "total_fee": "1",
  "trade_state_desc": "支付成功",
  "trade_type": "APP",
  "result_code": "SUCCESS",
  "attach": "",
  "time_end": "20210812154656",
  "is_subscribe": "N",
  "return_code": "SUCCESS"
}

未支付的订单查询的数据

{
  "nonce_str": "2WvDtB7ckmXjQ6a6",
  "device_info": "",
  "trade_state": "NOTPAY",
  "out_trade_no": "2021081200000000000000000011",
  "appid": "wx2e4196f713eac31a",
  "total_fee": "1",
  "trade_state_desc": "订单未支付",
  "return_msg": "OK",
  "result_code": "SUCCESS",
  "mch_id": "1609852809",
  "return_code": "SUCCESS"
}

订单不存在返回的数据

{
  "nonce_str": "yfyjuq7eyAECIfSL",
  "appid": "wx2e4196f713eac31a",
  "err_code": "ORDERNOTEXIST",
  "return_msg": "OK",
  "result_code": "FAIL",
  "err_code_des": "订单不存在",
  "mch_id": "1609852809",
  "return_code": "SUCCESS"
}

(我们用的是jdk11)

4、支付回调的接口
4-1:controller

    /**
     * 微信支付回调地址
     * @return 在线捐款 VO
     */
    @ApiOperation(value = "微信支付回调地址", notes = "微信支付回调地址")
    @PostMapping(value = "/notify" ,produces = "application/json;charset=UTF-8")
    String add(HttpServletRequest request) throws Exception;

4-2:service

   /**
     * 交易成功判断条件:return_code和result_code都为SUCCESS且trade_type为MICROPAY(二维码支付)
     * @param request
     * @return
     */
    @Override
    public synchronized String add(HttpServletRequest request) throws Exception {
        log.info("====微信回调===========日期:{}", TimeUtils.ldtToStandardString(LocalDateTime.now()));
        try {
            //读取参数
            BufferedReader reader = request.getReader();
            String line = "";
            StringBuffer inputString = new StringBuffer();
            while ((line = reader.readLine()) != null) {
                inputString.append(line);
            }
            reader.close();
            //解析xml成map
            Map<String,String> map= WXPayUtil.xmlToMap(inputString.toString());
            //判断签名是否正确
            Boolean isSignTrue = WXPayUtil.isSignatureValid(map, WxPayConfig.getKey());
            if (!isSignTrue){
                log.error("回调验证失败,签名结果:{},时间:{}",(isSignTrue == true ? "验签成功" : "验签失败"),TimeUtils.ldtToStandardString(LocalDateTime.now()));
                return "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>" + "<return_msg><![CDATA[支付回调验签失败]]></return_msg>" + "</xml>";
            }
            //校验回调结果code
            if(!ElderConstant.SUCCESS.equals(map.get(ElderConstant.WX_NOTICEFY.RETURN_CODE)) || !ElderConstant.SUCCESS.equals(map.get(ElderConstant.WX_NOTICEFY.RESULT_CODE))){
                log.error("回调验证失败,结果:return_code:{},result_code:{}",map.get(ElderConstant.WX_NOTICEFY.RETURN_CODE),map.get(ElderConstant.WX_NOTICEFY.RESULT_CODE));
                return "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>" + "<return_msg><![CDATA[支付回调结果失败,回调参数fail]]></return_msg>" + "</xml>";
            }
            String outTradeNo=map.get(ElderConstant.WX_NOTICEFY.OUT_TRADE_NO);
            if (StringUtils.isEmpty(outTradeNo)){
                log.error("回调验证失败,结果:{}","订单号为空");
                return "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>" + "<return_msg><![CDATA[订单号为空]]></return_msg>" + "</xml>";
            }
            OnlineContribution onlineContribution = onlineContributionService.selectOne(this.onlineContributionService.createQueryWrapper().lambda().eq(OnlineContribution::getOrderCode,outTradeNo));
            if (onlineContribution == null || Objects.equals((onlineContribution.getAmount().multiply(new BigDecimal(100))).toString(),map.get("total_fee"))){
                log.error("回调验证失败(注意检查订单数据是否安全),结果:系统支付金额:{},回调返回金额:{}",onlineContribution.getAmount().toString(),map.get(ElderConstant.WX_NOTICEFY.RESULT_CODE));
                return "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>" + "<return_msg><![CDATA[支付回调结果失败,下单金额与回调金额不一致]]></return_msg>" + "</xml>";
            }
            //处理业务开始-账单的金额要和支付回调的金额保持一致
            //保存微信支付回调记录数据
            saveOnlineContributionNotify(map,isSignTrue);
            //返回之前异步请求一次微信订单,查询订单状态并更新支付结果
            //Map<String,String> mapAsyncCheckStatus = new HashMap<>(5);
            //mapAsyncCheckStatus.put("orderCode",onlineContribution.getOrderCode());
            //stringRedisTemplate.convertAndSend(RedisKeyConstant.appendElderPrefix(RedisKeyConstant.REDIS_LISTENER_TOPIC_PAY),new Gson().toJson(map));
        }catch (Exception e){
            log.error("微信支付回调失败:{}"+e.getMessage());
            return  "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>" + "<return_msg><![CDATA[回调异常]]></return_msg>" + "</xml> ";
        }
        log.info("微信支付回调成功:时间{}",TimeUtils.ldtToStandardString(LocalDateTime.now()));

        return "<xml>" + "<return_code><![CDATA[SUCCESS]]></return_code>" + "<return_msg><![CDATA[OK]]></return_msg>" + "</xml>";
    }

三、定时任务刷新订单状态,防止订单支付后回调失败导致状态没有及时更新

定时任务看用的啥定时任务了。我们直接用的spring提供的 的schedule。

四、V3版本接口

https://github.com/wechatpay-apiv3/wechatpay-apache-httpclient

  • 0
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
对于在uniapp中使用微信支付V2,你可以按照以下步骤进行操作: 1. 在uniapp项目的根目录中,使用命令行安装`uni-pay`插件: ```bash npm install uni-pay --save ``` 2. 在`manifest.json`文件中添加以下配置: ```json "permission": { "scope.userLocation": { "desc": "你的位置信息将用于小程序支付" } } ``` 3. 在需要支付的页面中,引入并初始化`uni-pay`插件: ```javascript import uniPay from 'uni-pay' export default { data() { return { payParams: {}, // 支付参数 payProvider: '', // 支付提供商,例如:'wxpay' } }, onReady() { uniPay.init({ provider: this.payProvider, options: { appId: '微信小程序的AppID', mchId: '商户号', apiKey: 'API密钥', }, success: (res) => { // 支付成功回调 console.log('支付成功', res) }, fail: (err) => { // 支付失败回调 console.error('支付失败', err) }, }) }, methods: { // 发起支付 pay() { uniPay.requestPayment({ provider: this.payProvider, orderInfo: this.payParams, success: (res) => { // 支付成功回调 console.log('支付成功', res) }, fail: (err) => { // 支付失败回调 console.error('支付失败', err) }, }) }, }, } ``` 4. 在调用支付接口前,需要先获取支付参数,可以通过与后端服务器交互获取。获取到支付参数后,将其赋值给`payParams`和`payProvider`,然后调用`pay()`方法即可发起支付。 请注意,以上步骤中的微信支付相关参数(AppID、商户号、API密钥)需要根据你的实际情况进行替换。另外,如果你的uniapp支持多端编译,需要根据当前运行的平台选择不同的支付提供商,如微信小程序使用'wxpay',H5使用'wxpayH5'等。 希望以上信息对你有帮助!如有其他问题,请随时提问。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值