zoukankan      html  css  js  c++  java
  • 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 = map.get("return_code").toString();//返回状态码
    String return_msg = map.get("return_msg").toString();//返回信息
    String result_code = map.get("result_code").toString;//返回状态码
    
    System.out.println("请求微信预支付接口,返回 code:" + return_code);
    System.out.println("请求微信预支付接口,返回 msg:" + return_msg);
    if("SUCCESS".equals(return_code) && "SUCCESS".equals(result_code)){
    // 业务结果
    String prepay_id = map.get("prepay_id").toString();//返回的预付单信息
    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);
    }
    }
  • 相关阅读:
    [最短路径SPFA] POJ 1847 Tram
    [强连通分量] POJ 1236 Network of Schools
    [强连通分量] POJ 2762 Going from u to v or from v to u?
    [强连通分量] POJ 2186 Popular Cows
    [宽度优先搜索] HDU 1372 Knight Moves
    php错误级别和异常处理
    php配置优化
    魔术方法和魔术常量
    事务
    mysql备份和还原
  • 原文地址:https://www.cnblogs.com/tomingto/p/12133781.html
Copyright © 2011-2022 走看看