使用微信公众号支付接口,必须在微信公众号管理后台中设置支付路径。这个微信接口文档说得很清楚,请参考:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=7_3
简单的描述就是:
首先需要根据我们自己的订单(4),也就是需要支付的明细,然后使用统一下单API向微信请求生成微信看得懂的订单(5),然后生成支付参数及签名(6),在支付页面根据支付配置及微信统一订单的prepay_id,发起微信支付(7)。用户输入密码支付后,微信会通过异步的方式,通知我们的系统,告诉支付的结果(10)。我们在系统后台处理根据支付的结果,处理我们的业务后,返回信息告诉向往,此订单已确定(11)。
我们生成订单后,通过统一下单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值作校验。
◆ 微信接口可能增加字段,验证签名时必须支持增加的扩展字段
因为微信商户相关的开发中,多次用到排序后的字符签名,后面我们会写一个排序的工具类,以避免每次都是手写拼接字符串来实现。
支付相关文档参考: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请求进行支付。
当用户输入密码,支付动作完成后。微信会通过前面我们设置的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