前言:接到产品的一个需求,使用场景具体为生成的二维码进行宣传,用户扫码关注公众号之后,公众号会推送一条图文消息,用户点击则会跳转到事先准备好的支付选择页面,选择支付金额拉起微信支付控件, 输入密码进行支付。
仔细看微信公众号支付文档!仔细看微信公众号支付文档!仔细看微信公众号支付文档!重要的事情说三遍。因为本人没仔细看官方文档,所以走了不少弯路。 以下是公众号支付官方地址: 公众号支付
- 公众号开发前配置
本人测试微信公众号支付目录只需要项目url的第一层路径即可,如上图。另外说一点给小白,关注公众号后,系统发送图文消息是可以拿到用户对应的当前公众号的openid,所以这里不需要再使用用户授权等这样的操作了。
2.微信统一下单 微信统一下单接口说明 这里主要说明几点,在调起微信统一下单之前,我们自己的系统数据库是需要有一个预支付的订单号的。另外需要关注几个比较重要的字段,公众账号ID AppID 查看地址(开发-基本配置)。商户ID mch_id查看地址(微信支付-商户信息-基本信息)。trade_type(交易类型,公众号必须选择填写"JSAPI")。另附上代码一段供大家参考。特别注意:文章中两处签名的方法内wxpayApiKey变量的值也就是官方签名中注释(key为商户平台设置的密钥key)这个密钥key是需要你登陆微信支付商户平台中获取的.如图
这个是微信支付pojo类
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlSchemaType;
@XmlAccessorType(XmlAccessType.PROPERTY)
@XmlRootElement(name = "xml")
public class WxOrderVO {
protected String appid;//公众账号ID
protected String mch_id;//商户号
protected String attach;//附加信息
protected String device_info;//设备号
protected String nonce_str;//随机字符串
protected String sign;//签名
protected String body;//商品描述
protected String detail;//商品详情
protected String out_trade_no;//商户订单号
protected String fee_type;//货币类型
protected int total_fee;//总金额
protected String spbill_create_ip;//终端IP
protected String time_start;//交易起始时间
protected String time_expire;//交易结束时间
protected String goods_tag;//商品标记
protected String notify_url;//通知地址
protected String trade_type;//交易类型
protected String product_id;//商品ID
protected String limit_pay;//指定支付方式
protected String openid;//用户标识
protected String result_code;
protected String err_code;
protected String err_code_des;
protected String is_subscribe;
protected String bank_type;
protected int cash_fee;
protected String cash_fee_type;
protected Integer coupon_fee;
protected String coupon_id_0;
protected String coupon_fee_0;
protected int coupon_count;
protected String transaction_id;
protected String time_end;
protected String trade_state;
protected String return_code;
protected String return_msg;
protected String prepay_id;//预付订单ID
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 getAttach() {
return attach;
}
public void setAttach(String attach) {
this.attach = attach;
}
public String getDevice_info() {
return device_info;
}
public void setDevice_info(String device_info) {
this.device_info = device_info;
}
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 getDetail() {
return detail;
}
public void setDetail(String detail) {
this.detail = detail;
}
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 int getTotal_fee() {
return total_fee;
}
public void setTotal_fee(int total_fee) {
this.total_fee = total_fee;
}
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 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 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 getProduct_id() {
return product_id;
}
public void setProduct_id(String product_id) {
this.product_id = product_id;
}
public String getLimit_pay() {
return limit_pay;
}
public void setLimit_pay(String limit_pay) {
this.limit_pay = limit_pay;
}
public String getOpenid() {
return openid;
}
public void setOpenid(String openid) {
this.openid = openid;
}
public String getFee_type() {
return fee_type;
}
public void setFee_type(String fee_type) {
this.fee_type = fee_type;
}
public String getResult_code() {
return result_code;
}
public void setResult_code(String result_code) {
this.result_code = result_code;
}
public String getErr_code() {
return err_code;
}
public void setErr_code(String err_code) {
this.err_code = err_code;
}
public String getErr_code_des() {
return err_code_des;
}
public void setErr_code_des(String err_code_des) {
this.err_code_des = err_code_des;
}
public String getIs_subscribe() {
return is_subscribe;
}
public void setIs_subscribe(String is_subscribe) {
this.is_subscribe = is_subscribe;
}
public String getBank_type() {
return bank_type;
}
public void setBank_type(String bank_type) {
this.bank_type = bank_type;
}
public int getCash_fee() {
return cash_fee;
}
public void setCash_fee(int cash_fee) {
this.cash_fee = cash_fee;
}
public String getCash_fee_type() {
return cash_fee_type;
}
public void setCash_fee_type(String cash_fee_type) {
this.cash_fee_type = cash_fee_type;
}
public Integer getCoupon_fee() {
return coupon_fee;
}
public void setCoupon_fee(Integer coupon_fee) {
this.coupon_fee = coupon_fee;
}
public String getCoupon_id_0() {
return coupon_id_0;
}
public void setCoupon_id_0(String coupon_id_0) {
this.coupon_id_0 = coupon_id_0;
}
public String getCoupon_fee_0() {
return coupon_fee_0;
}
public void setCoupon_fee_0(String coupon_fee_0) {
this.coupon_fee_0 = coupon_fee_0;
}
public String getTransaction_id() {
return transaction_id;
}
public void setTransaction_id(String transaction_id) {
this.transaction_id = transaction_id;
}
public String getTime_end() {
return time_end;
}
public void setTime_end(String time_end) {
this.time_end = time_end;
}
public String getTrade_state() {
return trade_state;
}
public void setTrade_state(String trade_state) {
this.trade_state = trade_state;
}
public String getReturn_code() {
return return_code;
}
public void setReturn_code(String return_code) {
this.return_code = return_code;
}
public String getReturn_msg() {
return return_msg;
}
public void setReturn_msg(String return_msg) {
this.return_msg = return_msg;
}
public int getCoupon_count() {
return coupon_count;
}
public void setCoupon_count(int coupon_count) {
this.coupon_count = coupon_count;
}
public String getPrepay_id() {
return prepay_id;
}
public void setPrepay_id(String prepay_id) {
this.prepay_id = prepay_id;
}
@Override
public String toString() {
return "out_trade_no="+ out_trade_no + ",sign=" + sign;
}
}
下面一段是统一支付订单基本参数组装,注意该对象中setNotify_url方法,这里需要你的项目中已经提供微信能够回调通知你服务器的url地址.
WxOrderVO wxOrderVO = new WxOrderVO();
wxOrderVO.setAppid(SystemDefine.getInstance().wxpayAppId);
wxOrderVO.setMch_id(SystemDefine.getInstance().wxpayMchId);
wxOrderVO.setAttach(product.getName());
wxOrderVO.setBody("xxxxxx服务");
String nonceStr = StringUtil.genRandomStr(30);
wxOrderVO.setNonce_str(nonceStr);
wxOrderVO.setNotify_url(SystemDefine.getInstance().wxpayNotifyUrl);
wxOrderVO.setTotal_fee(userOrder.getPayPrice());
wxOrderVO.setOut_trade_no(orderNo);
wxOrderVO.setTrade_type("JSAPI");
wxOrderVO.setOpenid(openId);
String sign = WxPay.getSign(wxOrderVO);//签名处
wxOrderVO.setSign(sign);
再附上WxPay.getSign签名方法具体代码
public static String getSign(WxOrderVO wxOrder) {
String jsonString = JSONObject.toJSONString(wxOrder);
JSONObject jsonObject = JSONObject.parseObject(jsonString);
TreeMap<String, Object> treeMap = new TreeMap<String, Object>();
treeMap.putAll(jsonObject);
StringBuffer stringA = new StringBuffer();
for (Object key : treeMap.keySet()) {
// sign 字段不参与验证
if (!String.valueOf(key).equals("sign")) {
String value = String.valueOf(jsonObject.get(key));
if (StringUtils.isNotBlank(value) && !"null".equals(value) && !"0".equals(value)) {
stringA.append(key).append("=").append(value).append("&");
}
}
}
stringA.append("key=").append(SystemDefine.getInstance().wxpayApiKey);
return MD5Class.MD5Encode(stringA.toString()).toUpperCase();
}
下面向微信服务器下单
/**
* 向微信服务器下单
*
* @param wxOrder
* @return
* @throws Exception
*/
public static String postUnifiedorder(WxOrderVO wxOrder) throws Exception {
HttpClient client = new HttpClient();
PostMethod method = new PostMethod(SystemDefine.getInstance().wxpayServerUrl);
method.setRequestHeader("Content-type", "application/xml;charset=UTF-8");
String xml = wxOrder2XML(wxOrder);
String result = null;
try {
method.setRequestEntity(new StringRequestEntity(xml, "application/xml", "utf-8"));
client.executeMethod(method);
if (method.getStatusCode() == HttpStatus.SC_OK) {
result =
new String(method.getResponseBodyAsString().getBytes("iso-8859-1"), "utf-8");
}
} catch (Exception e) {
throw e;
} finally {
method.releaseConnection();
}
return result;
}
public static String wxOrder2XML(WxOrderVO wxOrder) {
StringBuffer xml = new StringBuffer("<xml>");
String jsonString = JSONObject.toJSONString(wxOrder);
JSONObject jsonObject = JSONObject.parseObject(jsonString);
for (Object key : jsonObject.keySet()) {
String value = String.valueOf(jsonObject.get(key));
if (StringUtils.isNotBlank(value) && !"null".equals(value) && !"0".equals(value)) {
xml.append("<").append(key).append(">").append(value).append("</").append(key)
.append(">");
}
}
return xml.append("</xml>").toString();
}
到这里如果统一下单成功微信会返回给一段xml格式的字符串,里面有订单信息.拿到微信服务器的预付订单ID则可以继续前进。如果有错误请查看官方错误码 错误码查询
下面是将返回的数据进行类型转换,并且获取到预付订单ID。(这里需要结合上面代码放在一起)这里需要进行二次签名,微信需要确认是否正确,防止安全及数据问题。
String result = WxPay.postUnifiedorder(wxOrderVO);
JSONObject json = WxPay.xml2Json(result);
String prepayId = json.getString("prepay_id");
SortedMap<String, String> finalpackage = new TreeMap<String, String>();
long timeStamp = System.currentTimeMillis()/1000;
String prepayId = json.getString("prepay_id");
// 加密
String packages = "prepay_id="+prepayId;
finalpackage.put("appId", SystemDefine.getInstance().wxpayAppId);
finalpackage.put("timeStamp", String.valueOf(timeStamp));
finalpackage.put("nonceStr", nonceStr);
finalpackage.put("package", packages);
finalpackage.put("signType", "MD5");
String finalsign = WeiXinUtils.createSign(finalpackage);
下面代码对应 WeiXinUtils.createSign方法
/**
* 创建md5摘要,规则是:按参数名称a-z排序,遇到空值的参数不参加签名。
*/
@SuppressWarnings("rawtypes")
public static String createSign(SortedMap<String, String> packageParams) {
StringBuffer sb = new StringBuffer();
Set es = packageParams.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 (null != v && !"".equals(v) && !"sign".equals(k)
&& !"key".equals(k)) {
sb.append(k + "=" + v + "&");
}
}
sb.append("key=").append(SystemDefine.getInstance().wxpayApiKey);
String sign = MD5Class.MD5Encode(sb.toString()).toUpperCase();
return sign;
}
最终上面的finalsign会被用到前端html页面中也就是官方文档中H5调起微信的步骤 H5调起微信支付 附上楼主H5页面中JS代码:
var appId;
var timeStampStr;
var nonceStr;
var packageStr;
var paySign;
var orderNo;
function toPay(){
var userId = $("#userId").val();
var productId = $("#productId").val();
var openId = $("#openId").val();
var wxUnionId = $("#wxUnionId").val();
var serialNum = $("#serialNum").val();
var params = {};
params.userId=userId;
params.productId=productId;
params.openId=openId;
params.wxUnionId=wxUnionId;
params.serialNum=serialNum;
$.ajax({
async:false,
type: "POST",
url: "/scanScene/toPay",
data:params,
dataType:"json",
success:function(data){
if(data.res_code == 0){
appId = data.appId;
timeStampStr = data.timeStamp;
nonceStr = data.nonceStr;
packageStr = data.packageStr;
paySign = data.paySign;//此处的值对应文章代码中字符串类型finalsign变量
orderNo = data.orderNo;
if (typeof WeixinJSBridge == "undefined"){
if(document.addEventListener){
document.addEventListener('WeixinJSBridgeReady', onBridgeReady, false);
}else if (document.attachEvent){
document.attachEvent('WeixinJSBridgeReady', onBridgeReady);
document.attachEvent('onWeixinJSBridgeReady', onBridgeReady);
}
}else{
onBridgeReady();
}
}else{
}
},
error:function(data){
alert(data.res_code);
}
});
}
function onBridgeReady(){
WeixinJSBridge.invoke(
'getBrandWCPayRequest', {
"appId":appId, //公众号名称,由商户传入
"timeStamp":timeStampStr, //时间戳,自1970年以来的秒数
"nonceStr":nonceStr, //随机串
"package":packageStr,
"signType":"MD5", //微信签名方式:
"paySign":paySign //微信签名
},
function(res){
// 使用以上方式判断前端返回,微信团队郑重提示:res.err_msg将在用户支付成功后返回 ok,但并不保证它绝对可靠。
if(res.err_msg == "get_brand_wcpay_request:ok" ) {
var userId = $("#userId").val();
var productId = $("#productId").val();
var serialNum = $("#serialNum").val();
var openId = $("#openId").val();
window.location.href = "/scanScene/success?orderNo=" + orderNo + "&userId="
+ userId + "&productId=" + productId +"&serialNum=" +serialNum
+ "&openId=" + openId;
}else{
//alert(res.err_msg);
}
}
);
}
主要是通过这个H5页面和后台进行交互操作.自此微信公众号支付基本完结。有问题可以大家多多交流。