微信小程序之支付初窥

微信小程序支付

微信小程序中调用支付的方法
  1. 在微信小程序中,我们只需要简单的调用下面的这个方法

    wx.requestPayment({
       timeStamp: '',
       nonceStr: '',
       package: '',
       signType: 'MD5',
       paySign: '',
       success:function(res){
       	console.log(res);
       },
       fail:function(res){
       	console.log(res);
       }
    });
    
    

参数说明:

参数类型必填说明
timeStampString时间戳从1970年1月1日00:00:00至今的秒数,即当前的时间
nonceStrString随机字符串,长度为32个字符以下。
packageString统一下单接口返回的 prepay_id 参数值,提交格式如:prepay_id=***
signTypeString签名算法,暂支持 MD5
paySignString签名,具体签名方案参见小程序支付接口文档;
successFunction接口调用成功的回调函数
failFunction接口调用失败的回调函数
completeFunction接口调用结束的回调函数(调用成功、失败都会执行)
开发者服务器
  1. 在微信小程序中,需要注意有几个参数是需要我们在开发者的服务器提供的。

    timeStamp、nonceStr、package、paySign

    要想得到这个几个参数,我们需要费点脑子和心思,毕竟微信的开发文档远远不如阿里云的文档好用。

开发准备
  1. 打开pom.xml添加以下的dependency

    <dependency>
        <groupId>org.apache.httpcomponents</groupId>
        <artifactId>httpclient</artifactId>
        <version>4.5.3</version>
    </dependency>
    
    1. 编写一个HttpApiService.java 用于在服务器上发送http请求
    package com.mmyhs.wechat.service;
    
    import com.alibaba.fastjson.JSON;
    import com.alibaba.fastjson.JSONObject;
    import com.sun.org.apache.regexp.internal.RE;
    import org.springframework.stereotype.Service;
    
    import java.io.*;
    import java.net.HttpURLConnection;
    import java.net.URL;
    
    @Service
    public class HttpAPIService {
    
        public static final String HTTP_METHOD_POST="POST";
    
        public static final String HTTP_METHOD_GET="GET";
    
        public String get(String url,String output){
            return this.request(url,HTTP_METHOD_GET,output,5000,5000);
        }
    
        public String get(String url, String output, int connTimeout, int readTimeout){
            return this.request(url,HTTP_METHOD_GET,output,connTimeout,readTimeout);
        }
    
        public String post(String url,String output){
            return this.request(url,HTTP_METHOD_POST,output,5000,5000);
        }
    
        public String post(String url, String output, int connTimeout, int readTimeout){
            return this.request(url,HTTP_METHOD_POST,output,connTimeout,readTimeout);
        }
    
        private String request(String url, String method, String output, int connTimeout, int readTimeout) {
            StringBuilder stringBuilder=null;
            OutputStream outputStream=null;
            InputStream inputStream=null;
            BufferedReader br=null;
            try{
                URL url1=new URL(url);
                HttpURLConnection conn= (HttpURLConnection) url1.openConnection();
                conn.setRequestMethod(method);
                conn.setConnectTimeout(connTimeout);
                conn.setReadTimeout(readTimeout);
                conn.setDoInput(true);
                conn.setDoOutput(true);
                conn.connect();
                outputStream = conn.getOutputStream();
                if(null!=output && !output.trim().equals("")){
                    outputStream.write(output.getBytes("utf-8"));
                }
                inputStream = conn.getInputStream();
                br = new BufferedReader(new InputStreamReader(inputStream));
                String temp=null;
                stringBuilder = new StringBuilder();
                while((temp=br.readLine())!=null){
                    stringBuilder.append(temp);
                }
            }catch (Exception e){
                e.printStackTrace();
            }finally {
                try {
                    if(null!=br){
                        br.close();
                    }
                    if(null!=inputStream){
                        inputStream.close();
                    }
                    if(null!=outputStream){
                        outputStream.close();
                    }
                }catch (Exception e){
                    e.printStackTrace();
                }
                return stringBuilder==null ? null:stringBuilder.toString();
            }
        }
    
    }
    
    1. 新建一个微信配置文件,包含一下的内容。
    package com.mmyhs.wechat.config;
    
    public class WeChatConfig {
    
        // 小程序唯一标识
        public static String APPID="";
    
        // 小程序的 app secret
        public static String SECRET="";
    
        // 商户号
        public static String MCHID="";
    
        // 商户密钥
        public static final String MCH_KEY = "";
    
        // 异步接收微信支付结果通知的回调地址,通知url必须为外网可访问的url,不能携带参数。
        public static final String NOTIFY_URL="";
    
        // 交易类型
        public static final String TRADE_TYPE = "JSAPI";
    
        // 支付密钥
        public static final String PAY_KEY="";
    }
    
    1. 通过wx.login()得到临时code,通过临时code得到session_key、openid
    @RequestMapping(value = "/login")
        public Map<String, Object> login(String code, HttpSession session) throws Exception {
            Map<String, Object> map = new HashMap<>();
            String url = "https://api.weixin.qq.com/sns/jscode2session?appid=" + WechatConfig.APPID + "&secret=" + WechatConfig.SECRET + "&js_code=" + code + "&grant_type=authorization_code";
            String result = httpAPIService.doGet(url);
            JSONObject json = JSONObject.fromObject(result);
            Object sessionKey = json.get("session_key");
            Object openid = json.get("openid");
            Object errcode = json.get("errcode");
            Object errmsg = json.get("errmsg");
            if(null!=errcode){
                map.put("code",errcode);
                map.put("errmsg",errmsg);
            }else{
                session.setAttribute("sessionKey",sessionKey);
                map.put("code",200);
                map.put("openId",openid);
                map.put("sessionId",session.getId());
            }
            System.out.println(result);
            return map;
        }
    

    在上面的代码需要注意的地方

    • 登录凭证校验

      • https://api.weixin.qq.com/sns/jscode2session?appid=APPID&secret=SECRET&js_code=JSCODE&grant_type=authorization_code

      • 请求参数

        参数必填说明
        appid小程序唯一标识
        secret小程序的 app secret
        js_code登录时获取的 code
        grant_type填写为 authorization_code
      • 在不满足UnionID下发条件的情况下,返回参数

      • 参数说明
        openid用户唯一标识
        session_key会话密钥
      • 在满足UnionID下发条件的情况下,返回参数、

      • 参数说明
        openid用户唯一标识
        session_key会话密钥
        unionid用户在开放平台的唯一标识符
统一下订单

文档地址:https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=9_1

统一下单实体类

public class UnifiedOrder {

    private String appid; // 小程序ID
    private String mchId;   // 商户号
    private String deviceInfo;  // 设备号
    private String nonceStr;    // 随机字符串
    private String sign;    // 签名
    private String signType;   // 签名类型
    private String body;    // 商品描述
    private String detaill; // 商品详情
    private String attach;  // 附加数据
    private String outTradeNo;    // 商户订单号
    private String feeType; // 标价币种
    private String totalFee;  // 标价金额
    private String spbillCreateIp;  // 终端IP
    private String timeStart;  // 交易起始时间
    private String timeExpire; // 交易结束时间
    private String goodsTag;   // 订单优惠标记
    private String notifyUrl;  // 通知地址
    private String tradeType;  // 交易类型
    private String productId;  // 商品ID
    private String limitpay;   // 指定支付方式
    private String openid;  // 用户标识
    
    // 省略了get/set方法
    
}

在这里我们还需要一个微信支付一个帮助类-- WechatPayUtil.java

public class WechatPayUtil {

    private static final String NONCE_STR="1234567890asdfghjklzxcvbnmqwertyuiopASDFGHJKLZXCVBNMQWERTYUIOP";

    private static final char[] NONCE_STRS=NONCE_STR.toCharArray();

    private static final SimpleDateFormat simpleDateFormat=new SimpleDateFormat("yyyyMMddHHmmss");

    public static StringBuffer mapToXmlStr(Map<String,Object> map){
        StringBuffer xml = new StringBuffer();
        xml.append("<xml>");
        for(String key:map.keySet()){
            Object value=map.get(key);
            if("total_fee".equals(key)){
                xml.append("<"+key+">"+value+"</"+key+">");
            }else{
                xml.append("<"+key+"><![CDATA["+value+"]]></"+key+">");
            }
        }
        xml.append("</xml>");
        return xml;
    }

    public static Map<String,Object> xmlToMap(String xmlStr){
        Map<String,Object> map=null;
        InputStream is=null;
        try {
            is= new ByteArrayInputStream(xmlStr.getBytes("utf-8"));
            map=xmlToMap(is);
        } catch (JDOMException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if(null!=is){
                try {
                    is.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            return map;
        }
    }

    public static Map<String,Object> xmlToMap(InputStream isXml) throws JDOMException, IOException {
        Map<String,Object> map=new HashMap<>();
        SAXBuilder builder=new SAXBuilder();
        Document document=builder.build(isXml);
        Element element=document.getRootElement();
        List<Element> elements=element.getContent();
        for(Element e:elements){
            String name=e.getName();
            String value=e.getValue();
            map.put(name,value);
        }
        return map;
    }

    public static String getOuTradeNo(){
        String result=getNonceStr(6);
        result+=simpleDateFormat.format(new Date());
        return result;
    }

    public static String getNonceStr(){
        return getNonceStr(32);
    }

    public static String getNonceStr(Integer length){
        StringBuilder sb=new StringBuilder();
        for(int i=0;i<length;i++){
            int index=CommonUtil.randNumer(0,NONCE_STR.length()-1);
            sb.append(NONCE_STRS[index]);
        }
        return sb.toString();
    }

    /**
     * 签名字符串
     * @param text 需要签名的字符串
     * @param key 密钥
     * @param input_charset 编码格式
     * @return 签名结果
     */
    public static String sign(String text, String key, String input_charset) {
        text = text + "&key=" + key;
        System.out.println(text);
        return DigestUtils.md5Hex(getContentBytes(text, input_charset));
    }

    /**
     * @param content
     * @param charset
     * @return
     * @throws UnsupportedEncodingException
     */
    public static byte[] getContentBytes(String content, String charset) {
        if (charset == null || "".equals(charset)) {
            return content.getBytes();
        }
        try {
            return content.getBytes(charset);
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException("MD5签名过程中出现错误,指定的编码集不对,您目前指定的编码集是:" + charset);
        }
    }

    public static String createLinkString(Map<String,String> params){
        List<String> keys=new ArrayList<String>(params.keySet());
        Collections.sort(keys);
        StringBuilder sb=new StringBuilder();
        for(int i=0;i<keys.size();i++){
            String key=keys.get(i);
            String value=params.get(key);
            if(keys.size()-1==i){
                sb.append(key+"="+value);
            }else{
                sb.append(key+"="+value+"&");
            }
        }
        return sb.toString();
    }

    /**
     *  除去数组中的空值和签名参数
     * @param map 签名参数组
     * @return 去掉空值与签名参数后的新签名参数组
     */
    public static Map<String,String> paraFilter(Map<String,String> map){
        Map<String,String> tempMap=new HashMap<>();
        if(null==map || map.size()<=0){
            return map;
        }
        for(String key:map.keySet()){
            String value=map.get(key).trim();
            if(null==value || "".equals(value) || "sign".equals(value) || "sign_type".equals(value)){
                continue;
            }
            tempMap.put(key,value);
        }
        return tempMap;
    }

}

开发者服务器上的统一下订单接口

    @RequestMapping("/unifiedOrder")
    public HttpResult unifiedOrder(UnifiedOrder unifiedOrder, HttpServletRequest request) throws Exception {
        String url = "https://api.mch.weixin.qq.com/pay/unifiedorder";
        String nonceStr=WechatPayUtil.getNonceStr();
        String outTradeNo=WechatPayUtil.getOuTradeNo();
        String spbillCreateIp= IpUtil.getIpAddr(request);
        Map<String, String> map = new HashMap<>();
        map.put("appid",WechatConfig.APPID);
        map.put("mch_id",WechatConfig.MCHID);
        map.put("device_info","WEB");
        map.put("nonce_str",nonceStr);
        map.put("body",unifiedOrder.getBody());
        map.put("out_trade_no",outTradeNo);
        map.put("total_fee",unifiedOrder.getTotalFee());
        map.put("spbill_create_ip",spbillCreateIp);
        map.put("notify_url",WechatConfig.NOTIFY_URL);
        map.put("trade_type",WechatConfig.TRADE_TYPE);
        map.put("openid",unifiedOrder.getOpenid());

        map=WechatPayUtil.paraFilter(map);

        Map<String,Object> map2=new HashMap<>();
        map2.putAll(map);

        String prestr=WechatPayUtil.createLinkString(map);
        String mysign=WechatPayUtil.sign(prestr,WechatConfig.MCH_KEY,"UTF-8").toUpperCase();
        map2.put("sign",mysign);
        WechatPayUtil.mapToXmlStr(map2);
        System.out.println("======================第一次签名======================");

        StringBuffer xml=WechatPayUtil.mapToXmlStr(map2);
        System.out.println(xml.toString());

        String result= httpAPIService.doPost(url,xml.toString());

        Map<String,Object> resultMap=WechatPayUtil.xmlToMap(result);
        Map<String,Object> returnMap=new HashMap<>();
        if("SUCCESS".equals(resultMap.get("return_code")) && "SUCCESS".equals(resultMap.get("result_code"))){
            Long timeStamp=System.currentTimeMillis()/1000;
            returnMap.put("msg","success");
            returnMap.put("timeStamp",timeStamp.toString());
            returnMap.put("nonceStr",nonceStr);
            returnMap.put("package","prepay_id="+resultMap.get("prepay_id"));
            returnMap.put("signType","MD5");
            System.out.println("======================第二次签名======================");
            String stringSignTemp="appId="+WechatConfig.APPID+"&nonceStr="+nonceStr+"&package=prepay_id="+resultMap.get("prepay_id")+"&signType=MD5&timeStamp="+timeStamp.toString();
            String paySign=WechatPayUtil.sign(stringSignTemp,WechatConfig.MCH_KEY,"UTF-8").toUpperCase();
            returnMap.put("paySign",paySign);
        }else{
            returnMap.put("msg","error");
            returnMap.put("return_msg",returnMap.get("return_msg"));
        }
        return new HttpResult(200,returnMap);
    }

在开发者服务器上的 /unifiedOrder 接口中,需要注意的是签名的次数,一定是需要签名两次。

第一次的签名是为了将数据发送到微信服务器上进行数据的校验。

第二次的签名是为了提供给微信小程序进行微信支付的参数。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值