微信支付V3集成过程(Native)

长话短说,微信支付V3版本相比V2版本,简化许多接口,大大降低了商户的接入代价,只不过由于相关的集成说明文档写的过于笼统,细节描述不够具体,demo版本不够完善等等原因,被大家诟病。

最近基于业务需要,完成了微信支付的接入,选择的是公众号扫码支付(Native模式)。在此把集成的大致过程贴出来,希望能对正在困扰的各位有所帮助。

整个交互过程分为四步:

1、商户:生成微信支付二维码

2、微信:扫描二维码,获取商户订单信息

3、微信:确认支付,调起微信支付模块,完成支付,回调商户通知接口

4、商户:接收微信支付成功后的通知信息,根据通知结果进行业务处理

交互时序图如下(参考微信官方说明):

微信支付V3集成过程(Native)

这四步中,商户需要做的有三块工作,明确指出的1和4需要商户完成,还有一块工作隐藏在2中,提供微信回调接口,将商户订单信息返回给微信,该接口在申请微信支付后需要在公众平台进行配置。

在进行详细说明之前,先做几点说明:

1、涉及传参及返回结果,均为xml格式

2、默认编码为UTF-8

3、签名算法

重点说下签名算法,官方文档中对签名算法做了比较详细的说明,可以直接参考。不过没有说明每次交互中用到的签名sign,都有哪些字段参与生成了签名。

这里明确下:微信支付每次交互中的签名字段sign,由本次交互所有参数(sign和空值除外)参与签名生成,这点也是经常多次尝试后确认的。

贴出源码:

/**

 * 创建签名

 * 

 * @param packageParams

 * @return

 */

@SuppressWarnings({ "rawtypes", "unchecked" })

public static String createSign(SortedMap packageParams) {

StringBuffer sb = new StringBuffer();

Set<Entry> es = packageParams.entrySet();

Iterator<Entry> it = es.iterator();

while (it.hasNext()) {

Map.Entry entry = (Map.Entry) it.next();

String k = (String) entry.getKey();

String v = entry.getValue() != null ? entry.getValue().toString() : null;

if (null != v 

&& !"".equals(v) 

&& !"sign".equals(k)

&& !"key".equals(k)) {

sb.append(k + "=" + v + "&");

}

}

sb.append("key=" + WxPayConfig.APP_KEY);

return MD5Util.MD5Encode(sb.toString(), WxPayConfig.CHAR_SET).toUpperCase();

}


/**

 * 签名校验

 * 

 * @return

 * @throws UnsupportedEncodingException

 */

@SuppressWarnings("rawtypes")

public static boolean verifyPaySign(SortedMap paras, String sign) throws UnsupportedEncodingException {

String paySign = Tools.createSign(paras);

return StringUtil.isNotEmpty(paySign) ? paySign.equalsIgnoreCase(sign) : false;

}


下面对每一步进行详细说明:

1、商户:生成微信支付二维码

微信支付链接格式为:weixin://wxpay/bizpayurl?sign=XXXXX&appid=XXXXX&mch_id=XXXXX&product_id=XXXXX&time_stamp=XXXXX&nonce_str=XXXXX

sign为签名字段,参与签名的字段为appid、mch_id、product_id、time_stamp、nonce_str

其中XXXXX为商户需要填写的内容,商户将该链接生成二维码,如需要打印发布二维码,需要采用此格式。商户可调用第三方库(zxing 、qrcode)生成二维码图片。

举例:weixin://wxpay/bizpayurl?appid=wx2421b1c4370ec43b&mch_id=10000100&nonce_str=f6808210402125e30663234f94c87a8c&product_id=1&time_stamp=1415949957&sign=512F68131DD251DA4A45DA79CC7EFE9D

贴出源码:略

2、微信:扫描二维码,获取商户订单信息

这里商户需要做的是,给微信提供获取订单信息的接口。接口需要完成三部分工作:

  • 获取微信回调接口时传过来的参数,进行签名校验

输入参数说明如下:

名称 变量名 类型 必填 示例值 描述
公众账号ID appid String(32) wx8888888888888888 微信分配的公众账号ID
用户标识 openid String(128) o8GeHuLAsgefS_80exEr1cTqekUs 用户在商户appid下的唯一标识
商户号 mch_id String(32) 1900000109 微信支付分配的商户号
是否关注公众账号 is_subscribe String(1) Y 用户是否关注公众账号,仅在公众账号类型支付有效,取值范围:Y或N;Y-关注;N-未关注
随机字符串 nonce_str String(32) 5K8264ILTKCH16CQ2502SI8ZNMTM67VS 随机字符串,不长于32位
商品ID product_id String(32) 88888 商户定义的商品id 或者订单号
签名 sign String(32) C380BEC2BFD727A4B6845133519F3AD6 返回数据签名

sign为签名字段,参与签名的字段为appid、openid、mch_id、is_subscribe、nonce_str、product_id。

  • 调用微信统一下单接口,生成预支付订单,获取预支付订单prepay_id

统一下单URL地址:https://api.mch.weixin.qq.com/pay/unifiedorder

输入参数说明如下:

字段名

变量名

必填

类型

示例值

描述

公众账号ID

appid

String(32)

wx8888888888888888

微信分配的公众账号ID

商户号

mch_id

String(32)

1900000109

微信支付分配的商户号

设备号

device_info

String(32)

013467007045764

微信支付分配的终端设备号,商户自定义

随机字符串

nonce_str

String(32)

5K8264ILTKCH16CQ2502SI8ZNMTM67VS

随机字符串,不长于32位

签名

sign

String(32)

C380BEC2BFD727A4B6845133519F3AD6

签名

商品描述

body

String(32)

Ipad mini  16G  白色

商品或支付单简要描述

商品详情

detail

String(8192)

Ipad mini  16G  白色

商品名称明细列表

附加数据

attach

String(127)

说明

附加数据,在查询API和支付通知中原样返回,该字段主要用于商户携带订单的自定义数据

商户订单号

out_trade_no

String(32)

1217752501201407033233368018

商户系统内部的订单号,32个字符内、可包含字母

货币类型

fee_type

String(16)

CNY

符合ISO 4217标准的三位字母代码,默认人民币:CNY

总金额

total_fee

Int

888

订单总金额,只能为整数,精确到分

终端IP

spbill_create_ip

String(16)

8.8.8.8

APP和网页支付提交用户端ip,Native支付填调用微信支付API的机器IP。

交易起始时间

time_start

String(14)

20091225091010

订单生成时间,格式为yyyyMMddHHmmss,如2009年12月25日9点10分10秒表示为20091225091010

交易结束时间

time_expire

String(14)

20091227091010

订单失效时间,格式为yyyyMMddHHmmss,如2009年12月27日9点10分10秒表示为20091227091010

商品标记

goods_tag

String(32)

WXG

商品标记,代金券或立减优惠功能的参数

通知地址

notify_url

String(256)

http://www.baidu.com/

接收微信支付异步通知回调地址

交易类型

trade_type

String(16)

JSAPI

取值如下:JSAPI,NATIVE,APP

商品ID

product_id

String(32)

12235413214070356458058

trade_type=NATIVE,此参数必传。此id为二维码中包含的商品ID,商户自行定义。

用户标识

openid

String(128)

oUpF8uMuAJO_M2pxb1Q9zNjWeS6o

trade_type=JSAPI,此参数必传,用户在商户appid下的唯一标识。


sign为签名字段,参与签名的字段包括上面除sign之外的所有非空字段。

返回结果说明如下

字段名

变量名

必填

类型

示例值

描述

返回状态码

return_code

String(16)

SUCCESS

SUCCESS/FAIL

此字段是通信标识,非交易标识,交易是否成功需要查看result_code来判断

返回信息

return_msg

String(128)

签名失败

 

返回信息,如非空,为错误原因

签名失败

参数格式校验错误

以下字段在return_code为SUCCESS的时候有返回

字段名

变量名

必填

类型

示例值

描述

公众账号ID

appid

String(32)

wx8888888888888888

调用接口提交的公众账号ID

商户号

mch_id

String(32)

1900000109

调用接口提交的商户号

设备号

device_info

String(32)

013467007045764

调用接口提交的终端设备号,

随机字符串

nonce_str

String(32)

5K8264ILTKCH16CQ2502SI8ZNMTM67VS

微信返回的随机字符串

签名

sign

String(32)

C380BEC2BFD727A4B6845133519F3AD6

微信返回的签名

业务结果

result_code

String(16)

SUCCESS

SUCCESS/FAIL

错误代码

err_code

String(32)

SYSTEMERROR

详细参见第6节错误列表

错误代码描述

err_code_des

String(128)

系统错误

错误返回的信息描述

以下字段在return_code 和result_code都为SUCCESS的时候有返回

字段名

变量名

必填

类型

示例值

描述

交易类型

trade_type

String(16)

JSAPI

调用接口提交的交易类型,取值如下:JSAPI,NATIVE,APP

预支付交易会话标识

prepay_id

String(64)

wx201410272009395522657a690389285100

 

微信生成的预支付回话标识,用于后续接口调用中使用,该值有效期为2小时

二维码链接

code_url

String(64)

URl:

weixin://wxpay/s/An4baqw

trade_type为NATIVE是有返回,可将该参数值生成二维码展示出来进行扫码支付

  • 向微信回写商户订单信息

参数说明如下:

名称 变量名 类型 必填 示例值 描述
返回状态码 return_code String(16) SUCCESS SUCCESS/FAIL,此字段是通信标识,非交易标识,交易是否成功需要查看result_code来判断
返回信息 return_msg String(128) 签名失败 返回信息,如非空,为错误原因;签名失败;具体某个参数格式校验错误.
公众账号ID appid String(32) wx8888888888888888 微信分配的公众账号ID
商户号 mch_id String(32) 1900000109 微信支付分配的商户号
随机字符串 nonce_str String(32) 5K8264ILTKCH16CQ2502SI8ZNMTM67VS 微信返回的随机字符串
预支付ID prepay_id String(64) wx201410272009395522657a690389285100 调用统一下单接口生成的预支付ID
业务结果 result_code String(16) SUCCESS SUCCESS/FAIL
错误描述 err_code_des String(128)
当result_code为FAIL时,商户展示给用户的错误提
签名 sign String(32) C380BEC2BFD727A4B6845133519F3AD6 返回数据签名

sign为签名字段,参与签名的字段包括上面除sign之外的所有非空字段。

贴出源码:

// ************************************************************************************

// 1、参数校验

// 微信版本&支付签名&订单信息

// ************************************************************************************

// 预定义订单信息

Order order = null;

// 解析微信post过来的xml数据

Map<String, Object> postData = Tools.getXmlDataFromWx(request);

if (postData != null

&& postData.get("product_id") != null) {

// 校验支付签名是否一致

boolean isPaySignValid = Tools.verifyPaySign(Tools.convertMap2SortedMap(postData), 

postData.get("sign").toString());

if (isPaySignValid) {

// 获取订单信息

order = orderService.get(postData.get("product_id").toString());

if (order == null) {

RetCode = WxPayRetCodeEnum.ORDERNOTEXIST.getCode();

RetErrMsg = WxPayRetCodeEnum.ORDERNOTEXIST.getMsg();

} else if (order.getOrderStatus() == OrderStatus.CANCELED.getStatus()) {

RetCode = WxPayRetCodeEnum.ORDEREXPIRED.getCode();

RetErrMsg = WxPayRetCodeEnum.ORDEREXPIRED.getMsg();

} else if (order.getPayStatus() == PayStatus.PAID.getStatus()

|| order.getPayAmount() <= order.getBalanceAmount()) {

RetCode = WxPayRetCodeEnum.ORDERPAYED.getCode();

RetErrMsg = WxPayRetCodeEnum.ORDERPAYED.getMsg();

}

} else {

RetCode = WxPayRetCodeEnum.PAYSIGNWRONG.getCode();

RetErrMsg = WxPayRetCodeEnum.PAYSIGNWRONG.getMsg();

}

} else {

RetCode = WxPayRetCodeEnum.WXPOSTXMLERROR.getCode();

RetErrMsg = WxPayRetCodeEnum.WXPOSTXMLERROR.getMsg();

}


// ************************************************************************************

// 2、调用微信统一下单接口,生成预支付订单

// ************************************************************************************

// 计算商品总金额,精确到分,取整

String totalFee = order != null ? String.valueOf(

NumberCaculator.multiply(NumberCaculator.substract(order.getPayAmount(), order.getBalanceAmount()), 100)) : "0";

totalFee = totalFee.indexOf(".") > -1 ? totalFee.substring(0, totalFee.indexOf(".")) : totalFee;

String outTradeNo = order != null ? order.getOrderSn() : null;


// 调用微信统一下单接口,生成微信预支付订单,获取交易会话标识

RequestHandler reqHandler = new RequestHandler();

reqHandler.setParameter("appid", WxPayConfig.APP_ID);

reqHandler.setParameter("mch_id", WxPayConfig.MCH_ID);

reqHandler.setParameter("nonce_str", Tools.getNonceStr());

reqHandler.setParameter("body", PurchaseTypeEnum.Course.getName());

reqHandler.setParameter("detail", PurchaseTypeEnum.Course.getDesc());

reqHandler.setParameter("out_trade_no", outTradeNo);

reqHandler.setParameter("total_fee", totalFee);

reqHandler.setParameter("spbill_create_ip", InetAddress.getLocalHost().getHostAddress().toString());

reqHandler.setParameter("notify_url", WxPayConfig.NOTIFY_URL);

reqHandler.setParameter("trade_type", WxPayTradeTypeEnum.NATIVE.getMsg());

reqHandler.setParameter("sign", Tools.createSign(reqHandler.getParameters()));


// 发起请求,调用统一下单接口,获取统一下单接口返回结果

String result = reqHandler.sendReq(WxPayConfig.UNIFIED_ORDER_API, reqHandler.getRequestParams4Xml());

Map<String, String> apiResult = XMLUtil.doXMLParse(result);


// ************************************************************************************

// 3、向微信回写订单信息

// ************************************************************************************

// 设置支付参数-为了返回Package 数据,回调URL 必须返回一个xml 格式的返回数据

SortedMap<String, Object> signParams = new TreeMap<String, Object>();

signParams.put("return_code", apiResult != null ? apiResult.get("return_code") : "FAIL"); 

signParams.put("return_msg", apiResult != null ? apiResult.get("return_msg") : "下单失败"); 

signParams.put("appid", WxPayConfig.APP_ID);

signParams.put("mch_id", WxPayConfig.MCH_ID);

signParams.put("nonce_str", Tools.getNonceStr());

signParams.put("prepay_id", apiResult != null ? apiResult.get("prepay_id") : "");

signParams.put("result_code", RetCode);

signParams.put("err_code_des", RetErrMsg);

signParams.put("sign", Tools.createSign(signParams));


// 回写订单信息

PrintWriter writer = null;

response.setHeader("ContentType", "text/xml");

response.setHeader("Pragma", "No-cache");

response.setHeader("Cache-Control", "no-cache");

response.setDateHeader("Expires", 0);

try {

writer = response.getWriter();

writer.flush();

writer.print(XMLUtil.parseXML(signParams));

//   writer.print(JSONObject.fromObject(signParams).toString());

} catch (IOException e) {

e.printStackTrace();

} finally {

writer.close();

}

3、微信:确认支付,调起微信支付模块,完成支付,回调商户通知接口

这一步不涉及商户

4、商户:接收微信支付成功后的通知信息,根据通知结果进行业务处理

微信支付成功后,会将支付结果通过统一下单接口中的notify_url参数回传通知给商户。商户需要在通知接口中完成签名校验、业务处理、回写处理结果等操作。

输入参数说明如下:

字段名

变量名

必填

类型

示例值

描述

返回状态码

return_code

String(16)

SUCCESS

SUCCESS/FAIL

此字段是通信标识,非交易标识,交易是否成功需要查看result_code来判断

返回信息

return_msg

String(128)

签名失败

 

返回信息,如非空,为错误原因

签名失败

参数格式校验错误

以下字段在return_code为SUCCESS的时候有返回


字段名

变量名

必填

类型

示例值

描述

公众账号ID

appid

String(32)

wx8888888888888888

微信分配的公众账号ID

商户号

mch_id

String(32)

1900000109

微信支付分配的商户号

设备号

device_info

String(32)

013467007045764

微信支付分配的终端设备号,

随机字符串

nonce_str

String(32)

5K8264ILTKCH16CQ2502SI8ZNMTM67VS

随机字符串,不长于32位

签名

sign

String(32)

C380BEC2BFD727A4B6845133519F3AD6

签名

业务结果

result_code

String(16)

SUCCESS

SUCCESS/FAIL

错误代码

err_code

String(32)

SYSTEMERROR

详细参见第6节错误列表

错误代码描述

err_code_des

String(128)

系统错误

错误返回的信息描述

用户标识

openid

String(128)

wxd930ea5d5a258f4f

用户在商户appid下的唯一标识

是否关注公众账号

is_subscribe

String(1)

Y

用户是否关注公众账号,Y-关注,N-未关注,仅在公众账号类型支付有效

交易类型

trade_type

String(16)

JSAPI

JSAPI、NATIVE、APP

付款银行

bank_type

String(16)

CMC

银行类型,采用字符串类型的银行标识,银行类型见附表

总金额

total_fee

Int

100

订单总金额,单位为分

货币种类

fee_type

String(8)

CNY

货币类型,符合ISO 4217标准的三位字母代码,默认人民币:CNY

现金支付金额

cash_fee

Int

100

现金支付金额订单现金支付金额

现金支付货币类型

cash_fee_type

String(16)

CNY

货币类型,符合ISO 4217标准的三位字母代码,默认人民币:CNY

代金券或立减优惠金额

coupon_fee

Int

10

代金券或立减优惠金额<=订单总金额,订单总金额-代金券或立减优惠金额=现金支付金额

代金券或立减优惠使用数量

coupon_count

Int

1

代金券或立减优惠使用数量

代金券或立减优惠批次ID

coupon_batch_id_$n

String(20)

100

代金券或立减优惠批次ID ,$n为下标,从1开始编号

代金券或立减优惠ID

coupon_id_$n

String(20)

10000 

代金券或立减优惠ID, $n为下标,从1开始编号

单个代金券或立减优惠支付金额

coupon_fee_$n

Int

100

单个代金券或立减优惠支付金额, $n为下标,从1开始编号

微信支付订单号

transaction_id

String(32)

1217752501201407033233368018

微信支付订单号

商户订单号

out_trade_no

String(32)

1212321211201407033568112322

商户系统的订单号,与请求一致。

商家数据包

attach

String(128)

123456

商家数据包,原样返回

支付完成时间

time_end

String(14)

20141030133525

支付完成时间,格式为yyyyMMddHHmmss,如2009年12月25日9点10分10秒表示为20091225091010

sign为签名字段,参与签名的字段包括上面除sign之外的所有非空字段。


贴出源码:


// 1、验证返回通知的合法性

Map<String, String> params = paymentService.getNotifyParamsMap(request);// 解析返回参数

boolean verifyStatus = paymentService.isNotifyLegal(params, null);

if (log.isInfoEnabled()) {

log.info("************WxpayNotifyAction**********Wxpay params Log: " + params.toString());

log.info("************WxpayNotifyAction**********verifyStatus: " + verifyStatus);

}


// 2、根据验证结果进行业务逻辑的操作

String returnCode = WxpayTradeStateEnum.FAIL.getCode();

String returnMsg = WxpayTradeStateEnum.FAIL.getMsg();

if (verifyStatus) {

// *****************************************************

// TODO:业务处理

// *****************************************************

// 向支付平台返回

returnCode = WxpayTradeStateEnum.SUCCESS.getCode();

returnMsg = WxpayTradeStateEnum.SUCCESS.getMsg();

} else {

returnCode = WxpayTradeStateEnum.FAIL.getCode();

returnMsg = "无效签名";

}


// 3、向微信回写处理结果

SortedMap<String, Object> resMap = new TreeMap<String, Object>();

resMap.put("return_code", returnCode);

resMap.put("return_msg", returnMsg);

PrintWriter writer = null;

response.setHeader("ContentType", "text/xml");

response.setHeader("Pragma", "No-cache");

response.setHeader("Cache-Control", "no-cache");

response.setDateHeader("Expires", 0);

try {

writer = response.getWriter();

writer.flush();

writer.print(XMLUtil.parseXML(resMap));

} catch (IOException e) {

e.printStackTrace();

} finally {

writer.close();

}


OK,可以系统联调了,祝你顺利!

微信支付V3集成过程(Native)

上一篇:微信平台开发——日历服务


下一篇:微信开发基础教程