Java微信支付开发 | 公众号发红包、企业付款、微信网页支付、微信小程序支付(附源码!!!) - Go语言中文社区

Java微信支付开发 | 公众号发红包、企业付款、微信网页支付、微信小程序支付(附源码!!!)


本文主要介绍Java微信公众号微信小程序支付和提现相关的开发流程。请注意,支付是用户向微信商户付款,提现是微信商户向用户付款或者发送红包

阅读本文前需掌握微信公众平台开发、微信小程序开发、微信支付等相关基础知识。

一、公众号提现

公众号中提现有2种形式,第一种是企业付款,另一种是发送红包。下面对这两种形式分别介绍。

1.1 公众号企业付款
企业付款为企业提供付款至用户零钱的能力,支持通过API接口付款,或通过微信支付商户平台(pay.weixin.qq.com)网页操作付款。我们要介绍的肯定是API接口付款。

1.1.1 准备工作

  1. 准备一个服务号或订阅号(不确定是否需要开通微信认证);
  2. 准备一个微信商户,该商户需开通企业付款产品权限;
  3. 微信商户需要与公众号绑定。

1.1.2 开发
企业付款微信API地址:https://pay.weixin.qq.com/wiki/doc/api/tools/mch_pay.php?chapter=14_2

废话不多说,直接上源码(包含注释,一目了然):
 

/**
 * 企业付款
 * @param publicAccountOpenId 用户相对于公众号的openId
 * @param money 付款金额 单位是分
 */
public static Map<String,Object> EnterprisePayment(String publicAccountOpenId,int money){
    Map<String,Object> resultMap = new HashMap<>();
    String u = getUUID();//这里是通过生成一个32位的UUID保证商户订单号不重复
    JSONObject jsonObj = new JSONObject();
    jsonObj.put("mch_appid", SystemContant.PUBLIC_ACCOUNT_APPID);//公众号appId
    jsonObj.put("mchid", SystemContant.MCH_ID);//商户号
    jsonObj.put("nonce_str",u);//随机数
    jsonObj.put("partner_trade_no", u);//商户订单号 这里为了方便,随机数和商户订单号采用相同的值
    jsonObj.put("openid", publicAccountOpenId);//用户相对于公众号的openId
    jsonObj.put("check_name", "NO_CHECK");//是否校验用户姓名 如果校验,re_user_name参数需填写用户真实姓名
    jsonObj.put("amount", money);//付款金额
    jsonObj.put("desc", "奖励提现");//企业付款备注
    jsonObj.put("spbill_create_ip", SystemContant.SERVER_IP);//Ip地址 该IP同在商户平台设置的IP白名单中的IP没有关联,该IP可传用户端或者服务端的IP
    try {
        jsonObj.put("sign", paramSort(jsonObj));//根据微信参数排序及加密规则生成签名
    } catch (Exception e) {
        log.error("--提现参数排序失败---Cause By:"+e.getMessage()+"--publicAccountOpenId:"+publicAccountOpenId+"--money:"+money);
        resultMap.put("flag", false);
        resultMap.put("msg", "系统内部错误");
        resultMap.put("errCodeDes", "提现参数排序失败");
        return resultMap;
    }
    String xmlStr = ssl(SystemContant.DRAW_CASH_WECHAT_URL,jsonToXML(jsonObj));//post方式携带证书调用微信接口
    try {
        JSONObject jsonObjResult = xmltoJson(xmlStr);//将微信返回的xml转化成json
        JSONObject jsonObjXML = jsonObjResult.getJSONObject("xml");
        String returnCode = jsonObjXML.getString("return_code");//通信标识
        if(StringUtils.equals("FAIL", returnCode)){//通信失败或签名错误
            log.error("调用微信支付通信失败---"+"--xmlStr:"+xmlStr+"--publicAccountOpenId:"+publicAccountOpenId+"--money:"+money);
            resultMap.put("flag", false);
            resultMap.put("msg", "系统内部错误");
            resultMap.put("errCodeDes", "调用微信支付通信失败");
            return resultMap;
        }
        String resultCode = jsonObjXML.getString("result_code");
        if(StringUtils.equals("FAIL", resultCode)){//状态未明确
            //resultCode返回值SUCCESS/FAIL,注意:当状态为FAIL时,存在业务结果未明确的情况。如果如果状态为FAIL,请务必关注错误代码(err_code字段),通过查询查询接口确认此次付款的结果。
            String errCodeDes = jsonObjXML.getString("err_code_des");//失败原因
            //errCode为错误码信息,注意:出现未明确的错误码时(SYSTEMERROR等),请务必用原商户订单号重试,或通过查询接口确认此次付款的结果。
            String errCode = jsonObjXML.getString("err_code");//失败状态码
            resultMap.put("flag", false);
            resultMap.put("errCode", errCode);
            resultMap.put("errCodeDes", errCodeDes);
            resultMap.put("msg", "付款失败。后台人工处理中,请您耐心等候");
            return resultMap;
        }else{
            //付款成功
            resultMap.put("flag", true);
            resultMap.put("partner_trade_no", jsonObjXML.getString("partner_trade_no"));//商户订单号
            resultMap.put("payment_no", jsonObjXML.getString("payment_no"));//微信付款订单号
            resultMap.put("msg", "提现成功");
            return resultMap;
        }
    } catch (Exception e) {
        log.error("微信支付解析微信返回xml失败---Cause By:"+e.getMessage()+"--xmlStr:"+xmlStr+"--publicAccountOpenId:"+publicAccountOpenId+"--money:"+money);
        resultMap.put("flag", false);
        resultMap.put("errCodeDes", "微信支付解析微信返回xml失败");
        resultMap.put("msg", "系统内部错误");
        return resultMap;
    }
}
/**
 * 按照微信API规则对参数排序并拼装字符串,最后对字符串MD5加密
 * @author fangw
 * @param jsonObj
 * @return
 * @throws UnsupportedEncodingException
 * @throws NoSuchAlgorithmException
 */
public static String paramSort(JSONObject jsonObj) throws NoSuchAlgorithmException, UnsupportedEncodingException{
    //声明一个数组,用于存放参数
    String[] arr=new String[jsonObj.keySet().size()];
    //将map中的参数存到数组中
    int num = 0;
    for(Object o : jsonObj.keySet()){
        arr[num] = String.valueOf(o);
        num++;
    }
    //排序(冒泡排序)
    for (int i = 0; i < arr.length - 1; i++) {// 外层循环控制排序趟数
        for (int j = 0; j < arr.length - 1 - i; j++) {// 内层循环控制每一趟排序多少次
            if (arr[j].compareTo(arr[j+1])>0) {
                String temp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = temp;
            }
        }
    }
    //拼装字符串
    StringBuffer sb = new StringBuffer();
    for(String s:arr){
        sb.append(s).append("=").append(jsonObj.get(s)).append("&");
    }
    String str = sb.append("key=").append(SystemContant.WECHAT_PAY_API_SECRET).toString();
    String sign=getMD5(str).toUpperCase();
    return sign;
}
/**
 * 生成MD5
 * @param str
 * @return
 */
public static String getMD5(String str) {
    try {
        // 生成一个MD5加密计算摘要
        MessageDigest md = MessageDigest.getInstance("MD5");
        // 计算md5函数
        md.update(str.getBytes());
        // digest()最后确定返回md5 hash值,返回值为8为字符串。因为md5 hash值是16位的hex值,实际上就是8位的字符
        // BigInteger函数则将8位的字符串转换成16位hex值,用字符串来表示;得到字符串形式的hash值
        String md5=new BigInteger(1, md.digest()).toString(16);
        //BigInteger会把0省略掉,需补全至32位
        return fillMD5(md5);
    } catch (Exception e) {
        throw new RuntimeException("MD5加密错误:"+e.getMessage(),e);
    }
}
/**
 * 填充MD5值
 * @param md5
 * @return
 */
public static String fillMD5(String md5){
    return md5.length()==32?md5:fillMD5("0"+md5);
}
 
/**
 * post请求微信接口,同时携带商户证书。需提前在商户平台上下载证书并存放到指定位置
 * @param url 微信企业付款接口url https://api.mch.weixin.qq.com/mmpaymkttransfers/promotion/transfers
 * @param data 请求接口携带的参数
 * @return
 */
public static String ssl(String url,String data){
    StringBuffer message = new StringBuffer();
    try {
        KeyStore keyStore  = KeyStore.getInstance("PKCS12");
        FileInputStream instream = new FileInputStream(new File("/usr/local/cert/apiclient_cert.p12"));
        keyStore.load(instream, SystemContant.MCH_ID.toCharArray());
        SSLContext sslcontext = SSLContexts.custom()
                .loadKeyMaterial(keyStore, SystemContant.MCH_ID.toCharArray())
                .build();
        SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
                sslcontext,
                new String[] { "TLSv1" },
                null,
                SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);
        CloseableHttpClient httpclient = HttpClients.custom()
                .setSSLSocketFactory(sslsf)
                .build();
        HttpPost httpost = new HttpPost(url);
        httpost.addHeader("Connection", "keep-alive");
        httpost.addHeader("Accept", "*/*");
        httpost.addHeader("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
        httpost.addHeader("Host", "api.mch.weixin.qq.com");
        httpost.addHeader("X-Requested-With", "XMLHttpRequest");
        httpost.addHeader("Cache-Control", "max-age=0");
        httpost.addHeader("User-Agent", "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0) ");
        httpost.setEntity(new StringEntity(data, "UTF-8"));
        CloseableHttpResponse response = httpclient.execute(httpost);
        try {
            HttpEntity entity = response.getEntity();
            if (entity != null) {
                BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(entity.getContent(),"UTF-8"));
                String text;
                while ((text = bufferedReader.readLine()) != null) {
                    message.append(text);
                }
            }
            EntityUtils.consume(entity);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            response.close();
        }
    } catch (Exception e1) {
        e1.printStackTrace();
    }
    return message.toString();
}
 
/**
 * 将json数据转化成XML字符串
 * @param json
 * @return
 */
public static String jsonToXML(JSONObject json) {
    XMLSerializer xmlSerializer = new XMLSerializer();
    // 根节点名称
    xmlSerializer.setRootName("xml");
    // 不对类型进行设置
    xmlSerializer.setTypeHintsEnabled(false);
    String xmlStr = "";
    xmlStr = xmlSerializer.write(json);
    return xmlStr;
}

主要依赖包如下:

import javax.net.ssl.SSLContext;
import java.io.File;
import java.io.FileInputStream;
import java.math.BigInteger;
import java.security.KeyStore;
import org.apache.http.conn.ssl.SSLContexts;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.HttpEntity;
import net.sf.json.JSONObject;
import net.sf.json.xml.XMLSerializer;
import java.security.MessageDigest;
import java.util.HashMap;
import java.util.Map;

1.1.3 注意点

  1. 需要根据微信参数排序、加密规则生成签名,并将签名放到post请求数据中;
  2. post方式请求微信企业付款接口时需携带证书,post请求数据需为xml字符串;
  3. 微信返回的result_code为FAIL时并不能说明付款失败,需要采用原商户订单号重试或调用接口查询付款结果。

1.1.4 效果图

1.2 公众号发放现金红包
商户可以向微信支付用户发放现金红包。用户领取红包后,资金到达用户微信支付零钱账户,和零钱包的其他资金有一样的使用出口;若用户未领取,资金将会在24小时后退回商户的微信支付账户中。

1.2.1 准备工作

  1. 准备一个服务号或订阅号(不确定是否需要开通微信认证);
  2. 准备一个微信商户,该商户需开通现金红包产品权限;
  3. 微信商户需要与公众号绑定。

1.2.2 开发
公众号发放现金红包API地址:https://pay.weixin.qq.com/wiki/doc/api/tools/cash_coupon.php?chapter=13_3&index=2

还是直接上源码和注释:

/**
 * 企业发送红包
 * @param publicAccountOpenId 用户公众号openId
 * @param money 红包金额 单位是分
 * @param wishStr 红包祝福语
 */
public static Map<String,Object> EnterpriseRedBag(String publicAccountOpenId,int money,String wishStr){
    Map<String,Object> resultMap = new HashMap<>();
    String u = getUUID();//生成一个32位的UUID用于随机数和商户订单号
    JSONObject jsonObj = new JSONObject();
    jsonObj.put("wxappid", SystemContant.PUBLIC_ACCOUNT_APPID);//公众号appId
    jsonObj.put("mch_id", SystemContant.MCH_ID);//商户ID
    jsonObj.put("nonce_str",u);//随机字符串
    jsonObj.put("mch_billno", System.currentTimeMillis()+u.substring(0, 12));//商户订单号
    jsonObj.put("send_name", "购即省");//红包发送者名称
    jsonObj.put("re_openid", publicAccountOpenId);//接受红包的用户openid,为用户在wxappid下的唯一标识
    jsonObj.put("total_amount", money);//红包金额
    jsonObj.put("total_num", 1);//红包发放总人数
    jsonObj.put("wishing", wishStr);//红包祝福语
    jsonObj.put("remark", "购物即省钱");//备注信息
    jsonObj.put("act_name", "奖励提现");//活动名称
    jsonObj.put("client_ip", SystemContant.SERVER_IP);//调用接口的机器Ip地址
    jsonObj.put("scene_id", "PRODUCT_2");//发放红包使用场景,红包金额大于200或者小于1元时必传
    try {
        jsonObj.put("sign", paramSort(jsonObj));//根据微信参数排序及加密规则生成签名
    } catch (Exception e) {
        log.error("--提现参数排序失败---Cause By:"+e.getMessage()+"--publicAccountOpenId:"+publicAccountOpenId+"--money:"+money);
        resultMap.put("flag", false);
        resultMap.put("msg", "系统内部错误");
        resultMap.put("errCodeDes", "提现参数排序失败");
        return resultMap;
    }
    String xmlStr = ssl(SystemContant.WECHAT_SEND_REDBAG_URL,jsonToXML(jsonObj));//post方式携带证书调用微信接口
    try {
        JSONObject jsonObjResult = xmltoJson(xmlStr);//将微信返回的xml转化成json
        JSONObject jsonObjXML = jsonObjResult.getJSONObject("xml");
        String returnCode = jsonObjXML.getString("return_code");//通信标识
        if(StringUtils.equals("FAIL", returnCode)){//通信失败或签名错误
            log.error("调用微信支付通信失败---"+"--xmlStr:"+xmlStr+"--publicAccountOpenId:"+publicAccountOpenId+"--money:"+money);
            resultMap.put("flag", false);
            resultMap.put("msg", "系统内部错误");
            resultMap.put("errCodeDes", "调用微信支付通信失败");
            return resultMap;
        }
        String resultCode = jsonObjXML.getString("result_code");//业务结果
        if(StringUtils.equals("FAIL", resultCode)){//状态未明确
            //请求查询接口确认状态信息
            //resultCode返回值SUCCESS/FAIL,注意:当状态为FAIL时,存在业务结果未明确的情况。如果如果状态为FAIL,请务必关注错误代码(err_code字段),通过查询查询接口确认此次付款的结果。
            String errCodeDes = jsonObjXML.getString("err_code_des");//失败原因
            String errCode = jsonObjXML.getString("err_code");//失败状态码
            resultMap.put("flag", false);
            resultMap.put("errCode", errCode);
            resultMap.put("errCodeDes", errCodeDes);
            resultMap.put("msg", "提现失败。后台人工处理中,请您耐心等候");
            return resultMap;
        }else{
            //付款成功
            resultMap.put("flag", true);
            resultMap.put("partner_trade_no", jsonObjXML.getString("mch_billno"));//商户订单号
            resultMap.put("payment_no", jsonObjXML.getString("send_listid"));//红包订单的微信单号
            resultMap.put("msg", "提现成功");
            return resultMap;
        }
    } catch (Exception e) {
        log.error("微信支付解析微信返回xml失败---Cause By:"+e.getMessage()+"--xmlStr:"+xmlStr+"--publicAccountOpenId:"+publicAccountOpenId+"--money:"+money);
        resultMap.put("flag", false);
        resultMap.put("errCodeDes", "微信支付解析微信返回xml失败");
        resultMap.put("msg", "系统内部错误");
        return resultMap;
    }
}

内部用到的方法及相关jar依赖已在上文给出。

1.2.3 注意点

  1. 需要根据微信参数排序、加密规则生成签名,并将签名放到post请求数据中;
  2. post方式请求微信企业付款接口时需携带证书,post请求数据需为xml字符串;
  3. 微信返回的result_code为FAIL时并不能说明付款失败,需要采用原商户订单号重试或调用接口查询付款结果;
  4. 如果需要发送小于1元或大于200元的红包需要在微信商户上申请开通,否则调用接口不成功;
  5. 现金红包发放后会以公众号消息的形式触达用户,不同情况下触达消息的形式会有差别,与用户关注公众号时长有关。

1.2.4 效果图

二、小程序提现

       小程序提现目前也是有2种形式,第一种形式是企业付款,第二种是发送现金红包。这里要注意,商家并不能直接向小程序用户发送红包,用户只有关注了绑定小程序的公众号才能以公众号的形式发送给用户现金红包。

2.1 小程序企业付款
小程序企业付款和公众号企业付款的开发代码是一样的,只是需要把mch_appid、openid等参数替换为小程序的对应参数值。代码这里不再赘述。

2.1.1 准备工作

  1. 准备一个微信小程序;
  2. 准备一个微信商户,该商户需开通企业付款产品权限;
  3. 微信商户需要与小程序绑定。

2.1.2 效果图
效果图和公众号企业付款效果图一致。

2.2 小程序发送红包
目前不支持商户向小程序用户直接发送红包,如果需要实现小程序发送红包的效果,那么可以有2种方式。

第一种是通过小程序前端效果图的方式来呈现。当用户在小程序中点击提现时,在小程序前端弹框实现一个“拆红包”的效果,当用户点击“拆”的时候,后台接口以企业付款的形式直接付款至用户零钱。这种方式效果极好。

第二种是通过公众号发送现金红包。这种方式需要开发者将小程序和公众号都绑定到微信开放平台,用于获取unionId。笔者所做的小程序因为需要向公众号引流,所以采用的是这种方式。首先我们在mysql中建一张表,表结构如下:

当小程序用户第一次访问小程序时,弹框获取授权,用于获取用户敏感信息:

<button class="btn-confirm" open-type="getUserInfo" lang="zh_CN" bindgetuserinfo="onGotUserInfo">确定</button>

用户点击确定之后在onGotUserInfo方法中对微信返回的敏感数据encryptedData进行解密,获取到unionId,进而将小程序openId和unionId保存到数据库中。当用户在小程序中点击提现时,因为表中的公众号openId不存在,所以需要提醒用户关注对应的公众号。在用户关注公众号时,通过用户的openId和公众号ACCESS_TOKEN作为参数,调用微信接口,可以获取到unionId。因为公众号和小程序都绑定到了微信开放平台上,所以它们的用户unionId是相同的。这样,就可以找到小程序和公众号两者的openId对应关系,小程序中点击提现的时候,可以将红包以公众号的形式发送给用户。

三、微信网页支付

微信网页支付在微信官方API中被称作微信JSAPI支付,应用场景如下:

商户已有H5商城网站,用户通过点击链接、点击公众号菜单或扫描二维码在微信内打开网页时,可以调用微信支付完成下单购买。

注:微信JSAPI支付是在微信浏览器内完成支付。如果是外部浏览器,那需要对接微信H5支付。

微信JSAPI支付官方文档:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=7_1

3.1 准备工作

  1. 准备一个已通过微信认证的公众号,同时保证该公众号有“微信支付”的权限
  2. 准备一个微信商户,该商户需具备JSAPI支付权限
  3. 微信商户需要与公众号绑定
  4. 在微信商户平台设置你的JSAPI支付目录,在微信公众平台设置授权域名

注:详见微信支付官方文档微信JSAPI支付开发步骤

3.2 开发
3.2.1 统一下单
在微信发起JSAPI支付之前,商户系统需要先调用统一下单接口在微信支付服务后台生成预支付交易单,返回正确的预支付交易会话标识后再按JSAPI场景生成交易串调起支付。

所以我们需要首先开发后台接口,完成统一下单,并返回订单详情扩展字符串、签名等信息。
 

/**
 * 完成统一下单,并返回订单详情扩展字符串、签名等信息
 * @param openId
 * @return
 * @throws Exception
 */
@RequestMapping(value="/payOrder")
@ResponseBody
public Map<String,Object> payOrder(String openId) throws Exception{
    Map<String,Object> resultMap = new HashMap<String, Object>();
    String u = RequestUtil.getUUID();//生成UUID,用于随机字符串、商户订单号
    JSONObject jsonObj = new JSONObject();
    jsonObj.put("appid", SystemContant.PUBLIC_ACCOUNT_APPID);//公众号appId
    jsonObj.put("mch_id", SystemContant.MCH_ID);//商户ID
    jsonObj.put("nonce_str",u);//随机字符串
    jsonObj.put("body", "银座汽贸洗车服务");//商品描述
    jsonObj.put("out_trade_no", u);//商户订单号
    jsonObj.put("spbill_create_ip", "xx.xx.xx.xx");//调用微信支付API的机器IP
    jsonObj.put("notify_url", "http://IP:port/Project/payCallBack");//异步接收微信支付结果通知的回调地址,通知url必须为外网可访问的url,不能携带参数。
    jsonObj.put("total_fee", Integer.parseInt(String.valueOf(SystemContant.PRETEST_MAP.get("cleanCarFee"))));//支付金额,单位是分
    jsonObj.put("trade_type", "JSAPI");//交易类型 JSAPI支付
    jsonObj.put("openid", openId);//用户在本公众号内的openId
    jsonObj.put("sign", RequestUtil.paramSort(jsonObj));//参数排序并生成签名
    String unifiedorderXML = RequestUtil.ssl("https://api.mch.weixin.qq.com/pay/unifiedorder",RequestUtil.jsonToXML(jsonObj));//调用微信接口统一下单
    JSONObject jsonObjResult = RequestUtil.xmltoJson(unifiedorderXML);//将微信返回的xml数据转换成json数据
    JSONObject jsonObjXML = jsonObjResult.getJSONObject("xml");
    String returnCode = jsonObjXML.getString("return_code");//通信标识
    if(StringUtils.equals("FAIL", returnCode)){//通信失败或签名错误
        logger.error("调用微信支付通信失败---"+"--xmlStr:"+unifiedorderXML+"--publicAccountOpenId:"+openId);
        resultMap.put("flag", false);
        resultMap.put("msg", "系统内部错误");
        resultMap.put("errCodeDes", "调用微信支付通信失败");
        return resultMap;
    }
    String resultCode = jsonObjXML.getString("result_code");
    if(StringUtils.equals("FAIL", resultCode)){//当result_code为FAIL时返回错误代码
        String errCodeDes = jsonObjXML.getString("err_code_des");//失败原因
        String errCode = jsonObjXML.getString("err_code");//失败状态码
        resultMap.put("flag", false);
        resultMap.put("errCode", errCode);
        resultMap.put("errCodeDes", errCodeDes);
        resultMap.put("msg", "统一下单失败。");
        return resultMap;
    }else{
        //下单成功
        String prepayId = jsonObjXML.getString("prepay_id");//预支付交易会话标识
        long timeStamp = System.currentTimeMillis()/1000;//时间戳
        JSONObject jsonObjTemp = new JSONObject();
        jsonObjTemp.put("appId", SystemContant.PUBLIC_ACCOUNT_APPID);//公众号appId
        jsonObjTemp.put("timeStamp",String.valueOf(timeStamp));//时间戳
        jsonObjTemp.put("nonceStr", u);//随机字符串
        jsonObjTemp.put("package", "prepay_id="+prepayId);//订单详情扩展字符串
        jsonObjTemp.put("signType", "MD5");//签名类型,默认为MD5,支持HMAC-SHA256和MD5。注意此处需与统一下单的签名类型一致
        String sign = RequestUtil.paramSort(jsonObjTemp);//参数排序并生成签名
        resultMap.put("flag", true);
        resultMap.put("msg", "下单成功");
        resultMap.put("appId", SystemContant.PUBLIC_ACCOUNT_APPID);
        resultMap.put("timeStamp", String.valueOf(timeStamp));
        resultMap.put("nonceStr", u);
        resultMap.put("packageStr", "prepay_id="+prepayId);
        resultMap.put("paySign", sign);
    }
    return resultMap;
}

3.2.2 微信页面内发起微信支付

首先,页面内需要引入微信相关的js:

<script src="http://res.wx.qq.com/open/js/jweixin-1.4.0.js"></script>

发起支付:

function payOrder() {
                $.ajax({
                    type: 'GET',
                    url: 'https://IP:port/xxxxx/payOrder?openId=' + openId,
                    dataType: 'json',
                    timeout: 3000,
                    success: function(data) {
                        var orderId = data.nonceStr;
                        WeixinJSBridge.invoke(
                            'getBrandWCPayRequest', {
                                "appId": data.appId, //公众号名称,由商户传入     
                                "timeStamp": data.timeStamp, //时间戳,自1970年以来的秒数     
                                "nonceStr": data.nonceStr, //随机串     
                                "package": data.packageStr,
                                "signType": "MD5", //微信签名方式:     
                                "paySign": data.paySign //微信签名 
                            },
                            function(res) {
                                if(res.err_msg == "get_brand_wcpay_request:ok") {
                                    payDone(openId, orderId);
                                } else {

                                }
                            });
                    },
                    error: function(xhr, type) {
                        alert('下单失败,请重试!')
                    }
                })
            }

可以看到,页面内首先调用后台payOrder接口完成统一下单,并返回发起支付需要的参数值,然后再通过这些参数发起微信支付。如果微信返回的err_msg == "get_brand_wcpay_request:ok",那么执行payDone方法。我们再看下payDone方法代码:

function payDone(openId, orderId) {
                $.ajax({
                    type: 'GET',
                    url: 'https://IP:port/xxxxx/payDone?openId=' + openId + '&orderId=' + orderId,
                    dataType: 'json',
                    timeout: 3000,
                    success: function(data) {
                        window.location.href = "success.html?openId=" + openId;
                    },
                    error: function(xhr, type) {
                        alert('生成订单失败,请保存支付凭证,联系商家!')
                    }
                })
            }

payDone方法中调用了后台payDone接口,我们再来看一下payDone接口代码:

/**
 * 验证订单是否真正支付成功
 * @param openId 用户openId
 * @param orderId 商户订单号
 * @return
 * @throws Exception
 */
@RequestMapping(value="/payDone")
@ResponseBody
public Map<String,Object> payDone(String openId,String orderId) throws Exception{
    Map<String,Object> resultMap = new HashMap<String, Object>();
    String u = RequestUtil.getUUID();
    JSONObject jsonObj = new JSONObject();
    jsonObj.put("appid", SystemContant.PUBLIC_ACCOUNT_APPID);
    jsonObj.put("mch_id", SystemContant.MCH_ID);
    jsonObj.put("nonce_str",u);
    jsonObj.put("out_trade_no", orderId);
    jsonObj.put("sign", RequestUtil.paramSort(jsonObj));
    CleanCarOrder cleanCarOrder = new CleanCarOrder();
    cleanCarOrder.setOpenId(openId);
    cleanCarOrder.setOutTradeNo(orderId);
    cleanCarOrder.setTradeType("JSAPI");
    try {
        //调用订单查询接口确认订单是否真正的支付成功
        String unifiedorderXML = RequestUtil.ssl("https://api.mch.weixin.qq.com/pay/orderquery",RequestUtil.jsonToXML(jsonObj));
        logger.error("payDoneXML:"+unifiedorderXML);
        JSONObject jsonObjResult = RequestUtil.xmltoJson(unifiedorderXML);
        JSONObject jsonObjXML = jsonObjResult.getJSONObject("xml");
        String returnCode = jsonObjXML.getString("return_code");//通信标识
        if(StringUtils.equals("FAIL", returnCode)){//通信失败
            logger.error("调用微信订单查询接口通信失败---"+"--xmlStr:"+unifiedorderXML+"--orderId:"+orderId);
            cleanCarOrder.setDataStatus(0);
        }
        String resultCode = jsonObjXML.getString("result_code");
        if(StringUtils.equals("FAIL", resultCode)){//状态未明确
            cleanCarOrder.setDataStatus(0);
        }else{
            String tradeState = jsonObjXML.getString("trade_state");
            //交易彻底成功
            if(StringUtils.isNotEmpty(tradeState)&&StringUtils.equals("SUCCESS", tradeState)){
                cleanCarOrder.setDataStatus(1);
                cleanCarOrder.setResultCode(resultCode);
                cleanCarOrder.setTimeEnd(jsonObjXML.getString("time_end"));
                cleanCarOrder.setTotalFee(jsonObjXML.getInt("cash_fee"));
                cleanCarOrder.setTransactionId(jsonObjXML.getString("transaction_id"));
            }else{
                cleanCarOrder.setDataStatus(0);
            }
        }
    } catch (Exception e) {
        cleanCarOrder.setDataStatus(0);
        e.printStackTrace();
    }
    messageProducer.sendMessage(cleanCarOrder.getDataStatus()==1?"确认交易成功":"未确认交易成功", "银座洗车票", "o8jb70el0nonB-s4OLrzYBwAMz48", "sendSpecifyTemplateMsg", "");
    if(cleanCarService.addCleanCarOrder(cleanCarOrder)<1){
        logger.error("保存用户洗车购买记录失败cleanCarOrder:"+cleanCarOrder);
    }
    resultMap.put("status", true);
    resultMap.put("msg", "保存成功");
    return resultMap;
}

从上述代码中可以看到,后台payDone接口功能是根据商户订单号用来查询订单是否成功支付,同时处理其他业务逻辑。为什么微信那边err_msg 已经返回了"get_brand_wcpay_request:ok",我们还要主动去查询订单状态呢?这里存在一个问题:

上图是微信支付官方API上的一段话,所以在err_msg返回 ok 的情况下,我们还需要主动调用订单查询接口或者根据微信的回调来判断订单支付状态。在统一下单的时候存在notify_url参数,这里需要传你的服务器接口地址,在支付完成之后微信会回调该接口通知订单支付状态。

3.2.3 注意点
在微信页面发起微信支付之前,需要商户系统在服务器端调用统一下单接口发起微信预支付;
后台完成统一下单之后,需要再次进行参数排序、生成签名等操作,然后将数据返回给前端,用于前端发起支付;
支付完成之后,微信前端返回 err_msg=="get_brand_wcpay_request:ok",这并不能保证支付成功,需要主动调用订单查询接口或者根据微信的回调来判断订单支付状态。并且接收微信回调时也要进行签名校验,防止接收到伪回调消息;
以上涉及到参数排序、生成签名等操作必须在服务器端完成,前端只负责接收和发送数据。
3.2.4 效果图

四、小程序支付
小程序发起微信支付交易类型也是JSAPI类型,流程步骤和微信网页支付基本相同。

4.1 准备工作
1、准备一个微信小程序,同时保证该小程序有“微信支付”的权限

2、准备一个微信商户,该商户需具备JSAPI支付权限

3、微信商户需要与小程序绑定

4、在微信商户平台设置你的JSAPI支付目录,在小程序平台设置授权域名

4.2 开发
和微信网页支付一样,在微信小程序发起支付之前,同样需要商户系统先调用统一下单接口在微信支付服务后台生成预支付交易单。

4.2.1 统一下单
后台代码和上述微信网页开发后台代码一致:
 

/**
 * 调用微信统一下单接口并返回签名等信息
 * @param openId
 * @return
 * @throws Exception
 */
@RequestMapping(value="/payOrder")
@ResponseBody
public Map<String,Object> payOrder(String openId) throws Exception{
   Map<String,Object> resultMap = new HashMap<String, Object>();
   String u = RequestUtil.getUUID();
   JSONObject jsonObj = new JSONObject();
   jsonObj.put("appid", SystemContant.WECHAT_APPID);
   jsonObj.put("mch_id", SystemContant.MCH_ID);
   jsonObj.put("nonce_str",u);
   jsonObj.put("body", "速挪车服务");
   jsonObj.put("out_trade_no", u);
   jsonObj.put("spbill_create_ip", "xx.xx.xx.xx");
   jsonObj.put("notify_url", "https://IP:port/xxx/payCallBack");
   jsonObj.put("total_fee", SystemContant.POSTAGE);
   jsonObj.put("trade_type", "JSAPI");
   jsonObj.put("openid", openId);
   jsonObj.put("sign", RequestUtil.paramSort(jsonObj));
   String unifiedorderXML = RequestUtil.ssl("https://api.mch.weixin.qq.com/pay/unifiedorder",RequestUtil.jsonToXML(jsonObj));
   JSONObject jsonObjResult = RequestUtil.xmltoJson(unifiedorderXML);
   JSONObject jsonObjXML = jsonObjResult.getJSONObject("xml");
   String returnCode = jsonObjXML.getString("return_code");//通信标识
   if(StringUtils.equals("FAIL", returnCode)){//通信失败
      log.error("调用微信支付通信失败---"+"--xmlStr:"+unifiedorderXML+"--publicAccountOpenId:"+openId);
      resultMap.put("flag", false);
      resultMap.put("msg", "系统内部错误");
      resultMap.put("errCodeDes", "调用微信支付通信失败");
      return resultMap;
   }
   String resultCode = jsonObjXML.getString("result_code");
   if(StringUtils.equals("FAIL", resultCode)){//状态未明确
      //请求查询接口确认状态信息
      String errCodeDes = jsonObjXML.getString("err_code_des");//失败原因
      String errCode = jsonObjXML.getString("err_code");//失败状态码
      resultMap.put("flag", false);
      resultMap.put("errCode", errCode);
      resultMap.put("errCodeDes", errCodeDes);
      resultMap.put("msg", "统一下单失败。");
      return resultMap;
   }else{
      //下单成功
      String prepayId = jsonObjXML.getString("prepay_id");
      long timeStamp = System.currentTimeMillis()/1000;
      JSONObject jsonObjTemp = new JSONObject();
      jsonObjTemp.put("appId", SystemContant.WECHAT_APPID);
      jsonObjTemp.put("timeStamp",String.valueOf(timeStamp));
      jsonObjTemp.put("nonceStr", u);
      jsonObjTemp.put("package", "prepay_id="+prepayId);
      jsonObjTemp.put("signType", "MD5");
      String sign = RequestUtil.paramSort(jsonObjTemp);
      resultMap.put("flag", true);
      resultMap.put("msg", "下单成功");
      resultMap.put("appId", SystemContant.WECHAT_APPID);
      resultMap.put("timeStamp", String.valueOf(timeStamp));
      resultMap.put("nonceStr", u);
      resultMap.put("packageStr", "prepay_id="+prepayId);
      resultMap.put("paySign", sign);
   }
   return resultMap;
}

4.2.2 微信小程序发起支付

wx.request({
    url: serverUrl + 'payOrder',
            method: 'GET',
            data: {
        openId: app.globalData.openId
    },
    success: function (res) {
        if (res.data.flag == true) {
            var outTradeNo = res.data.nonceStr;
            wx.requestPayment(
                    {
                    'timeStamp': res.data.timeStamp,
                    'nonceStr': res.data.nonceStr,
                    'package': res.data.packageStr,
                    'signType': 'MD5',
                    'paySign': res.data.paySign,
                    'success': function (res) {
                console.log("success:" + res);
                //支付成功,保存申请记录
                wx.request({
                        url: serverUrl + 'saveUserCodeApply',
                        method: 'GET',
                        data: {
                    openId: app.globalData.openId,
                            area: that.data.region[0] + "-" + that.data.region[1] + "-" + that.data.region[2],
                            address: e.detail.value.address,
                            tel: e.detail.value.tel,
                            name: e.detail.value.userName,
                            outTradeNo: outTradeNo,
                },
                success: function (res) {
                    if (res.data.status) {
                        wx.showToast({
                                title: res.data.errmsg,
                                icon: 'success',
                                duration: 2000
                  })
                        //跳转页面
                    }else{
                        //提示失败
                        wx.showModal({
                                title: '提示',
                                content: res.data.errmsg,
                                success(res) {

                        }
                  })
                    }
                }
            })
            },
            'fail': function (res) {
                console.log("fail:" + res);
            },
            'complete': function (res) {
                console.log("complete:" + res);
            }
        })
        } else {
            wx.showModal({
                    title: '系统提示',
                    content: res.data.msg,
                    showCancel: false
      })
        }
    }
})

微信小程序调用后台payOrder接口完成统一下单,同时根据接口返回的参数发起微信支付,当微信requestPayment请求返回success时代表支付成功。这个地方微信官方文档上并没有提到是否存在风险,必要的情况下也可以通过主动查询或者微信回调来校验订单状态。

4.2.3 注意点
注意点同3.2.3。

4.2.4 效果图

以上内容介绍了微信支付开发中常见的公众号发红包、企业付款、微信网页支付、微信小程序支付等功能的开发流程。

 

 

版权声明:本文来源CSDN,感谢博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/liu59412/article/details/91419027
站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。
  • 发表于 2020-06-28 02:54:02
  • 阅读 ( 1086 )
  • 分类:

0 条评论

请先 登录 后评论

官方社群

GO教程

猜你喜欢