文档章节

APP调起微信支付╮( ̄▽ ̄")╭ (JAVA服务端开发)

保护单身狗协会理事长
 保护单身狗协会理事长
发布于 2015/04/21 18:40
字数 1356
阅读 343
收藏 10

最近在做APP调起微信支付,鉴于文档之坑爹,特此记录,希望对大家有所帮助。

1、第一步,先把相关配置弄到一个类里面,方便以后使用。

public final class WeChatConfig {
    public final static String TRADE_TYPE = "APP";//支付方式
    public final static String CHARSET = "UTF-8";//编码格式
    public final static String APP_KEK = ""; //api 秘钥
    public final static String APP_ID = "";//appid
    public final static String MCH_ID = "";//商户号
}

2、生成待支付订单,微信支付的套路是酱紫的,先生成一笔待付款的订单,然后把待付款订单的ID和其他一堆数据抛给前台,然后APP发起付款。生成预支付订单的方式,请参见文档,这块写的还是挺清楚的,这里只是提供一下具体的代码。这里有个坑,后台发送HTTPS请求生成预支付订单的时候,必须把发送的内容重新编码为ISO-8859-1,否则APP支付的时候显示的中文都是乱码,对于微信返回的预支付订单的信息,必须用UTF-8重新编码,否则中文也是乱码。

import org.apache.commons.lang3.StringUtils;
import org.dom4j.Document;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;

import java.io.UnsupportedEncodingException;
import java.util.*;
import java.util.regex.Pattern;

public class PayCore {
    //按ASCII码表正序,然后生成链接
    public static String sortAndCreateLink(SortedMap<String, String> params) throws UnsupportedEncodingException {
        Set<Map.Entry<String, String>> es = params.entrySet();
        return createUrlPara(es.iterator());
    }

    private final static Pattern pattern = Pattern.compile("[\\u4e00-\\u9fa5]");
    //把Map拼装成xml string
    public static String toXml(Map<String, String> map) throws UnsupportedEncodingException {
        Set<Map.Entry<String, String>> es = map.entrySet();
        Iterator<Map.Entry<String, String>> iterator = es.iterator();
        StringBuffer buffer = new StringBuffer();
        buffer.append("<xml>");
        while (iterator.hasNext()) {
            Map.Entry<String, String> entry = iterator.next();
            if (StringUtils.isBlank(entry.getValue())) continue;
            buffer.append(String.format("<%s>", entry.getKey()));
            buffer.append(String.format("<![CDATA[%s]]>", entry.getValue()));
            buffer.append(String.format("</%s>", entry.getKey()));
        }
        buffer.append("</xml>");
        return buffer.toString();
    }

    //把xml解析成Map
    public static Map<String, String> fromXml(String xml) throws UnsupportedEncodingException {
        Map map = new HashMap();
        try {
            Document document = DocumentHelper.parseText(xml);
            Element element = document.getRootElement();
            Iterator iterator = element.elementIterator();
            while (iterator.hasNext()) {
                Element childElement = (Element) iterator.next();
                map.put(childElement.getName(), childElement.getText());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return map;
    }

    private static String createUrlPara(Iterator<Map.Entry<String, String>> iterator) throws UnsupportedEncodingException {
        StringBuffer stringBuffer = new StringBuffer();
        while (iterator.hasNext()) {
            Map.Entry<String, String> entry = iterator.next();
            stringBuffer.append(entry.getKey());
            stringBuffer.append("=");
            stringBuffer.append(entry.getValue());
            stringBuffer.append("&");
        }
        String value = stringBuffer.toString();
        return value.substring(0, value.lastIndexOf("&"));
    }
}
import org.apache.commons.lang3.StringUtils;
//配置回调地址
public class Callback {
    private String callback;
    private String notify;

    public Callback() {
        this.callback = StringUtils.EMPTY;
        this.notify = StringUtils.EMPTY;
    }

    public String getCallback() {
        return callback;
    }

    public void setCallback(String callback) {
        this.callback = callback;
    }

    public String getNotify() {
        return notify;
    }

    public void setNotify(String notify) {
        this.notify = notify;
    }
}

import com.jiayukang.common.pay.Pay;
import com.jiayukang.common.pay.dao.PayRecordDao;
import com.jiayukang.common.pay.dao.model.PayMethod;
import com.jiayukang.common.pay.dao.model.PayRecord;
import com.jiayukang.common.pay.exception.PayFailException;
import com.jiayukang.common.pay.model.Callback;
import com.jiayukang.common.utils.http.Https;
import com.jiayukang.common.utils.sign.MD5;
import com.jiayukang.common.utils.PayCore;
import com.jiayukang.common.utils.UUIDGenerator;
import org.apache.commons.lang3.StringUtils;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.io.SAXReader;
import org.xml.sax.InputSource;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.InetAddress;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.*;

public final class WeChartPay{
    private Callback callback;

    public WeChartPay(Callback callback) {
        this.callback = callback;
    }

    private final static String SIGN = "sign";
    private final static String REQUEST_URL = "https://api.mch.weixin.qq.com/pay/unifiedorder";
    private final static NumberFormat numberFormat = new DecimalFormat("#");//格式化数字为

    public Map pay(String orderNumber, String productName, Float price) throws KeyManagementException, NoSuchAlgorithmException, DocumentException, IOException {
        try {
            InetAddress addr = InetAddress.getLocalHost();
            Map orderInfo = new HashMap();

            orderInfo.put("appid", WeChartConfig.APP_ID);//
            orderInfo.put("mch_id", WeChartConfig.MCH_ID); //商户号
            orderInfo.put("nonce_str", UUIDGenerator.generator());//随机数,保证请求唯一性
            orderInfo.put("out_trade_no", orderNumber);//商户订单号
            orderInfo.put("body", productName); //商品描述
            orderInfo.put("total_fee", numberFormat.format(price)); //总金额,单位为分,不能有小数部分,只能是正整数
            orderInfo.put("spbill_create_ip", addr.getHostAddress().toString());//你的服务器的IP
            orderInfo.put("notify_url", callback.getNotify());//支付成功后异步回调的方法
            orderInfo.put("trade_type", WeChartConfig.TRADE_TYPE);//支付类型,之前在WeChatConfig里配置的
            orderInfo.put("sign", sign(orderInfo));//签名,╮( ̄▽ ̄")╭ ,讨厌的玩意

            String xml = PayCore.toXml(orderInfo);
            String resultXml = Https.post(REQUEST_URL, new String(xml.getBytes(WeChartConfig.CHARSET), "ISO-8859-1"));//这里发送请求的时候,一定要
            return analyzeReturnValue(new String(resultXml.getBytes("ISO-8859-1"), WeChartConfig.CHARSET));
        } catch (Exception ex) {
            throw ex;
        }
    }

    private String sign(Map map) throws UnsupportedEncodingException {
        String pack = PayCore.sortAndCreateLink(new TreeMap<String, String>(map));
        String signValue = MD5.sign(pack, "&key=" + WeChartConfig.APP_KEK, WeChartConfig.CHARSET).toUpperCase();
        return signValue;
    }

    private Map analyzeReturnValue(String resultXml) throws UnsupportedEncodingException, DocumentException {
        SAXReader saxReader = new SAXReader();
        Document doc = saxReader.read(new InputSource(new ByteArrayInputStream(resultXml.getBytes(WeChartConfig.CHARSET))));
        String returnValue = doc.selectSingleNode("/xml/return_code").getText();
        Map<String, String> map = new HashMap<>();
        if (SUCCESS.equals(returnValue)) {
            String resultValue = doc.selectSingleNode("/xml/result_code").getText();
            if (SUCCESS.equals(resultValue)) {
                map.put("appid", WeChartConfig.APP_ID);
                map.put("partnerid", WeChartConfig.MCH_ID);
                map.put("prepayid", doc.selectSingleNode("/xml/prepay_id").getText());//微信返回的预支付订单
                map.put("package", "Sign=WXPay");
                map.put("noncestr", UUIDGenerator.generator());//随机数,防止唯一
                map.put("timestamp", String.valueOf(new Date().getTime() / 1000));//这里返回的是秒
                map.put("sign", sign(map));//签名
            }
        }
        return map;
    }
    //验证返回值
    public String check(Map map) throws PayFailException {
        if (isTenpaySign(new TreeMap(map))) {
            String orderNumber = (String) map.get("out_trade_no");//商户订单号
            String tradeNumber = (String) map.get("transaction_id");//交易流水号
 
            if (!StringUtils.isBlank(orderNumber) && !StringUtils.isBlank(tradeNumber))
                //.dosth
            } else {
                throw new PayFailException("签名验证失败");
            }
    }
    
    //验证微信支付回调返回值签名
    private Boolean isTenpaySign(SortedMap map) {
        StringBuffer sb = new StringBuffer();
        Set es = map.entrySet();
        Iterator it = es.iterator();
        while (it.hasNext()) {
            Map.Entry entry = (Map.Entry) it.next();
            String k = (String) entry.getKey();
            String v = (String) entry.getValue();
            if (!SIGN.equals(k) && !StringUtils.isBlank(v)) {
                sb.append(k + "=" + v + "&");
            }
        }
        String mySign = MD5.sign(sb.toString(), "key=" + WeChartConfig.APP_KEK, WeChartConfig.CHARSET).toLowerCase();
        String tenpaySign = map.get(SIGN).toString().toLowerCase();
        return tenpaySign.equals(mySign);
    }
}

 3、支付成功后,会异步回调notify_url指定的地址,微信会把文档上写的那些值用xml的格式POST过来。╮( ̄▽ ̄")╭ ,所以,我们得解析XML,方法如下。

private final static Logger logger = LoggerFactory.getLogger("PayLogger");

@RequestMapping(value = "wechart/notify.do", method = RequestMethod.POST)
public void weChartPayNotify(HttpServletRequest request, HttpServletResponse response) throws IOException {
    BufferedReader bufferedReader = request.getReader();
    StringBuffer contentBuffer = new StringBuffer();
    String line = null;
    while (!StringUtils.isBlank((line = bufferedReader.readLine()))) {
        contentBuffer.append(line);
    }
    String content = contentBuffer.toString();//以上代码用来获取content.

    Map map = null;
    try {
        map = xmlToMap(content);
    } catch (DocumentException e) {
        logger.error(exceptionToString(e));
    }

    logger.info(content);
    try {
        weChartPay.check(map);
        response.getOutputStream().println("Success");
    } catch (PayFailException iex) {
        response.getOutputStream().println("Fail");
    } catch (Exception ex) {
        response.getOutputStream().println("Fail");
    }
    response.getOutputStream().flush();
    response.getOutputStream().close();
}
//似乎之前写过了..?忘了,再来一百年吗,XML TO OBJECT
private Map xmlToMap(String xml) throws DocumentException {
    Map map = new HashMap();
    Document document = DocumentHelper.parseText(xml);
    Element element = document.getRootElement();
    Iterator iterator = element.elementIterator();
    while (iterator.hasNext()) {
        Element childElement = (Element) iterator.next();
        map.put(childElement.getName(), childElement.getText());
    }
    return map;
}
//把堆栈信息转换成字符串
private String exceptionToString(Exception ex) {
    StringWriter stringWriter = new StringWriter();
    PrintWriter writer = new PrintWriter(stringWriter);
    ex.printStackTrace(writer);
    StringBuffer buffer = stringWriter.getBuffer();
    return buffer.toString();
}

╮( ̄▽ ̄")╭ ,如上,就是微信支付的整个流程,用到了的jar如下。

<dependency>
    <groupId>dom4j</groupId>
    <artifactId>dom4j</artifactId>
    <version>1.6.1</version>
</dependency>

<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
    <version>4.3.1</version>
</dependency>

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>3.3.2</version>
</dependency>

应该就这些了吧╮( ̄▽ ̄")╭ ,如果缺点啥,那个,大家克服下...

© 著作权归作者所有

共有 人打赏支持
保护单身狗协会理事长
粉丝 20
博文 27
码字总数 10638
作品 0
杭州
程序员
私信 提问
加载中

评论(4)

王了个伟
王了个伟
能不能加个qq
1365935941一个来自深圳的程序员
疯狂的逍遥
疯狂的逍遥

引用来自“记录生活”的评论

引用来自“疯狂的逍遥”的评论

能留下QQ 不

你也在做么?
都完成好久了
记录生活
记录生活

引用来自“疯狂的逍遥”的评论

能留下QQ 不

你也在做么?
疯狂的逍遥
疯狂的逍遥
能留下QQ 不
微信app支付php服务端轮子

本帖 部分为转贴 顺便吐槽一下 微信的开发者文档真是坑啊 首先作为服务端 你要了解 整个APP 支付流程 看文档 https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=83 然后 下载官方...

hfisop
2017/03/28
0
0
CraftyJS有人用过这个JS游戏引擎吗?

目前有个问题,我用0.4.2版本然后click的回调里无法调用Crafty.scene("main"),其他的都可以?有了解的吗? PS:希望红薯收录这个开源JS引擎,因为网站是要那啥的,你懂得,╮( ̄▽ ̄")╭ 地址...

xu81.com
2011/09/06
1K
0
+新建任务有问题:;(∩´﹏`∩);:

新建任务时,如果点击选择“选择项目”选取当前进行的项目后,则无法选择选择任务标签,指派人员只能选择项目创建者,如下图: 如果选择默认的“不指定项目”,则可以勾选任务标签、指派人员...

zmldo
2015/06/05
1
0
Java 工具集 Hutool 4.0.10 发布,bug 修复

Hutool是一个Java工具包,也只是一个工具包,它帮助我们简化每一行代码,减少每一个方法,让Java语言也可以“甜甜的”。Hutool最初是我项目中“util”包的一个整理,后来慢慢积累并加入更多非...

路小磊
04/17
776
6
请问HP-Socket怎么解决粘包拆包半包问题?

您好,请问用HP-Socket如何解决TCP通讯中的粘包拆包半包问题? 比如消息是类似FTP协议一样以换行符(或者是特殊字符)作为结尾的包,或者是定长数据。 有什么地方可以方便的加入自己的Decod...

nanxiaoqiang
2014/06/19
1K
2

没有更多内容

加载失败,请刷新页面

加载更多

CentOS配置Tomcat监听80端口,虚拟主机

Tomcat更改默认端口为80 更改的配置文件是: /usr/local/tomcat/conf/server.xml [root@test-a ~]# vim /usr/local/tomcat/conf/server.xml # 找到 Connector port="8080" protocol="HTTP/1......

野雪球
今天
5
0
《稻盛和夫经营学》读后感心得体会3180字范文

《稻盛和夫经营学》读后感心得体会3180字范文: 一代日本经营之圣稻盛和夫凭借刻苦勤奋的精神以及深植于佛教的商业道德准则,成为了“佛系”企业家的代表人物。在《稻盛和夫经营学》“领导人...

原创小博客
今天
3
0
java框架学习日志-5(常见的依赖注入)

依赖注入(dependency injection) 之前提到控制反转(Inversion of Control)也叫依赖注入,它们其实是一个东西,只是看的角度不同,这章详细说一下依赖注入。 依赖——指bean对象创建依赖于...

白话
今天
4
0
红外接收器驱动开发

背景:使用系统的红外遥控软件没有反应,然后以为自己接线错误,反复测试,结果烧坏了一个红外接收器,信号主板没有问题。所以自己开发了一个红外接收器的python驱动。接线参见https://my.os...

mbzhong
今天
2
0
ActiveMQ消息传送机制以及ACK机制详解

AcitveMQ是作为一种消息存储和分发组件,涉及到client与broker端数据交互的方方面面,它不仅要担保消息的存储安全性,还要提供额外的手段来确保消息的分发是可靠的。 一. ActiveMQ消息传送机...

watermelon11
今天
2
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部