java微信支付创单APP、H5示例

以下是本人写的微信支付创单APP、H5的demo,没有什么封装,简易示例。
代码部分没有那么规范,只是练习使用的,例如:XMLUtil、MD5Encryption这样的命名不符合阿里P3C规约。望读者阅读后自行整改。

1.首先下载相关的包

log4j:请自行下载

jdom:https://mvnrepository.com/artifact/org.jdom/jdom2

如有其他缺少的包,请自行下载,下面主要看看代码部分。

2.WeChatPayUtil

package com.alipay.api.test.my;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.Logger;

import com.erlan.common.base.http.HttpRequest;
import com.erlan.common.util.MD5Encryption;
import com.erlan.common.util.XMLUtil;

/**
 * @Description: 微信支付工具类
 * @author admin
 * @date 2017年12月30日 下午1:55:21
 * @version V1.0
 */
public class WeChatPayUtil {

    private static Logger log = Logger.getLogger(WeChatPayUtil.class);

    public static void main(String[] args) {

        // APP支付测试
        Map<String, Object> appMap = unifiedorderAPP("1000010", "1", null, "这里填写统一下单回调接口(关于回调接口,下面有示例)");

        // H5支付测试(创单成功后,返回的url必须在H5申请的支付域名之下访问,这点要特别注意。而且我测试的是必须手机访问才成功,手机!手机!手机!三遍了,老铁们!),关于H5支付创单失败的问题,我其他文章有提到
        Map<String, Object> mwebMap = unifiedorderMWEB("1000012", "1", null, "这里填写统一下单回调接口(关于回调接口,下面有示例)");
    }

    /**
     * 统一下单(APP)
     *
     * @param outTradeNo 系统订单号
     * @param totalFee 金额(分)
     * @param attach 附加数据
     * @param notifyUrl 回调地址
     * @author admin
     * @date 2017年12月30日 下午2:18:29
     * @version V1.0
     */
    @SuppressWarnings("unchecked")
    public static Map<String, Object> unifiedorderAPP(String outTradeNo, String totalFee, String attach, String notifyUrl) {
        Map<String, Object> resultMap = new HashMap<String, Object>(8);
        resultMap.put("return_code", "FAIL");

        // ======================================
        // 以下有注释的代码,需要更改对应的值-Begin
        // ======================================

        // 连接商户key
        String key = "W8FFB955CA32319C3AAAE96286CC6666";

        // 统一下单接口
        String url = "https://api.mch.weixin.qq.com/pay/unifiedorder";
        // 应用ID(微信开放平台审核通过的应用APPID)
        String appid = "******************";
        // 商户号(微信支付分配的商户号)
        String mch_id = "2506666250";
        // 随便的一个MD5加密字符串就行(工具类有提供,见下面)
        String nonce_str = "49ba59abbe56e057";
        String sign = "";
        // 支付的商品信息(测试随便输入,支付后在微信里面看下付费账单信息就明白了)
        String body = "**-微信支付";
        String detail = "**-微信支付";
        String out_trade_no = outTradeNo;
        String total_fee = totalFee;
        // 终端IP(改成你的服务器IP就行)
        String spbill_create_ip = "10.10.10.110";
        String notify_url = notifyUrl;
        String trade_type = "APP";
        // 这个值可以不给,如果不给的话,下面不要拼接,我下面有拼接此参数,所以给它一个值,详情官方文档说明
        attach = StringUtils.isBlank(attach) ? "no" : attach;

        // ======================================
        // 以上有注释的代码,需要更改对应的值-End
        // ======================================

        // 对参数按照key=value的格式,并按照参数名ASCII字典序排序生成字符串
        String signStr = "appid=" + appid + "&attach=" + attach + "&body=" + body + "&detail=" + detail + "&mch_id=" + mch_id + "&nonce_str="
                + nonce_str + "&notify_url=" + notify_url + "&out_trade_no=" + out_trade_no + "&spbill_create_ip=" + spbill_create_ip + "&total_fee="
                + total_fee + "&trade_type=" + trade_type;
        // 连接商户key
        signStr += ("&key=" + key);
        System.out.println("signStr:" + signStr);
        // 生成sign并转成大写
        sign = MD5Encryption.toMD5(signStr).toUpperCase();
        System.out.println(sign);

        // 最终的提交xml:
        String params = "<xml><appid><![CDATA[" + appid + "]]></appid><attach><![CDATA[" + attach + "]]></attach><body><![CDATA[" + body
                + "]]></body><detail><![CDATA[" + detail + "]]></detail><mch_id><![CDATA[" + mch_id + "]]></mch_id><nonce_str><![CDATA[" + nonce_str
                + "]]></nonce_str><notify_url><![CDATA[" + notify_url + "]]></notify_url><out_trade_no><![CDATA[" + out_trade_no
                + "]]></out_trade_no><spbill_create_ip><![CDATA[" + spbill_create_ip + "]]></spbill_create_ip><total_fee><![CDATA[" + total_fee
                + "]]></total_fee><trade_type><![CDATA[" + trade_type + "]]></trade_type><sign>" + sign + "</sign></xml>";

        try {
            System.out.println(params);
            String result = HttpRequest.sendPost(url, params);
            System.out.println(result);
            Map<String, String> map = null;
            map = XMLUtil.doXMLParse(result);
            // System.out.println("return_code:" + map.get("return_code"));
            if (map.get("return_code").equals("SUCCESS")) {
                // 时间戳(秒)
                Date date = new Date();
                long time = date.getTime();
                time = (time / 1000);
                String timestamp = time + "";
                String resultAppid = map.get("appid").toString();
                String resultPartnerid = map.get("mch_id").toString();
                String resultPrepayid = map.get("prepay_id").toString();
                String resultPackage = "Sign=WXPay";
                String resultNoncestr = map.get("nonce_str").toString();
                // 对参数按照key=value的格式,并按照参数名ASCII字典序排序生成字符串
                String resultSignStr = "appid=" + resultAppid + "&noncestr=" + resultNoncestr + "&package=" + resultPackage + "&partnerid="
                        + resultPartnerid + "&prepayid=" + resultPrepayid + "&timestamp=" + timestamp;
                // 连接商户key
                resultSignStr += ("&key=" + key);
                System.out.println("resultSignStr:" + resultSignStr);
                // 生成sign并转成大写
                String resultSign = MD5Encryption.toMD5(resultSignStr).toUpperCase();
                System.out.println("resultSign:" + resultSign);
                // APP支付所需参数(需要把这些信息返给APP开发者)
                resultMap.put("return_code", "SUCCESS");
                resultMap.put("appid", resultAppid);
                resultMap.put("partnerid", resultPartnerid);
                resultMap.put("prepayid", resultPrepayid);
                resultMap.put("package", resultPackage);
                resultMap.put("noncestr", resultNoncestr);
                resultMap.put("timestamp", timestamp);
                resultMap.put("sign", resultSign);
                System.out.println(resultMap.toString());
            }
        }
        catch (Exception e) {
            // TODO Auto-generated catch block
            log.error("APP统一下单异常");
            e.printStackTrace();
        }

        return resultMap;
    }

    /**
     * 统一下单(H5)
     *
     * @param outTradeNo 系统订单号
     * @param totalFee 金额(分)
     * @param attach 附加数据
     * @param notifyUrl 回调地址
     * @author admin
     * @date 2017年12月30日 下午2:18:29
     * @version V1.0
     */
    @SuppressWarnings("unchecked")
    public static Map<String, Object> unifiedorderMWEB(String outTradeNo, String totalFee, String attach, String notifyUrl) {
        Map<String, Object> resultMap = new HashMap<String, Object>(8);
        resultMap.put("return_code", "FAIL");

        // ======================================
        // 以下有注释的代码,需要更改对应的值-Begin
        // ======================================

        // 连接商户key
        String key = "W8FFB955CA32319C3AAAE96286CC6666";

        // 统一下单接口
        String url = "https://api.mch.weixin.qq.com/pay/unifiedorder";
        // 应用ID(微信开放平台审核通过的应用APPID)
        String appid = "******************";
        // 商户号(微信支付分配的商户号)
        String mch_id = "2506666250";
        // 随便的一个MD5加密字符串就行(工具类有提供,见下面)
        String nonce_str = "49ba59abbe56e057";
        String sign = "";
        // 支付的商品信息(测试随便输入,支付后在微信里面看下付费账单信息就明白了)
        String body = "**-微信支付";
        String detail = "**-微信支付";
        String out_trade_no = outTradeNo;
        String total_fee = totalFee;
        // 终端IP(改成你的服务器IP就行)
        String spbill_create_ip = "10.10.10.110";
        String notify_url = notifyUrl;
        String trade_type = "APP";

        // 商户平台申请开通H5支付,对应的信息,详见官方文档(bundle_id的值在微信开放平台-管理中心-对应的产品里面查看)
        String scene_info = "{\"h5_info\": {\"type\":\"IOS\",\"app_name\": \"你的产品名称\",\"bundle_id\": \"cn.**.**\"}}";

        attach = StringUtils.isBlank(attach) ? "no" : attach;
        // ======================================
        // 以上有注释的代码,需要更改对应的值-End
        // ======================================

        // 对参数按照key=value的格式,并按照参数名ASCII字典序排序生成字符串
        String signStr = "appid=" + appid + "&attach=" + attach + "&body=" + body + "&detail=" + detail + "&mch_id=" + mch_id + "&nonce_str="
                + nonce_str + "&notify_url=" + notify_url + "&out_trade_no=" + out_trade_no + "&scene_info=" + scene_info + "&spbill_create_ip="
                + spbill_create_ip + "&total_fee=" + total_fee + "&trade_type=" + trade_type;
        // 连接商户key
        signStr += ("&key=" + key);
        System.out.println("signStr:" + signStr);
        // 生成sign并转成大写
        sign = MD5Encryption.toMD5(signStr).toUpperCase();
        System.out.println(sign);

        // 最终的提交xml:
        String params = "<xml><appid><![CDATA[" + appid + "]]></appid><attach><![CDATA[" + attach + "]]></attach><body><![CDATA[" + body
                + "]]></body><detail><![CDATA[" + detail + "]]></detail><mch_id><![CDATA[" + mch_id + "]]></mch_id><nonce_str><![CDATA[" + nonce_str
                + "]]></nonce_str><notify_url><![CDATA[" + notify_url + "]]></notify_url><out_trade_no><![CDATA[" + out_trade_no
                + "]]></out_trade_no><scene_info><![CDATA[" + scene_info + "]]></scene_info><spbill_create_ip><![CDATA[" + spbill_create_ip
                + "]]></spbill_create_ip><total_fee><![CDATA[" + total_fee + "]]></total_fee><trade_type><![CDATA[" + trade_type
                + "]]></trade_type><sign>" + sign + "</sign></xml>";

        try {
            System.out.println(params);
            String result = HttpRequest.sendPost(url, params);
            System.out.println(result);
            Map<String, String> map = null;
            map = XMLUtil.doXMLParse(result);
            System.out.println("return_code:" + map.get("return_code"));
            if (map.get("return_code").equals("SUCCESS")) {
                String mwebUrl = map.get("mweb_url").toString();
                // H5支付所需要的URL(必须在H5申请的支付域名之下访问,而且我测试的是必须手机访问才成功,手机!手机!手机!三遍了,老铁们!)
                resultMap.put("url", mwebUrl);
                resultMap.put("return_code", "SUCCESS");
                System.out.println(resultMap.toString());
            }
        }
        catch (Exception e) {
            // TODO Auto-generated catch block
            log.error("H5统一下单异常");
            e.printStackTrace();
        }

        return resultMap;
    }

}

3.MD5Encryption

package com.alipay.api.test.my;

import java.security.MessageDigest;

/**
 * @Description: MD5工具类
 * @author admin
 * @date 2018年1月12日 上午9:10:39
 * @version V1.0
 */
public class MD5Encryption {

    public static String toMD5(String plainText) {
        try {
            if ("".equals(plainText) || plainText == null) {
                return "erlan";
            }
            MessageDigest md = MessageDigest.getInstance("MD5");
            md.update(plainText.getBytes());
            byte b[] = md.digest();

            int i;
            StringBuffer buf = new StringBuffer("");
            for (int offset = 0; offset < b.length; offset++) {
                i = b[offset];
                if (i < 0) {
                    i += 256;
                }
                if (i < 16) {
                    buf.append("0");
                }
                buf.append(Integer.toHexString(i));
            }
            return buf.toString();
            // System.out.println("32λ: " + buf.toString());// 32位
            // System.out.println("16λ: " + buf.toString().substring(8, 24));// 16位
        }
        catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    // public static void main(String[] args) {
    // MD5Encryption md5 = new MD5Encryption();
    // String md52 = md5.toMD5("123456ERlan");
    // System.out.println(md52.toUpperCase());
    // }

}

4.XMLUtil

package com.erlan.common.util;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import org.jdom2.Document;
import org.jdom2.Element;
import org.jdom2.JDOMException;
import org.jdom2.input.SAXBuilder;

/**
 * @Description: XML解析工具类
 * @author admin
 * @date 2018年1月12日 上午9:10:10
 * @version V1.0
 */
public class XMLUtil {
    
    /**
     * 解析xml,返回第一级元素键值对。如果第一级元素有子节点,则此节点的值是子节点的xml数据。
     * 
     * @param strxml
     * @return
     * @throws JDOMException
     * @throws IOException
     */
    public static Map doXMLParse(String strxml) throws JDOMException, IOException {
        strxml = strxml.replaceFirst("encoding=\".*\"", "encoding=\"UTF-8\"");
        if (null == strxml || "".equals(strxml)) {
            return null;
        }

        Map m = new HashMap();

        InputStream in = new ByteArrayInputStream(strxml.getBytes("UTF-8"));
        SAXBuilder builder = new SAXBuilder();
        Document doc = builder.build(in);
        Element root = doc.getRootElement();
        List list = root.getChildren();
        Iterator it = list.iterator();
        while (it.hasNext()) {
            Element e = (Element) it.next();
            String k = e.getName();
            String v = "";
            List children = e.getChildren();
            if (children.isEmpty()) {
                v = e.getTextNormalize();
            } else {
                v = XMLUtil.getChildrenText(children);
            }

            m.put(k, v);
        }

        // 关闭流
        in.close();

        return m;
    }

    /**
     * 获取子结点的xml
     * 
     * @param children
     * @return String
     */
    public static String getChildrenText(List children) {
        StringBuffer sb = new StringBuffer();
        if (!children.isEmpty()) {
            Iterator it = children.iterator();
            while (it.hasNext()) {
                Element e = (Element) it.next();
                String name = e.getName();
                String value = e.getTextNormalize();
                List list = e.getChildren();
                sb.append("<" + name + ">");
                if (!list.isEmpty()) {
                    sb.append(XMLUtil.getChildrenText(list));
                }
                sb.append(value);
                sb.append("</" + name + ">");
            }
        }

        return sb.toString();
    }

    /**
     * 获取xml编码字符集
     * 
     * @param strxml
     * @return
     * @throws IOException
     * @throws JDOMException
     */
    public static String getXMLEncoding(String strxml) throws JDOMException, IOException {
        InputStream in = HttpClientUtil.String2Inputstream(strxml);
        SAXBuilder builder = new SAXBuilder();
        Document doc = builder.build(in);
        in.close();
        return (String) doc.getProperty("encoding");
    }

    /**
     * 支付成功,返回微信那服务器
     * 
     * @param return_code
     * @param return_msg
     * @return
     */
    public static String setXML(String return_code, String return_msg) {
        return "<xml><return_code><![CDATA[" + return_code + "]]></return_code><return_msg><![CDATA[" + return_msg + "]]></return_msg></xml>";
    }

    public static String createXML(Map<String, Object> map) {
        Set<Entry<String, Object>> set = map.entrySet();
        set.iterator();
        return null;
    }

}

4.WeChatController
这里要注意了!例如你的这个接口是针对APP支付成功后的业务处理,访问你这个接口的地址是:http://192.168.0.110:8080//wechat/successRechargeNotify
那么上面提到的unifiedorderAPP方法里的参数notifyUrl的值就是这个接口地址,例如:
unifiedorderAPP("1000010", "1", null, "http://192.168.0.110:8080//wechat/successRechargeNotify");

package com.erlan.mobile.controller.pay;

import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.log4j.Logger;
import org.jdom2.JDOMException;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.alibaba.dubbo.config.annotation.Reference;
import com.erlan.api.service.TbOrderService;
import com.erlan.api.service.TbUserService;
import com.erlan.common.util.XMLUtil;
import com.erlan.common.util.wxpay.WeChatPayConfig;

/**
 * @Description: 微信支付成功回调
 * @author admin
 * @date 2017年12月29日 上午9:19:25
 * @version V1.0
 */
@RestController
@RequestMapping("/wechat")
public class WeChatController {

    private static Logger log = Logger.getLogger(WeChatController.class);

    @Reference
    private TbOrderService tbOrderService;

    @Reference
    private TbUserService tbUserService;

    /**
     * 订单支付成功回调
     *
     * @param param
     * @return
     * @author admin
     * @date 2017年12月27日 下午2:21:53
     * @version V1.0
     */
    @SuppressWarnings({ "unchecked" })
    @RequestMapping("/successPayNotify")
    public String successPayNotify(HttpServletRequest request, HttpServletResponse response) {
        log.info("pay......success");
        String notifyResult = "<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>";

        try {
            System.out.println("微信支付回调");
            // PrintWriter writer = response.getWriter();
            InputStream inStream = request.getInputStream();
            ByteArrayOutputStream outSteam = new ByteArrayOutputStream();
            byte[] buffer = new byte[1024];
            int len = 0;
            while ((len = inStream.read(buffer)) != -1) {
                outSteam.write(buffer, 0, len);
            }
            outSteam.close();
            inStream.close();
            String result = new String(outSteam.toByteArray(), "utf-8");
            System.out.println("微信支付通知结果:" + result);
            Map<String, String> map = null;
            try {
                /**
                 * 解析微信通知返回的信息
                 */
                map = XMLUtil.doXMLParse(result);
            }
            catch (JDOMException e) {
                // TODO Auto-generated catch block
                log.error("微信支付回调解析异常");
                e.printStackTrace();
            }
            System.out.println("=========:" + result);
            // 若支付成功,则告知微信服务器收到通知
            if (map.get("return_code").equals("SUCCESS")) {

                // 这里可以写你的系统业务逻辑......

            }
        }
        catch (Exception e) {
            // TODO: handle exception
            log.error("微信支付回调异常");
        }

        System.out.println("notifyResult:" + notifyResult);
        return notifyResult;
    }
}

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350