最近公司需求,需要在微信公众号内完成支付,找到官方文档,文档还可以,讲的也挺详细,不过有一个地方很坑爹,就是微信内H5调起支付需要一个签名,而他给出的参考签名方式跟统一下单签名一致,害的我以为,他这个签名就是统一下单那个签名,后面找了很久看了好多博客才明白这个签名是怎么生成的(JS-SDK中微信支付有说明)。弄了半天,汗颜。下面进入正题。
微信支付分为很多种,有刷卡支付,公众号支付,扫码支付,APP支付,H5支付,小程序支付,本教程只讲解公众号支付。主要分三大块来讲解。
1.公众号支付介绍
微信支付只面向已认证客户,需要申请,申请成功后有个商家号,还要一个API密匙即key(微信商户平台(pay.weixin.qq.com)-->账户中心-->账户设置-->API安全-->密钥设置)。而公众号支付是用户在微信中打开商户的H5页面,商户在H5页面通过调用微信支付提供的JSAPI接口调起微信支付模块完成支付。应用场景有:
◆ 用户在微信公众账号内进入商家公众号,打开某个主页面,完成支付
◆ 用户的好友在朋友圈、聊天窗口等分享商家页面连接,用户点击链接打开商家页面,完成支付
◆ 将商户页面转换成二维码,用户扫描二维码后在微信浏览器中打开页面后完成支付
◆ 用户在微信公众账号内进入商家公众号,打开某个主页面,完成支付
◆ 用户的好友在朋友圈、聊天窗口等分享商家页面连接,用户点击链接打开商家页面,完成支付
◆ 将商户页面转换成二维码,用户扫描二维码后在微信浏览器中打开页面后完成支付
2.开发流程
在这里贴出官方文档地址https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=7_1。有兴趣的可以研究研究,贴出最主要的业务流程图:
啥,看不下去,那简单来说,你要掌握三点:
1.在商家后台设置支付目录, 设置路径:商户平台-->产品中心-->开发配置。填写你项目所在域名2.在公众号后台 设置授权域名,因为在统一下单接口中要求必传用户openid,而获取openid则需要您在公众平台设置获取openid的域名,只有被设置过的域名才是一个有效的获取openid的域名,否则将获取失败。3.调通统一下单与 支付结果通知接口(详细看demo)
tip:如果不知道如何获取openid,可以看本人另外一篇博客微信公众号开发《一》OAuth2.0网页授权认证获取用户的详细信息,实现自动登陆
3.demo展示
demo使用了包:xstream-1.3.1.jar,jdom.jar,用于把数据封装成xml格式与xml解析。首先我们把请求参数封装成类
/**
* 统一下单请求参数
* @author lhao
*
*/
public class UnifiedOrderRequest {
//变量名 字段名 必填 类型 示例值 描述
private String appid;// 公众账号ID 是 String(32) wxd678efh567hg6787 微信支付分配的公众账号ID(企业号corpid即为此appId)
private String mch_id;//商户号 必填 String(32) 1230000109 微信支付分配的商户号
private String device_info; //设备号 否 String(32) 013467007045764 自定义参数,可以为终端设备号(门店号或收银设备ID),PC网页或公众号内支付可以传"WEB"
private String nonce_str;//随机字符串 是 String(32) 5K8264ILTKCH16CQ2502SI8ZNMTM67VS 随机字符串,长度要求在32位以内。推荐随机数生成算法
private String sign;//签名 是 String(32) C380BEC2BFD727A4B6845133519F3AD6 通过签名算法计算得出的签名值,详见签名生成算法
private String sign_type;//签名类型 sign_type 否 String(32) HMAC-SHA256 签名类型,默认为MD5,支持HMAC-SHA256和MD5。
private String body;//商品描述 body 是 String(128) 腾讯充值中心-QQ会员充值 商品简单描述,该字段请按照规范传递,具体请见参数规定
private String detail;//商品详情 detail 否 String(6000) 单品优惠字段(暂未上线)
private String attach;//附加数据 attach 否 String(127) 深圳分店 附加数据,在查询API和支付通知中原样返回,可作为自定义参数使用。
private String out_trade_no;//商户订单号 out_trade_no 是 String(32) 20150806125346 商户系统内部订单号,要求32个字符内,只能是数字、大小写字母_-|*@ ,且在同一个商户号下唯一。详见商户订单号
private String fee_type;//标价币种 fee_type 否 String(16) CNY 符合ISO 4217标准的三位字母代码,默认人民币:CNY,详细列表请参见货币类型
private String total_fee;//标价金额 total_fee 是 Int 88 订单总金额,单位为分,详见支付金额
private String spbill_create_ip;//终端IP spbill_create_ip 是 String(16) 123.12.12.123 APP和网页支付提交用户端ip,Native支付填调用微信支付API的机器IP。
private String time_start;//交易起始时间 time_start 否 String(14) 20091225091010 订单生成时间,格式为yyyyMMddHHmmss,如2009年12月25日9点10分10秒表示为20091225091010。其他详见时间规则
private String time_expire;//交易结束时间 time_expire 否 String(14) 20091227091010 订单失效时间,格式为yyyyMMddHHmmss,如2009年12月27日9点10分10秒表示为20091227091010。其他详见时间规则 注意:最短失效时间间隔必须大于5分钟
private String goods_tag;//订单优惠标记 goods_tag 否 String(32) WXG 订单优惠标记,使用代金券或立减优惠功能时需要的参数,说明详见代金券或立减优惠
private String notify_url;//通知地址 notify_url 是 String(256) http://www.weixin.qq.com/wxpay/pay.php 异步接收微信支付结果通知的回调地址,通知url必须为外网可访问的url,不能携带参数。
private String trade_type;//交易类型 trade_type 是 String(16) JSAPI 取值如下:JSAPI,NATIVE,APP等,说明详见参数规定
private String product_id;//商品ID product_id 否 String(32) 12235413214070356458058 trade_type=NATIVE时(即扫码支付),此参数必传。此参数为二维码中包含的商品ID,商户自行定义。
private String limit_pay;//指定支付方式 limit_pay 否 String(32) no_credit 上传此参数no_credit--可限制用户不能使用信用卡支付
private String openid;//用户标识 openid 否 String(128) oUpF8uMuAJO_M2pxb1Q9zNjWeS6o trade_type=JSAPI时(即公众号支付),此参数必传,此参数为微信用户在商户对应appid下的唯一标识。openid如何获取,可参考【获取openid】。企业号请使用【企业号OAuth2.0接口】获取企业号内成员userid,再调用【企业号userid转openid接口】进行转换
//省略get,set方法
}
/**
* 统一下单返回参数
* @author lhao
*
*/
public class UnifiedOrderRespose {
private String return_code; //返回状态码
private String return_msg; //返回信息
private String appid; //公众账号ID
private String mch_id; //商户号
private String device_info; //设备号
private String nonce_str; //随机字符串
private String sign; //签名
private String result_code; //业务结果
private String err_code; //错误代码
private String err_code_des; //错误代码描述
private String trade_type; //交易类型
private String prepay_id; //预支付交易会话标识
private String code_url; //二维码链接
//省略get/set方法
}
/**
* 常量
*/
public class WXPayConstants {
public enum SignType {
MD5, HMACSHA256
}
public static final String FAIL = "FAIL";
public static final String SUCCESS = "SUCCESS";
public static final String HMACSHA256 = "HMAC-SHA256";
public static final String MD5 = "MD5";
public static final String FIELD_SIGN = "sign";
public static final String FIELD_SIGN_TYPE = "sign_type";
}
import java.security.MessageDigest;
/**
* MD5工具类
* @author lh
*/
public class MD5Util {
public final static String MD5(String s) {
char hexDigits[]={'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};
try {
byte[] btInput = s.getBytes();
// 鑾峰緱MD5鎽樿绠楁硶鐨�MessageDigest 瀵硅�?
MessageDigest mdInst = MessageDigest.getInstance("MD5");
// 浣跨敤鎸囧畾鐨勫瓧鑺傛洿鏂版憳瑕�?
mdInst.update(btInput);
// 鑾峰緱�?�嗘�?
byte[] md = mdInst.digest();
// 鎶婂瘑鏂囪浆鎹㈡垚鍗佸叚杩涘埗鐨勫瓧绗︿覆褰㈠紡
int j = md.length;
char str[] = new char[j * 2];
int k = 0;
for (int i = 0; i < j; i++) {
byte byte0 = md[i];
str[k++] = hexDigits[byte0 >>> 4 & 0xf];
str[k++] = hexDigits[byte0 & 0xf];
}
String md5Str = new String(str);
return md5Str;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.StringWriter;
import java.io.Writer;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.*;
import java.security.MessageDigest;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
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 wp.WXPayConstants.SignType;
import javax.crypto.Mac;
import javax.crypto.spec.Se