您的位置:首页 > 移动开发 > 微信开发

Java微信开发之公众号支付接口

2015-11-18 14:41 801 查看

1、设置支付路径

使用微信公众号支付接口,必须在微信公众号管理后台中设置支付路径。这个微信接口文档说得很清楚,请参考:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=7_3

2、公众号支付业务流程



简单的描述就是:

首先需要根据我们自己的订单(4),也就是需要支付的明细,然后使用统一下单API向微信请求生成微信看得懂的订单(5),然后生成支付参数及签名(6),在支付页面根据支付配置及微信统一订单的prepay_id,发起微信支付(7)。用户输入密码支付后,微信会通过异步的方式,通知我们的系统,告诉支付的结果(10)。我们在系统后台处理根据支付的结果,处理我们的业务后,返回信息告诉向往,此订单已确定(11)。

3、统一下单

我们生成订单后,通过统一下单API,把微信的订单与我们的订单关联起来。

微信的统一下单接口:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_1

统一订单实体类:

package com.benben.timetable.wechat.entity;

/**
* 统一下单
*
* @ClassName: UnifiedOrder
* @Description: TODO
* @author 潘广伟(笨笨)
* @date 2015年9月18日
*
*/
public class UnifiedOrder {
/**
* 公众账号ID
*/
private String appid;
/**
* 商户号
*/
private String mch_id;
/**
* 附加数据(说明)
*/
private String attach;
/**
* 商品描述
*/
private String body;
/**
* 随机串
*/
private String nonce_str;
/**
* 通知地址
*/
private String notify_url;
/**
* 用户标识
*/
private String openid;
/**
* 商户订单号
*/
private String out_trade_no;
/**
* 终端IP(用户)
*/
private String spbill_create_ip;
/**
* 总金额
*/
private Integer total_fee;
/**
* 交易类型
*/
private String trade_type;
/**
* 签名
*/
private String sign;
/**
* WEB
*/
private String device_info;

public UnifiedOrder(){
this.trade_type = "JSAPI";
this.device_info = "WEB";
}

public String getAppid() {
return appid;
}

public void setAppid(String appid) {
this.appid = appid;
}

public String getAttach() {
return attach;
}

public void setAttach(String attach) {
this.attach = attach;
}

public String getBody() {
return body;
}

public void setBody(String body) {
this.body = body;
}

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 getNotify_url() {
return notify_url;
}

public void setNotify_url(String notify_url) {
this.notify_url = notify_url;
}

public String getOpenid() {
return openid;
}

public void setOpenid(String openid) {
this.openid = openid;
}

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 getSpbill_create_ip() {
return spbill_create_ip;
}

public void setSpbill_create_ip(String spbill_create_ip) {
this.spbill_create_ip = spbill_create_ip;
}

public Integer getTotal_fee() {
return total_fee;
}

public void setTotal_fee(Integer total_fee) {
this.total_fee = total_fee;
}

public String getTrade_type() {
return trade_type;
}

public void setTrade_type(String trade_type) {
this.trade_type = trade_type;
}

public String getSign() {
return sign;
}

public void setSign(String sign) {
this.sign = sign;
}

public String getDevice_info() {
return device_info;
}

public void setDevice_info(String device_info) {
this.device_info = device_info;
}
}


统一下单代码中使用到的参数:

/**
* 应用编号(微信公众号编号)
*/
private @Value("${app_id}") String appId;
/**
* 商户号码
*/
private @Value("${mch_id}") String mchId;
/**
* 支付码
*/
private @Value("${pay_key}") String payKey;
/**
* 统一下单URL
*/
private @Value("${pay_unifiedorder_url}") String unifiedOrderUrl;


统一下单,并返回获取prepay_id:

/**
* 统一下单
* @Title: unifiedOrder
* @Description: TODO
* @param: @param openId 微信用户openId
* @param: @param orderId 订单ID
* @param: @param money 订单总价,单位:分
* @param: @param callbackUrl 回调路径
* @param: @return
* @param: @throws Exception
* @return: String
*/
public String unifiedOrder(String openId,String orderId,int money,String callbackUrl) throws Exception{
UnifiedOrder unifiedOrder = new UnifiedOrder();
unifiedOrder.setAppid(appId);
unifiedOrder.setAttach("hehedesk");

unifiedOrder.setBody("hehedesk");
unifiedOrder.setMch_id(mchId);

String nonce = UUID.randomUUID().toString().substring(0, 30);
unifiedOrder.setNonce_str(nonce);
unifiedOrder.setNotify_url(callbackUrl);

unifiedOrder.setOpenid(openId);
unifiedOrder.setOut_trade_no(orderId);

unifiedOrder.setSpbill_create_ip("14.23.150.211");
unifiedOrder.setTotal_fee(money);

String sign = createUnifiedOrderSign(unifiedOrder);
unifiedOrder.setSign(sign);

/**
* 转成XML格式
*/
xmlUtil.getXstreamInclueUnderline().alias("xml", unifiedOrder.getClass());
String xml = xmlUtil.getXstreamInclueUnderline().toXML(unifiedOrder);

String response = httpConnection.post(unifiedOrderUrl, xml);
logger.info("unifiedOrder");
logger.info(response);
Map<String, String> responseMap = xmlUtil.parseXml(response);

return responseMap.get("prepay_id");
}


openid相关介绍:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=4_4

实体对象转XML介绍:http://blog.csdn.net/jrainbow/article/details/49779277

关联我们的订单与微信订单的prepay_id:

String url = "http://letus.xyz/mall/order/wechat_notify";
String prepayId = wechatMerchantFactory.unifiedOrder(order.getUser().getOpenId(), order.getId(), (int) (100*order.getTotal()),url );
order.setPrepayId(prepayId);


回调路径callbackUrl是指支付成功后,微信向我们系统服务器回复支付结果信息的路径。支付成功后的订单处理逻辑一这个路径中写。

关联了订单与prepay_id后,我们就可以使用prepay_id来获取我们的订单及处理后续业务。

统一下单的签名:

/**
* 获取统一下单签名
* @Title: createUnifiedOrderSign
* @Description: TODO
* @param @param unifiedOrder
* @param @return
* @return String
* @throws
*/
public String createUnifiedOrderSign(UnifiedOrder unifiedOrder){
StringBuffer sign = new StringBuffer();
sign.append("appid=").append(unifiedOrder.getAppid());
sign.append("&attach=").append(unifiedOrder.getAttach());
sign.append("&body=").append(unifiedOrder.getBody());
sign.append("&device_info=").append(unifiedOrder.getDevice_info());
sign.append("&mch_id=").append(unifiedOrder.getMch_id());
sign.append("&nonce_str=").append(unifiedOrder.getNonce_str());
sign.append("¬ify_url=").append(unifiedOrder.getNotify_url());
sign.append("&openid=").append(unifiedOrder.getOpenid());
sign.append("&out_trade_no=").append(unifiedOrder.getOut_trade_no());
sign.append("&spbill_create_ip=").append(unifiedOrder.getSpbill_create_ip());
sign.append("&total_fee=").append(unifiedOrder.getTotal_fee());
sign.append("&trade_type=").append(unifiedOrder.getTrade_type());
sign.append("&key=").append(payKey);

return DigestUtils.md5Hex(sign.toString()).toUpperCase();
}


签名算法请参考:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=4_3

签名特别注意以下重要规则(微信文档中也有):

◆ 参数名ASCII码从小到大排序(字典序);

◆ 如果参数的值为空不参与签名;

◆ 参数名区分大小写;

◆ 验证调用返回或微信主动通知签名时,传送的sign参数不参与签名,将生成的签名与该sign值作校验。

◆ 微信接口可能增加字段,验证签名时必须支持增加的扩展字段

微信商户相关的开发中,多次用到排序后的字符签名。为了避免多次手写拼接字符串和手动拼接会出现人为的失误,我们可以使用动态对对象的属性名排序并拼接字符串的方式代替。

/**
* 获取统一下单签名
* @Title: createUnifiedOrderSign
* @Description: TODO
* @param @param unifiedOrder
* @param @return
* @return String
* @throws
*/
public String createUnifiedOrderSign(UnifiedOrder unifiedOrder){
StringBuffer sign = new StringBuffer();
Map<String, String> map = getSortMap(unifiedOrder);

boolean isNotFirst = false;

for (Map.Entry<String, String> entry : map.entrySet()) {
if(isNotFirst == true){
sign.append("&");
}else{
isNotFirst = true;
}

sign.append(entry.getKey()).append("=").append(entry.getValue());
}
sign.append("&key=").append(payKey);

return DigestUtils.md5Hex(sign.toString()).toUpperCase();

}

/**
* 获取排序后的类属性及值
* @param object
* @return
* @throws Exception
*/
private Map<String, String> getSortMap(Object object) throws Exception{
Field[] fields = object.getClass().getDeclaredFields();
Map<String, String> map = new HashMap<String, String>();

for(Field field : fields){
String name = field.getName();
String methodName = "get" + name.replaceFirst(name.substring(0, 1), name.substring(0, 1)
.toUpperCase());
Method getter = object.getClass().getMethod(methodName);
// 调用getter方法获取属性值
String value =  getter.invoke(object)+"";
if (value != null){
map.put(name, value);
}
}

Map<String, String> sortMap = new TreeMap<String, String>(
new Comparator<String>() {

@Override
public int compare(String arg0, String arg1) {

return arg0.compareTo(arg1);
}

});

sortMap.putAll(map);

return sortMap;
}


4、网页端调起支付API

支付相关文档参考:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=7_7&index=6

4.1、生成支付参数及签名

支付配置实体:

package com.benben.timetable.wechat.entity;

/**
* JS-SDK使用的配置信息
* @ClassName: JsAPIConfig
* @Description: TODO
* @author 潘广伟
* @date 2015年8月17日 下午3:12:35
*
*/
public class JsAPIConfig {
private boolean debug;
private String appId;
private String timestamp;
private String nonce;
private String signature;
private String title;
private String link;
private String signType;
private String packageName;

public JsAPIConfig(){
signType = "MD5";
}

public boolean isDebug() {
return debug;
}

public void setDebug(boolean debug) {
this.debug = debug;
}

public String getAppId() {
return appId;
}

public void setAppId(String appId) {
this.appId = appId;
}

public String getTimestamp() {
return timestamp;
}

public void setTimestamp(String timestamp) {
this.timestamp = timestamp;
}

public String getNonce() {
return nonce;
}

public void setNonce(String nonce) {
this.nonce = nonce;
}

public String getSignature() {
return signature;
}

public void setSignature(String signature) {
this.signature = signature;
}

public String getTitle() {
return title;
}

public void setTitle(String title) {
this.title = title;
}

public String getLink() {
return link;
}

public void setLink(String link) {
this.link = link;
}

public String getSignType() {
return signType;
}

public void setSignType(String signType) {
this.signType = signType;
}

public String getPackageName() {
return packageName;
}

public void setPackageName(String packageName) {
this.packageName = packageName;
}
}


生成支付配置:

/**
* 获取支付配置
* @Title: createPayConfig
* @Description: TODO
* @param @param preayId 统一下单prepay_id
* @param @return
* @param @throws Exception
* @return JsAPIConfig
* @throws
*/
public JsAPIConfig createPayConfig(String prepayId) throws Exception {
JsAPIConfig config = new JsAPIConfig();

String nonce = UUID.randomUUID().toString();
String timestamp = Long.toString(System.currentTimeMillis() / 1000);
String packageName = "prepay_id="+prepayId;
StringBuffer sign = new StringBuffer();
sign.append("appId=").append(appId);
sign.append("&nonceStr=").append(nonce);
sign.append("&package=").append(packageName);
sign.append("&signType=").append(config.getSignType());
sign.append("&timeStamp=").append(timestamp);
sign.append("&key=").append(payKey);
String signature = DigestUtils.md5Hex(sign.toString()).toUpperCase();

config.setAppId(appId);
config.setNonce(nonce);
config.setTimestamp(timestamp);
config.setPackageName(packageName);
config.setSignature(signature);

return config;
}


4.2、页面调用JSAPI来进行支付

$('#pay-button').on('click',function(){
$.ajax({
url:'wechat/mall/pay/'+$('#prepay_id').html(),
type : "get",
data : {
"timestamp" : new Date().getTime()
},
success : function(response) {
var data = eval('(' + response + ')');
var obj = data.msg;
WeixinJSBridge.invoke('getBrandWCPayRequest',{
"appId" : obj.appId,                  //公众号名称,由商户传入
"timeStamp":obj.timestamp,          //时间戳,自 1970 年以来的秒数
"nonceStr" : obj.nonce,         //随机串
"package" : obj.packageName,      //商品包信息</span>
"signType" : obj.signType,        //微信签名方式
"paySign" : obj.signature           //微信签名
},function(res){

if(res.err_msg == "get_brand_wcpay_request:ok" ) {
alert('支付成功');
}
});
}
});
});


单点击支付按钮时,首先获取JSAPI需要的配置参数,然后再调用WeixinJSBridge来使用getBrandWCPayRequest请求进行支付。

5、支付结果回调

当用户输入密码,支付动作完成后。微信会通过前面我们设置的callbackUrl来返回支付结果。

相关资料参考:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_7

下面是http://letus.xyz/mall/order/wechat_notify回调路径的内容:

/**
* 微信支付回调页面
* @Title: wechatPayNotify
* @Description: TODO
* @param @param request
* @param @param trade_status
* @param @param out_trade_no
* @param @param trade_no
* @return void
* @throws
*/
@ResponseBody
@RequestMapping(value="wechat_notify")
public String wechatPayNotify(HttpServletRequest request){
try {
Map<String, String> map = getCallbackParams(request);
if (map.get("result_code").toString().equalsIgnoreCase("SUCCESS")) {
String orderId = map.get("out_trade_no");
//这里写成功后的业务逻辑
orderService.updateConfirm(orderId);
}
} catch (Exception e) {
e.printStackTrace();
}

return wechatMerchantFactory.getPayCallback();
}
/**
* 获取请求参数
* @Title: getCallbackParams
* @Description: TODO
* @param @param request
* @param @return
* @param @throws Exception
* @return Map<String,String>
* @throws
*/
public Map<String, String> getCallbackParams(HttpServletRequest request)
throws Exception {
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);
}
System.out.println("~~~~~~~~~~~~~~~~付款成功~~~~~~~~~");
outSteam.close();
inStream.close();
String result = new String(outSteam.toByteArray(), "utf-8");
return xmlUtil.parseXml(result);
}


注:这里的out_trade_no指的不是前面说的prepay_id。而是我们系统中的orderId。

支付结果确认收到后,我们还需要告知微信。

return wechatMerchantFactory.getPayCallback();


PayCallback:

package com.benben.timetable.wechat.entity;
/**
* 支付成功回复
* @ClassName: PayCallBack
* @Description: TODO
* @author 潘广伟(笨笨)
* @date 2015年11月12日
*
*/
public class PayCallback {
private String return_code;
private String return_msg;

public PayCallback() {
this.return_code = "SUCCESS";
this.return_msg = "OK";
}

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;
}

}


getPayCallback方法:

/**
* 生成收到支付结果的确认信息
* @Title: getPayCallback
* @Description: TODO
* @param @return
* @return String
* @throws
*/
public String getPayCallback(){
PayCallback callback = new PayCallback();
xmlUtil.xstream().alias("xml", callback.getClass());
String xml = xmlUtil.xstream().toXML(callback);

return xml;
}


注:这里记得一定要回复确认信息,微信会可能会多次发送同样的通知给商户系统。商户系统必须能够正确处理重复的通知。

HttpConnection:https://code.csdn.net/snippets/1361856
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息