Java实现小程序微信支付

时间:2023-02-22 10:32:10

小程序支付流程交互图:

Java实现小程序微信支付

进入小程序,下单,请求下单支付,调用小程序登录API来获取Openid,生成商户订单

// pages/pay/pay.js 
var app = getApp(); 
Page({ 
    data: {}, 
    onLoad: function (options) { 
        // 页面初始化 options为页面跳转所带来的参数 
    }, 
    /* 微信支付 */ 
    wxpay: function () { 
        var that = this;
        //登陆获取code 
        wx.login({ 
            success: function (res) { 
                //获取openid 
                that.getOpenId(res.code); 
            } 
        }); 
    }, 
    getOpenId: function (code) { 
        var that = this; 
        wx.request({ 
            url: "https://api.weixin.qq.com/sns/jscode2session?appid=小程序appid&secret=小程序Secret&js_code=" + code + "&grant_type=authorization_code", 
            data: {}, 
            method: 'GET', 
            success: function (res) { 
                console.log(res.data); 
                that.generateOrder(res.data.openid); 
            }, 
            fail: function () { 
                // fail 
            }, 
            complete: function () { 
                // complete 
            } 
        }) 
    }, 
    /**生成商户订单 */ 
    generateOrder: function (openid) { 
        var that = this; 
        //统一支付 
        wx.request({ 
            url: 'http://localhost:9090/weixin/payment.do', 
            method: 'GET', 
            data: { 
                total_fee: '666', //金额,注意以分为单位
                body: '茅台', //产品简单描述
                attach:'广州分店' //附加数据
            }, 
            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); 
            }, 
        }) 
    }, 
    /* 支付   */ 
    pay: function (param) { 
        wx.requestPayment({ 
            timeStamp: param.timeStamp, 
            nonceStr: param.nonceStr, 
            package: param.package, 
            signType: param.signType, 
            paySign: param.paySign, 
            success: function (res) { 
                wx.navigateBack({ 
                    delta: 1, // 回退前 delta(默认为1) 页面 
                    success: function (res) { 
                        wx.showToast({ 
                            title: '支付成功', 
                            icon: 'success', 
                            duration: 2000 
                        }) 
                    }, 
                    fail: function () { 
                        // fail 
                    }, 
                    complete: function () { 
                        // complete 
                    } 
                }) 
            }, 
            fail: function (res) { 
                // fail 
                console.log("支付失败"); 
            }, 
            complete: function () { 
                // complete 
                console.log("pay complete"); 
            } 
        }) 
    } 
}) 

调用支付统一下单API来获取prepay_id,并将小程序调起支付数据需要签名的字段appId,timeStamp,nonceStr,package再次签名

后台代码

package com.card.mp.controller;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.card.dto.PaymentDto;
import com.card.framework.utils.*;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.servlet.http.HttpServletRequest;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Controller
public class WeiXinPaymentController extends BaseController {
    private final String mch_id = "填写商户号";//商户号
    private final String spbill_create_ip = "填写终端IP";//终端IP
    private final String notify_url = "域名/weixin/paycallback.do";//通知地址
    private final String trade_type = "JSAPI";//交易类型
    private final String url = "https://api.mch.weixin.qq.com/pay/unifiedorder";//统一下单API接口链接
    private final String key = "&key=填写商户支付密钥"; // 商户支付密钥
    private final String appid = "填写小程序AppId";

    /**
     *
     * @param openId
     * @param total_fee 订单总金额,单位为分。
     * @param body  商品简单描述,该字段请按照规范传递。 例:腾讯充值中心-心悦会员充值
     * @param attach    附加数据,在查询API和支付通知中原样返回,可作为自定义参数使用。 例:广州分店
     * @return
     * @throws UnsupportedEncodingException
     * @throws DocumentException
     */
    @RequestMapping("/weixin/payment.do")
    @ResponseBody
    public JSONObject payment(@RequestParam(required = true) String openId, @RequestParam(required = true)String total_fee, @RequestParam(required = false) String body, @RequestParam(required = false) String attach) throws UnsupportedEncodingException, DocumentException {
        JSONObject JsonObject = new JSONObject() ;
        body = new String(body.getBytes("UTF-8"),"ISO-8859-1");
        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 openid = openId;//用户标识
        PaymentDto paymentPo = new PaymentDto();
        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<String, Object> 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 sPara = PayUtil.paraFilter(sParaTemp);
        String prestr = PayUtil.createLinkString(sPara); // 把数组所有元素,按照“参数=参数值”的模式用“&”字符拼接成字符串

        //MD5运算生成签名
        String mysign = PayUtil.sign(prestr, key, "utf-8").toUpperCase();
        paymentPo.setSign(mysign);
        //打包要发送的xml
        String respXml = XmlUtil.messageToXML(paymentPo);
        // 打印respXml发现,得到的xml中有“__”不对,应该替换成“_”
        respXml = respXml.replace("__", "_");
        String param = respXml;
        //String result = SendRequestForUrl.sendRequest(url, param);//发起请求
        String result = PayUtil.httpRequest(url, "POST", param);
        System.out.println("请求微信预支付接口,返回 result:"+result);
        // 将解析结果存储在Map中
        Map map = new HashMap();
        InputStream in=new ByteArrayInputStream(result.getBytes());
        // 读取输入流
        SAXReader reader = new SAXReader();
        Document document = reader.read(in);
        // 得到xml根元素
        Element root = document.getRootElement();
        // 得到根元素的所有子节点
        List<Element> elementList = root.elements();
        for (Element element : elementList) {
            map.put(element.getName(), element.getText());
        }
        // 返回信息
        String return_code = Common.getStringToObject(map.get("return_code"));//返回状态码
        String return_msg = Common.getStringToObject(map.get("return_msg"));//返回信息
        String result_code = Common.getStringToObject(map.get("result_code"));//返回状态码

        System.out.println("请求微信预支付接口,返回 code:" + return_code);
        System.out.println("请求微信预支付接口,返回 msg:" + return_msg);
        if("SUCCESS".equals(return_code) && "SUCCESS".equals(result_code)){
            // 业务结果
            String prepay_id = Common.getStringToObject(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);
        }
        return JsonObject;
    }


    /**
     * 预支付时填写的 notify_url ,支付成功后的回调接口
     * @param request
     */
    @RequestMapping("/weixin/paycallback.do")
    @ResponseBody
    public void paycallback(HttpServletRequest request) {
        try {
            Map<String, Object> dataMap = XmlUtil.parseXML(request);
            System.out.println(JSON.toJSONString(dataMap));
            //{"transaction_id":"4200000109201805293331420304","nonce_str":"402880e963a9764b0163a979a16e0002","bank_type":"CFT","openid":"oXI6G5Jc4D44y2wixgxE3OPwpDVg","sign":"262978D36A3093ACBE4B55707D6EA7B2","fee_type":"CNY","mch_id":"1491307962","cash_fee":"10","out_trade_no":"14913079622018052909183048768217","appid":"wxa177427bc0e60aab","total_fee":"10","trade_type":"JSAPI","result_code":"SUCCESS","time_end":"20180529091834","is_subscribe":"N","return_code":"SUCCESS"}
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

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

XmlUtil

package com.card.framework.utils;

import com.card.dto.PaymentDto;
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.core.util.QuickWriter;
import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
import com.thoughtworks.xstream.io.xml.PrettyPrintWriter;
import com.thoughtworks.xstream.io.xml.XppDriver;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.io.Writer;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class XmlUtil {
    public static Map<String,Object> parseXML(HttpServletRequest request) throws IOException, DocumentException {
        Map<String,Object> map=new HashMap<String,Object>();
        /* 通过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,Map<String,Object> map){
        //得到根节点的子节点列表
        List<Element> 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(text);
                    } else {
                        writer.write(text);
                    }
                }
            };
        }
    });
    public static String messageToXML(PaymentDto paymentPo){
        xstream.alias("xml",PaymentDto.class);
        return xstream.toXML(paymentPo);
    }
}

PaymentDto  //封装支付参数实体 

package com.card.dto;

import java.io.Serializable;

public class PaymentDto implements Serializable {
    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;//用户标识
    public String getAppid() {
        return appid;
    }
    public void setAppid(String appid) {
        this.appid = appid;
    }
    public String getMch_id() {
        return mch_id;
    }
    public void setMch_id(String mch_id) {
        this.mch_id = mch_id;
    }
    public String getNonce_str() {
        return nonce_str;
    }
    public void setNonce_str(String nonce_str) {
        this.nonce_str = nonce_str;
    }
    public String getSign() {
        return sign;
    }
    public void setSign(String sign) {
        this.sign = sign;
    }
    public String getBody() {
        return body;
    }
    public void setBody(String body) {
        this.body = body;
    }
    public String getOut_trade_no() {
        return out_trade_no;
    }
    public void setOut_trade_no(String out_trade_no) {
        this.out_trade_no = out_trade_no;
    }
    public String getTotal_fee() {
        return total_fee;
    }
    public void setTotal_fee(String total_fee) {
        this.total_fee = total_fee;
    }
    public String getNotify_url() {
        return notify_url;
    }
    public void setNotify_url(String notify_url) {
        this.notify_url = notify_url;
    }
    public String getTrade_type() {
        return trade_type;
    }
    public void setTrade_type(String trade_type) {
        this.trade_type = trade_type;
    }
    public String getOpenid() {
        return openid;
    }
    public void setOpenid(String openid) {
        this.openid = openid;
    }
    public String getSpbill_create_ip() {
        return spbill_create_ip;
    }
    public void setSpbill_create_ip(String spbill_create_ip) {
        this.spbill_create_ip = spbill_create_ip;
    }
    public String getDevice_info() {
        return device_info;
    }
    public void setDevice_info(String device_info) {
        this.device_info = device_info;
    }
    public String getDetail() {
        return detail;
    }
    public void setDetail(String detail) {
        this.detail = detail;
    }
    public String getAttach() {
        return attach;
    }
    public void setAttach(String attach) {
        this.attach = attach;
    }
    public String getFee_type() {
        return fee_type;
    }
    public void setFee_type(String fee_type) {
        this.fee_type = fee_type;
    }
    public String getTime_start() {
        return time_start;
    }
    public void setTime_start(String time_start) {
        this.time_start = time_start;
    }
    public String getTime_expire() {
        return time_expire;
    }
    public void setTime_expire(String time_expire) {
        this.time_expire = time_expire;
    }
    public String getGoods_tag() {
        return goods_tag;
    }
    public void setGoods_tag(String goods_tag) {
        this.goods_tag = goods_tag;
    }
    public String getLimit_pay() {
        return limit_pay;
    }
    public void setLimit_pay(String limit_pay) {
        this.limit_pay = limit_pay;
    }
}

PayUtil

package com.card.framework.utils;

import org.apache.commons.codec.digest.DigestUtils;

import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.*;

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));
        return mysign.equals(sign);
    }
    /**
     * @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);
        }
    }
    /**
     * 生成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;
        return (ch >= 0x4e00 && ch <= 0x7fff) || (ch >= 0x8000 && ch <= 0x952f);
    }
    /**
     * 除去数组中的空值和签名参数
     * @param sArray 签名参数组
     * @return 去掉空值与签名参数后的新签名参数组
     */
    public static Map paraFilter(Map<String,Object> sArray) {
        Map result = new HashMap();
        if (sArray == null || sArray.size() <= 0) {
            return result;
        }
        for (String key : sArray.keySet()) {
            String value = (String) 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 = (String) keys.get(i);
            String value = (String) 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 com.card.framework.utils;

import java.net.InetAddress;

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 + bytes[i];
//            result = (result << - Byte.MIN_VALUE + (int) bytes);
        }
        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();
    }
    /**
     * @param args
     */
    public static void main(String[] args) {
        String id="";
        UUIDHexGenerator uuid = UUIDHexGenerator.getInstance();
        /*
        for (int i = 0; i < 100; i++) {
            id = uuid.generate();
        }*/
        id = generate();
        System.out.println(id);
    }
}