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

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

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

整个交互过程分为四步:

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

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

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

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

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

技术分享

这四步中,商户需要做的有三块工作,明确指出的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、微信:扫描二维码,获取商户订单信息

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

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

输入参数说明如下:

名称变量名类型必填示例值描述
公众账号IDappidString(32)是wx8888888888888888微信分配的公众账号ID
用户标识openidString(128)是o8GeHuLAsgefS_80exEr1cTqekUs用户在商户appid下的唯一标识
商户号mch_idString(32)是1900000109微信支付分配的商户号
是否关注公众账号is_subscribeString(1)是Y用户是否关注公众账号,仅在公众账号类型支付有效,取值范围:Y或N;Y-关注;N-未关注
随机字符串nonce_strString(32)是5K8264ILTKCH16CQ2502SI8ZNMTM67VS随机字符串,不长于32位
商品IDproduct_idString(32)是88888商户定义的商品id 或者订单号
签名signString(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_codeString(16)是SUCCESSSUCCESS/FAIL,此字段是通信标识,非交易标识,交易是否成功需要查看result_code来判断
返回信息return_msgString(128)否签名失败返回信息,如非空,为错误原因;签名失败;具体某个参数格式校验错误.
公众账号IDappidString(32)是wx8888888888888888微信分配的公众账号ID
商户号mch_idString(32)是1900000109微信支付分配的商户号
随机字符串nonce_strString(32)是5K8264ILTKCH16CQ2502SI8ZNMTM67VS微信返回的随机字符串
预支付IDprepay_idString(64)是wx201410272009395522657a690389285100调用统一下单接口生成的预支付ID
业务结果result_codeString(16)是SUCCESSSUCCESS/FAIL
错误描述err_code_desString(128)否
当result_code为FAIL时,商户展示给用户的错误提
签名signString(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,可以系统联调了,祝你顺利!

文章来自:http://52525252.blog.51cto.com/4305143/1620361
© 2021 jiaocheng.bubufx.com  联系我们
ICP备案:鲁ICP备09046678号-3