微信小程序____微信支付 实战文档(一)


引言

前文已对微信支付的相关文档进行了解读并理解,自己也反复查了几遍,才算是对整个流程有了一定的了解。本篇就代码实现作出记录。因为大家也知道,copy从来都是最简单、最直接的实现方式。


小程序端需要实现的内容

第一步:生成商户订单

// 生成商户订单即发起预支付
/**生成商户订单 */
generateOrder: function (openid) {
  var that = this
  //统一支付
  wx.request({
    url: 'http://localhost:8070/RMS/pay_pay.action',  // 自己的后台支付接口
    method: 'GET',
    data: {
      total_fee: '1', // 以分为单位,'1'即 -> 1分钱, 必填!!! 
      body: '支付测试', // 商品描述,即商品名称,注意:此参数必填!!!
    },
    success: function (res) {
      var pay = res.data
      //发起支付
      var timeStamp = pay[0].timeStamp;
      var packages = pay[0].package;
      var paySign = pay[0].paySign;
      var nonceStr = pay[0].nonceStr;
      var param = { "timeStamp": timeStamp, "package": packages, "paySign": paySign, "signType": "MD5", "nonceStr": nonceStr };
      that.pay(param)
     },
  })
},

第二步:发起支付

/* 支付   */
  payMoney: function (param) {
    console.log("开始支付");
    var that = this;
    wx.requestPayment({
      'timeStamp': param.timeStamp,
      'nonceStr': param.nonceStr,
      'package': param.package,
      'signType': param.signType,
      'paySign': param.paySign,
      'success': function (res) {
         wx.showToast({
           title: '支付成功',
           icon: 'success',
           duration: 3000
         })
      },
      'fail': function (res) {
        console.log("支付失败");
        api.showModal("支付失败,请重试");
      }
    })
  },

其实支付功能在小程序前端就这么点代码,主要还是后台的一些操作。
为了安全考虑,需要用到的,小程序appid和秘钥商户的商户号和支付秘钥全都放在后台。
哦,看到这里你可能问我,什么是商户号和商户秘钥,那你首先要做的就不是写代码了,而是先去看一下官方文档,起码简单了解一下支付的业务流程,这里你需要注意微信官方文档页面下边的五个步骤,看看它跳转之后的内容。

然后先去申请开通一下小程序的支付功能

微信小程序____微信支付 实战文档(一)_第1张图片

再去微信商户的平台设置并获取一下你的商户号和支付秘钥,具体方法在这也给你:微信支付商户平台-配置密钥/API安全

之后就基本都是后台的任务了。
调用支付统一下单API来获取prepay_id,并将小程序调起支付数据需要签名的字段appId,timeStamp,nonceStr,package再次签名——小程序调起支付API

后台代码

/** 
* @author 
* @version 创建时间:2017年1月21日 下午4:59:03 
* 小程序端请求的后台action,返回签名后的数据传到前台
*/
public class PayAction {
    private String total_fee;//总金额
    private String body;//商品描述
    private String detail;//商品详情    
    private String attach;//附加数据
    private String time_start;//交易起始时间
    private String time_expire;//交易结束时间 
    private String openid;//用户标识

    private JSONArray jsonArray=new JSONArray();
    public String pay() throws UnsupportedEncodingException, DocumentException{

        body = new String(body.getBytes("UTF-8"),"ISO-8859-1");  
        String appid = "替换为自己的小程序ID";//小程序ID
        String mch_id = "替换为自己的商户号";//商户号
        String nonce_str = UUIDHexGenerator.generate();//随机字符串
        String today = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date());
        String code = PayUtil.createCode(8);
        String out_trade_no = mch_id+today+code;//商户订单号
        String spbill_create_ip = "替换为自己的终端IP";//终端IP
        String notify_url = "http://www.weixin.qq.com/wxpay/pay.php";//通知地址
        String trade_type = "JSAPI";//交易类型  
        String openid="替换为用户的openid";//用户标识

        //封装支付参数
        PaymentPo paymentPo = new PaymentPo();

        paymentPo.setAppid(appid);
        paymentPo.setMch_id(mch_id);
        paymentPo.setNonce_str(nonce_str);
        String newbody=new String(body.getBytes("ISO-8859-1"),"UTF-8");//以utf-8编码放入paymentPo,微信支付要求字符编码统一采用UTF-8字符编码
        paymentPo.setBody(newbody);
        paymentPo.setOut_trade_no(out_trade_no);
        paymentPo.setTotal_fee(total_fee);
        paymentPo.setSpbill_create_ip(spbill_create_ip);
        paymentPo.setNotify_url(notify_url);
        paymentPo.setTrade_type(trade_type);
        paymentPo.setOpenid(openid);

        // 把请求参数打包成Map
        Map sParaTemp = new HashMap();
        sParaTemp.put("appid", paymentPo.getAppid());
        sParaTemp.put("mch_id", paymentPo.getMch_id());
        sParaTemp.put("nonce_str", paymentPo.getNonce_str());
        sParaTemp.put("body",  paymentPo.getBody());
        sParaTemp.put("out_trade_no", paymentPo.getOut_trade_no());
        sParaTemp.put("total_fee",paymentPo.getTotal_fee());
        sParaTemp.put("spbill_create_ip", paymentPo.getSpbill_create_ip());
        sParaTemp.put("notify_url",paymentPo.getNotify_url());
        sParaTemp.put("trade_type", paymentPo.getTrade_type());
        sParaTemp.put("openid", paymentPo.getOpenid());

        // 除去Map中的空值和签名参数
        Map sPara = PayUtil.paraFilter(sParaTemp);
        String prestr = PayUtil.createLinkString(sPara); // 把Map所有元素,按照“参数=参数值”的模式用“&”字符拼接成字符串
        String key = "&key=替换为商户支付密钥"; // 商户支付密钥
        //MD5运算生成签名
        String mysign = PayUtil.sign(prestr, key, "utf-8").toUpperCase();
        paymentPo.setSign(mysign);
        //打包要发送的xml
        String respXml = MessageUtil.messageToXML(paymentPo);
        // 打印respXml发现,得到的xml中有“__”不对,应该替换成“_”
        respXml = respXml.replace("__", "_");
        String url = "https://api.mch.weixin.qq.com/pay/unifiedorder";//统一下单API接口链接
        String param = respXml;
        String result =PayUtil.httpRequest(url, "POST", param);
        // 将解析结果存储在HashMap中
        Map map = new HashMap();
        InputStream in=new ByteArrayInputStream(result.getBytes());  
        // 读取输入流
        SAXReader reader = new SAXReader();
        Document document = reader.read(in);
        // 得到xml根元素
        Element root = document.getRootElement();
        // 得到根元素的所有子节点
        @SuppressWarnings("unchecked")
        List elementList = root.elements();
        for (Element element : elementList) {
            map.put(element.getName(), element.getText());
        }
        // 返回信息
        String return_code = map.get("return_code");//返回状态码
        String return_msg = map.get("return_msg");//返回信息
        JSONObject JsonObject=new JSONObject() ;
        //请求成功
        if(return_code=="SUCCESS"||return_code.equals(return_code)){
            // 业务结果
            String prepay_id = map.get("prepay_id");//返回的预付单信息
            String nonceStr=UUIDHexGenerator.generate();
            JsonObject.put("nonceStr", nonceStr);
            JsonObject.put("package", "prepay_id="+prepay_id);
            Long timeStamp= System.currentTimeMillis()/1000;
            JsonObject.put("timeStamp", timeStamp+"");
            String stringSignTemp = "appId="+appid+"&nonceStr=" + nonceStr + "&package=prepay_id=" + prepay_id+ "&signType=MD5&timeStamp=" + timeStamp;
            //再次签名
            String paySign=PayUtil.sign(stringSignTemp, "&key=替换为自己的商户密钥", "utf-8").toUpperCase();
            JsonObject.put("paySign", paySign);
            jsonArray.add(JsonObject);
        }
        return "pay";

    }
    //set get方法略
}

这里边开头获取的那一段数据(总金额/商品描述/商品详情/附加数据/...),里边只有总金额和商品描述是必须的,其他的你酌情处理。
不过基本都是传递一个商品id,然后后台自己去数据库中查找对应的商品信息。看你跟后台如何沟通处理了。

  • 后台业务逻辑涉及到的工具类及参数封装类
MessageUtil 

public class MessageUtil {

    public static HashMap parseXML(HttpServletRequest request) throws Exception, IOException{
        HashMap map=new HashMap();
        // 通过IO获得Document
        SAXReader reader = new SAXReader();
        Document doc = reader.read(request.getInputStream());

        //得到xml的根节点
        Element root=doc.getRootElement();
        recursiveParseXML(root,map);
        return map;
    }
    private static void recursiveParseXML(Element root,HashMap map){
        //得到根节点的子节点列表
        List elementList=root.elements();
        //判断有没有子元素列表
        if(elementList.size()==0){
            map.put(root.getName(), root.getTextTrim());
        }
        else{
            //遍历
            for(Element e:elementList){
                recursiveParseXML(e,map);
            }
        }
    }
    private static XStream xstream = new XStream(new XppDriver() {
        public HierarchicalStreamWriter createWriter(Writer out) {
            return new PrettyPrintWriter(out) {
                // 对所有xml节点都增加CDATA标记
                boolean cdata = true;

                public void startNode(String name, Class clazz) {
                    super.startNode(name, clazz);
                }

                protected void writeText(QuickWriter writer, String text) {
                    if (cdata) {
                        writer.write("");
                    } else {
                        writer.write(text);
                    }
                }
            };
        }
    });

    public static String messageToXML(PaymentPo paymentPo){
        xstream.alias("xml",PaymentPo.class);
        return xstream.toXML(paymentPo);
    }
}

PaymentPo//封装支付参数实体
package cn.it.shop.util;
/** 
* @author 
* @version 创建时间:2017年1月21日 下午4:20:52 
* 类说明 
*/
public class PaymentPo {
    private String appid;//小程序ID
    private String mch_id;//商户号
    private String device_info;//设备号
    private String nonce_str;//随机字符串
    private String sign;//签名
    private String body;//商品描述  
    private String detail;//商品详情    
    private String attach;//附加数据
    private String out_trade_no;//商户订单号
    private String fee_type;//货币类型
    private String spbill_create_ip;//终端IP
    private String time_start;//交易起始时间
    private String time_expire;//交易结束时间 
    private String goods_tag;//商品标记
    private String total_fee;//总金额
    private String notify_url;//通知地址    
    private String trade_type;//交易类型    
    private String limit_pay;//指定支付方式
    private String openid;//用户标识

    //set get方法略

}
PayUtil//工具类
/**
 * @author 
 * @version 创建时间:2017年1月17日 下午7:46:29 类说明
 */
public class PayUtil {
    /**
     * 签名字符串
     * @param text需要签名的字符串
     * @param key 密钥
     * @param input_charset编码格式
     * @return 签名结果
     */
    public static String sign(String text, String key, String input_charset) {
        text = text + key;
        return DigestUtils.md5Hex(getContentBytes(text, input_charset));
    }

    /**
     * 签名字符串
     *  @param text需要签名的字符串
     * @param sign 签名结果
     * @param key密钥
     * @param input_charset 编码格式
     * @return 签名结果
     */
    public static boolean verify(String text, String sign, String key, String input_charset) {
        text = text + key;
        String mysign = DigestUtils.md5Hex(getContentBytes(text, input_charset));
        if (mysign.equals(sign)) {
            return true;
        } else {
            return false;
        }
    }

    /**
     * @param content
     * @param charset
     * @return
     * @throws SignatureException
     * @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);
        }
    }

    /**
     * 生成6位或10位随机数 param codeLength(多少位)
     * @return
     */
    public static String createCode(int codeLength) {
        String code = "";
        for (int i = 0; i < codeLength; i++) {
            code += (int) (Math.random() * 9);
        }
        return code;
    }

    private static boolean isValidChar(char ch) {
        if ((ch >= '0' && ch <= '9') || (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z'))
            return true;
        if ((ch >= 0x4e00 && ch <= 0x7fff) || (ch >= 0x8000 && ch <= 0x952f))
            return true;// 简体中文汉字编码
        return false;
    }

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

    /**
     * 把数组所有元素排序,并按照“参数=参数值”的模式用“&”字符拼接成字符串
     * @param params 需要排序并参与字符拼接的参数组
     * @return 拼接后字符串
     */
    public static String createLinkString(Map params) {
        List keys = new ArrayList(params.keySet());
        Collections.sort(keys);

        String prestr = "";

        for (int i = 0; i < keys.size(); i++) {
            String key = keys.get(i);
            String value = params.get(key);
            if (i == keys.size() - 1) {// 拼接时,不包括最后一个&字符
                prestr = prestr + key + "=" + value;
            } else {
                prestr = prestr + key + "=" + value + "&";
            }
        }
        return prestr;
    }
    /**
     * 
     * @param requestUrl请求地址
     * @param requestMethod请求方法
     * @param outputStr参数
     */
    public static String httpRequest(String requestUrl,String requestMethod,String outputStr){
        // 创建SSLContext
        StringBuffer buffer=null;
        try{
        URL url = new URL(requestUrl);
        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
        conn.setRequestMethod(requestMethod);
        conn.setDoOutput(true);
        conn.setDoInput(true);
        conn.connect();

        //往服务器端写内容
        if(null !=outputStr){
            OutputStream os=conn.getOutputStream();
            os.write(outputStr.getBytes("utf-8"));
            os.close();
        }
        // 读取服务器端返回的内容
        InputStream is = conn.getInputStream();
        InputStreamReader isr = new InputStreamReader(is, "utf-8");
        BufferedReader br = new BufferedReader(isr);
        buffer = new StringBuffer();
        String line = null;
        while ((line = br.readLine()) != null) {
                      buffer.append(line);
        }
        }catch(Exception e){
            e.printStackTrace();
        }
        return buffer.toString();
        }   
    public static String urlEncodeUTF8(String source){
        String result=source;
        try {
            result=java.net.URLEncoder.encode(source, "UTF-8");
        } catch (UnsupportedEncodingException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return result;
    }
}
UUIDHexGenerator
package cn.it.shop.util;

import java.net.InetAddress;

/**
 * @author 
 * @version 创建时间:2017年1月17日 下午7:45:06 类说明
 */
public class UUIDHexGenerator {
    private static String sep = "";

    private static final int IP;

    private static short counter = (short) 0;

    private static final int JVM = (int) (System.currentTimeMillis() >>> 8);

    private static UUIDHexGenerator uuidgen = new UUIDHexGenerator();

    static {
        int ipadd;
        try {
            ipadd = toInt(InetAddress.getLocalHost().getAddress());
        } catch (Exception e) {
            ipadd = 0;
        }
        IP = ipadd;
    }

    public static UUIDHexGenerator getInstance() {
        return uuidgen;
    }

    public static int toInt(byte[] bytes) {
        int result = 0;
        for (int i = 0; i < 4; i++) {
            result = (result << 8) - Byte.MIN_VALUE + (int) bytes[i];
        }
        return result;
    }

    protected static String format(int intval) {
        String formatted = Integer.toHexString(intval);
        StringBuffer buf = new StringBuffer("00000000");
        buf.replace(8 - formatted.length(), 8, formatted);
        return buf.toString();
    }

    protected static String format(short shortval) {
        String formatted = Integer.toHexString(shortval);
        StringBuffer buf = new StringBuffer("0000");
        buf.replace(4 - formatted.length(), 4, formatted);
        return buf.toString();
    }

    protected static int getJVM() {
        return JVM;
    }

    protected synchronized static short getCount() {
        if (counter < 0) {
            counter = 0;
        }
        return counter++;
    }

    protected static int getIP() {
        return IP;
    }

    protected static short getHiTime() {
        return (short) (System.currentTimeMillis() >>> 32);
    }

    protected static int getLoTime() {
        return (int) System.currentTimeMillis();
    }

    public static String generate() {
            return  new   StringBuffer(36).append(format(getIP())).append(sep).append(format(getJVM())).append(sep)
                .append(format(getHiTime())).append(sep).append(format(getLoTime())).append(sep)
                .append(format(getCount())).toString();
    }
}

这里最后还有个需要注意的东西:你支付成功后需要给微信反馈!!!
具体如何反馈,让你们后台参考前文业务流程中的第四条:支付结果通用通知

你可能感兴趣的:(微信小程序____微信支付 实战文档(一))