现在因为微信的流量大、使用的人多,基于微信小程序开发成本低,且开发不需要进行区分安卓和IOS两端分别开发。所以小程序上诞生了越来越多的电商平台。
但小程序支付是专门被定义使用在小程序中的支付产品。目前在小程序中能且只能使用小程序支付的方式来唤起微信支付。所以我们作为程序员就需要去掌握学习微信小程序的相关知识。
微信官方文档链接
商户后台和微信支付后台根据相同的密钥和算法生成一个结果,用于校验双方身份合法性。签名的算法由微信支付制定并公开,常用的签名方式有:MD5、SHA1、SHA256、HMAC等。
用户在小程序内的身份标识,不同小程序拥有不同的openid。商户后台系统通过登录授权、支付通知、查询订单等API可获取到用户的openid。主要用途是判断同一个用户。可调用接口获取openid。(前期准备工作中较为复杂的一步,就是获取openid)
商户证书是微信提供的二进制文件,商户系统发起与微信支付后台服务器通信请求的时候,作为微信支付后台识别商户真实身份的凭据。
appid是微信小程序后台APP的唯一标识,在小程序后台申请小程序账号后,微信会自动分配对应的appid,用于标识该应用。可在小程序–>设置–>开发设置中查看。
商户申请微信支付后,由微信支付分配的商户收款账号。
交易过程生成签名的密钥,仅保留在商户系统和微信支付后台,不会在网络中传播。商户妥善保管该Key,切勿在网络中传输,不能在其他客户端中存储,保证key不会被泄漏。商户可根据邮件提示登录微信商户平台进行设置。也可按以下路径设置:微信商户平台(pay.weixin.qq.com)–>账户设置–>API安全–>密钥设置
AppSecret是APPID对应的接口密码,用于获取接口调用凭证时使用。
微信小程序后台链接
小程序认证以后,可以在小程序后台,微信支付菜单栏,申请微信支付。
申请成功之后会在邮件中收到由微信支付小助手发送的邮件,此邮件包含开发时需要使用的支付账户信息
其中包含了几个重要的参数值
邮件中参数 | API参数名 | 详细说明 |
---|---|---|
APPID | appid | appid是微信小程序后台APP的唯一标识,在小程序后台申请小程序账号后,微信会自动分配对应的appid,用于标识该应用。可在小程序–>设置–>开发设置中查看。 |
微信支付商户号 | mch_id | 商户申请微信支付后,由微信支付分配的商户收款账号。 |
API密钥 | key | 交易过程生成签名的密钥,仅保留在商户系统和微信支付后台,不会在网络中传播。商户妥善保管该Key,切勿在网络中传输,不能在其他客户端中存储,保证key不会被泄漏。商户可根据邮件提示登录微信商户平台进行设置。也可按以下路径设置:微信商户平台(pay.weixin.qq.com)–>账户设置–>API安全–>密钥设置 |
Appsecret | secret | AppSecret是APPID对应的接口密码,用于获取接口调用凭证时使用。 |
接口规范 | 规范详情 |
---|---|
传输方式 | 为保证交易安全性,采用HTTPS传输 |
提交方式 | 采用POST方法提交 |
数据格式 | 提交和返回数据都为XML格式,根节点名为xml |
字符编码 | 统一采用UTF-8字符编码 |
签名算法 | MD5,后续会兼容SHA1、SHA256、HMAC等。 |
签名要求 | 请求和接收数据均需要校验签名,详细方法请参考安全规范-签名算法 |
证书要求 | 调用申请退款、撤销订单接口需要商户证书 |
判断逻辑 | 先判断协议字段返回,再判断业务返回,最后判断交易状态 |
1、交易金额
交易金额默认为人民币交易,接口中参数支付金额单位为【分】,参数值不能带小数。对账单中的交易金额单位为【元】。
外币交易的支付金额精确到币种的最小单位,参数值不能带小数点。
2、交易类型trade_type
JSAPI–JSAPI支付(或小程序支付)、NATIVE–Native支付、APP–app支付,MWEB–H5支付,不同trade_type决定了调起支付的方式,请根据支付产品正确上传
MICROPAY–付款码支付,付款码支付有单独的支付接口,所以接口不需要上传,该字段在对账单中会出现
3、货币类型
境内商户号仅支持人民币
CNY:人民币
4、时间
标准北京时间,时区为东八区;如果商户的系统时间为非标准北京时间。参数值必须根据商户系统所在时区先换算成标准北京时间, 例如商户所在地为0时区的伦敦,当地时间为2014年11月11日0时0分0秒,换算成北京时间为2014年11月11日8时0分0秒。
5、时间戳
标准北京时间,时区为东八区,自1970年1月1日 0点0分0秒以来的秒数。注意:部分系统取到的值为毫秒级,需要转换成秒(10位数字)。
6、商户订单号
商户支付的订单号由商户自定义生成,仅支持使用字母、数字、中划线-、下划线_、竖线|、星号*这些英文半角字符的组合,请勿使用汉字或全角等特殊字符。微信支付要求商户订单号保持唯一性(建议根据当前系统时间加随机序列来生成订单号)。重新发起一笔支付要使用原订单号,避免重复支付;已支付过或已调用关单、撤销(请见后文的API列表)的订单号不能重新发起支付。
1、密钥设置路径:微信商户平台(pay.weixin.qq.com)–>账户中心–>账户设置–>API安全–>密钥设置
2、设置生效时间:一般为立刻生效,少数情况下会延迟几分钟,如长时间错误,可重复设置几次
3、密钥设置是影响此商户号下所有接口的,请谨慎设置。如怀疑密钥错误,又担心影响其他接口,可以尝试设置与原先设置的一样的密钥
xml工具,我们在请求同意支付接口时需要将我们准备好的对象转化为xml进行传输
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import java.io.StringReader;
import java.io.StringWriter;
/**
* xml工具类
*/
public class XmlUtils {
/**
* 实体转xml
* @param obj
* @param load
* @param is_fragment 是否去掉默认报文头
* @return
* @throws JAXBException
*/
public static String beanToXml(Object obj, Class<?> load, boolean is_fragment) throws JAXBException {
JAXBContext context = JAXBContext.newInstance(load);
Marshaller marshaller = context.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.setProperty(Marshaller.JAXB_FRAGMENT, is_fragment);
StringWriter writer = new StringWriter();
marshaller.marshal(obj,writer);
return writer.toString();
}
/**
* xml字符转对象
* @param xmlStr
* @param load
* @return
* @throws JAXBException
*/
public static Object xmlToBean(String xmlStr, Class<?> load) throws JAXBException {
JAXBContext context = JAXBContext.newInstance(load);
// 进行将Xml转成对象的核心接口
Unmarshaller unmarshaller = context.createUnmarshaller();
StringReader sr = new StringReader(xmlStr);
return unmarshaller.unmarshal(sr);
}
}
基础常量存储
public class WechatApiContext {
//统一下单支付接口
public static final String WECHAT_PAY_API = "https://api.mch.weixin.qq.com/pay/unifiedorder";
public static final String WECHAT_PAY_TYPE_JSAPI = "JSAPI"; //小程序或js支付
public static final String WECHAT_PAY_TYPE_NATIVE = "NATIVE"; //扫码
public static final String WECHAT_PAY_TYPE_APP = "APP"; //app支付
public static final String WECHAT_PAY_TYPE_MWEB = "MWEB"; //H5支付
public static final String WECHAT_PAY_SGIN_PACKAGE_PREFIX = "prepay_id=";//支付时需要拼接的一段字符串
public static final String WECHAT_PAY_SGIN_PACKAGE_SGINSTR = "package";//应该package与java关键字冲突,所以提出来单独存储
public static final String notify_url = "https://域名/请求路径"; //如https://www.baidu.com/xx/wechatNotify
}
支付返回实体类
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import java.io.Serializable;
// 详细说明见文档 https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=9_1
@XmlRootElement(name = "xml")
public class WechatPayReturnVo implements Serializable {
private static final long serialVersionUID = 2514714605929906573L;
//返回状态码 SUCCESS/FAIL 此字段是**通信标识**,非交易标识,交易是否成功需要查看result_code来判断
private String return_code;
private String return_msg; //返回信息 当return_code为FAIL时返回信息为错误原因 ,例如 签名失败
private String appId; //调用接口提交的公众账号ID
private String mch_id; //调用接口提交的商户号
private String device_info; //请求支付的终端设备号
private String nonce_str; //微信返回的随机字符串
private String sign; //微信返回的签名值
//业务结果 SUCCESS/FAIL
private String result_code;
private String err_code; //错误代码描述
private String err_code_des; //错误代码
private String trade_type; //交易类型
private String prepay_id; //预支付交易会话标识
private String code_url; //二维码链接
---get/set方法
支付信息类
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import java.io.Serializable;
@XmlRootElement(name = "xml")
public class WechatPayParamsVo{
private String appid; //公众号appid 或 小程序appi(注意这里是小写!!!)
private String mch_id; //商户id
private String nonce_str; //随机字符串
private String sign; //签名
private String sign_type = "MD5"; //签名类型 默认MD5
private String body; // 商品描述 [腾讯充值中心-QQ会员充值]
private String detail; //商品详情
private String out_trade_no; //系统订单编号
private Integer total_fee; //支付金额 单位分
private String spbill_create_ip; //终端ip
private String notify_url; //通知地址
private String trade_type; //交易类型
private String product_id; //扫码支付时 必传 产品id
private String openid; //支付对象 openid trade_type = JSAPI时 必传
private String timeStamp = String.valueOf(System.currentTimeMillis());//时间戳
---get/set方法
}
用户进入商户系统先调用登录API获取到Openid(),然后商户系统调用统一下单接口在微信支付服务后台生成预支付交易单,返回正确的预支付交易回话标识后再按扫码、JSAPI、APP、小程序等不同场景生成交易串调起支付
官方文档链接
前端调用wx.login(Object object) 这个接口,获取登录凭证。通过凭证就可以得到一系列信息,如:唯一标识(openid)及本次登录的会话密钥(session_key)
属性 | 类型 | 默认值 | 必填 | 说明 |
---|---|---|---|---|
success | function | 否 | 接口调用成功的回调函数 |
Object res
属性 | 类型 | 说明 |
---|---|---|
code | string | 用户登录凭证(有效期五分钟)。开发者需要在开发者服务器后台调用 auth.code2Session,使用 code 换取 openid 和 session_key 等信息 |
**前端代码**
wx.login({
success (res) {
if (res.code) {
//发起网络请求
wx.request({
url: 'https://test.com/onLogin',
data: {
code: res.code
}
})
} else {
console.log('登录失败!' + res.errMsg)
}
}
})
应用场景
商户在小程序中先调用该接口在微信支付服务后台生成预支付交易单,返回正确的预支付交易后调起支付。
接口链接
URL地址:https://api.mch.weixin.qq.com/pay/unifiedorder
请求参数
注意!!这里的appId是小写的
i
字段名 | 变量名 | 必填 | 类型 | 示例值 | 描述 |
---|---|---|---|---|---|
小程序ID | appid(注意这里是小写!!!!) | 是 | String(32) | wxd678efh567hg6787 | 微信分配的小程序ID |
商户号 | mch_id | 是 | String(32) | 1230000109 | 微信支付分配的商户号 |
设备号 | device_info | 否 | String(32) | 013467007045764 | 自定义参数,可以为终端设备号(门店号或收银设备ID),PC网页或公众号内支付可以传"WEB" |
随机字符串 | nonce_str | 是 | String(32) | 5K8264ILTKCH16CQ2502SI8ZNMTM67VS | 随机字符串,长度要求在32位以内。推荐随机数生成算法 |
签名 | sign | 是 | String(64) | C380BEC2BFD727A4B6845133519F3AD6 | 通过签名算法计算得出的签名值,详见签名生成算法 |
签名类型 | sign_type | 否 | String(32) | MD5 | 签名类型,默认为MD5,支持HMAC-SHA256和MD5。 |
商品描述 | body | 是 | String(128) | 腾讯充值中心-QQ会员充值 | 商品简单描述,该字段请按照规范传递,具体请见参数规定 |
商品详情 | detail | 否 | String(6000) | 商品详细描述,对于使用单品优惠的商户,该字段必须按照规范上传,详见“单品优惠参数说明” | |
附加数据 | attach | 否 | String(127) | 深圳分店 | 附加数据,在查询API和支付通知中原样返回,可作为自定义参数使用。 |
商户订单号 | out_trade_no | 是 | String(32) | 20150806125346 | 商户系统内部订单号,要求32个字符内,只能是数字、大小写字母_-|*且在同一个商户号下唯一。详见商户订单号 |
标价币种 | fee_type | 否 | String(16) | CNY | 符合ISO 4217标准的三位字母代码,默认人民币:CNY,详细列表请参见货币类型 |
标价金额 | total_fee | 是 | Int | 88 | 订单总金额,单位为分,详见支付金额 |
终端IP | spbill_create_ip | 是 | String(64) | 123.12.12.123 | 支持IPV4和IPV6两种格式的IP地址。调用微信支付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。订单失效时间是针对订单号而言的,由于在请求支付的时候有一个必传参数prepay_id只有两小时的有效期,所以在重入时间超过2小时的时候需要重新请求下单接口获取新的prepay_id。其他详见时间规则建议:最短失效时间间隔大于1分钟 |
订单优惠标记 | goods_tag | 否 | String(32) | WXG | 订单优惠标记,使用代金券或立减优惠功能时需要的参数,说明详见代金券或立减优惠 |
通知地址 | notify_url | 是 | String(256) | http://www.weixin.qq.com/wxpay/pay.php | 异步接收微信支付结果通知的回调地址,通知url必须为外网可访问的url,不能携带参数。 |
交易类型 | trade_type | 是 | String(16) | JSAPI | 小程序取值如下:JSAPI,详细说明见参数规定 |
商品ID | product_id | 否 | String(32) | 12235413214070356458058 | trade_type=NATIVE时,此参数必传。此参数为二维码中包含的商品ID,商户自行定义。 |
指定支付方式 | limit_pay | 否 | String(32) | no_credit | 上传此参数no_credit–可限制用户不能使用信用卡支付 |
用户标识 | openid | 否 | String(128) | oUpF8uMuAJO_M2pxb1Q9zNjWeS6o | trade_type=JSAPI,此参数必传,用户在商户appid下的唯一标识。openid如何获取,可参考【获取openid】。 |
电子发票入口开放标识 | receipt | 否 | String(8) | Y | Y,传入Y时,支付成功消息和支付详情页将出现开票入口。需要在微信支付商户平台或微信公众平台开通电子发票功能,传此字段才可生效 |
+场景信息 | scene_info | 否 | String(256) | {“store_info” : { “id”: “SZTX001”, “name”: “腾大餐厅”, “area_code”: “440305”, “address”: “科技园中一路腾讯大厦” }} | 该字段常用于线下活动时的场景信息上报,支持上报实际门店信息,商户也可以按需求自己上报相关信息。该字段为JSON对象数据,对象格式为{“store_info”:{“id”: “门店ID”,“name”: “名称”,“area_code”: “编码”,“address”: “地址” }} ,字段详细说明请点击行前的+展开 |
以上打勾的都是已有的参数,其中比较难一点获取的就是 签名(sign)。其它没有获取都就是一些通过随机数算法就可以获取。
签名步骤
第一步,设所有发送或者接收到的数据为集合M,将集合M内非空参数值的参数按照参数名ASCII码从小到大排序(字典序),使用URL键值对的格式(即key1=value1&key2=value2…)拼接成字符串stringA。
特别注意以下重要规则:
◆ 参数名ASCII码从小到大排序(字典序);
◆ 如果参数的值为空不参与签名;
◆ 参数名区分大小写;
◆ 验证调用返回或微信主动通知签名时,传送的sign参数不参与签名,将生成的签名与该sign值作校验。
◆ 微信接口可能增加字段,验证签名时必须支持增加的扩展字段
第二步,在stringA最后拼接上key得到stringSignTemp字符串,并对stringSignTemp进行MD5运算,再将得到的字符串所有字符转换为大写,得到sign值signValue。
◆ key设置路径:微信商户平台(pay.weixin.qq.com)–>账户设置–>API安全–>密钥设置
/**
* 获取微信支付签名
* @param vo 支付必传参数的一个实体类
* @param mchApiKey 商户密钥,在第三步 3小点已经获取
* @throws Exception
*/
public static void getWechatPaySignStr(WechatPayParamsVo vo ,String mchApiKey) throws Exception{
StringBuffer sbuff = new StringBuffer();
//随机字符串
vo.setNonce_str(RandomUtil.randomString(32));
//字典排序
Map<String,Object> paramMap = BeanUtil.beanToMap(vo);
List<String> paramsList = new ArrayList<>(paramMap.keySet());
Collections.sort(paramsList);
for (int i = 0; i < paramsList.size(); i++){
String key = paramsList.get(i);
Object val = paramMap.get(key);
if (val != null){
sbuff.append(key);
sbuff.append("=");
sbuff.append(val);
sbuff.append("&");
}
}
//拼接商户APIkey
sbuff.append("key=".concat(mchApiKey));
//签名MD5转大写加密
String sign = MD5Tools.MD5(sbuff.toString()).toUpperCase();
vo.setSign(sign);
}
在这个方法中:我们主要是将参数转换为XML格式的数据,进行传输给微信。
微信也将处理结果以XML的格式返回给我们,我们再将XML文件转化为我们已经准备好的实体类WechatPayReturnVo
/**
* 微信预支付接口
* @param payParamsVo
* payParamsVo =》
* 必传参数
* appid 小程序或公众号 appid(注意这里是小写!!!)
* mch_id 微信商户id
* out_trade_no 系统订单业务编号
* total_fee 支付金额 单位分
* spbill_create_ip 终端ip
* notify_url 通知地址
* trade_type 交易类型
* openid trade_type = JSAPI时必传
* 可选参数
* body 商品描述 [腾讯充值中心-QQ会员充值]
* detail 商品详情
* @param mchApiKey 商户API秘钥
*
*
* @return
*/
public static WechatSignVo wechatPay(WechatPayParamsVo payParamsVo,String mchApiKey){
WechatSignVo sginVo = null; //返回对象
try {
//初始化参数 生成签名 组装发送参数
initPayParams(payParamsVo, mchApiKey);
String apiParamXml = XmlUtils.beanToXml(payParamsVo, WechatPayParamsVo.class, true); //对象转xml
logger.info("========微信统一预支付参数:{}", apiParamXml);
//发送post请求
String response = HttpRequest.sendPost(WechatApiContext.WECHAT_PAY_API, apiParamXml);
logger.info(response);
//返回信息
WechatPayReturnVo wechatReturn = (WechatPayReturnVo) XmlUtils.xmlToBean(response, WechatPayReturnVo.class); //xml转对象
//这一小部分与下面内容相关--返回数据签名,如果成功就调起小程序支付API
if ("SUCCESS".equals(wechatReturn.getResult_code()) && "SUCCESS".equals(wechatReturn.getReturn_code())){
sginVo = responseSgin(wechatReturn, mchApiKey);
}else {
logger.error("预支付异常:{}", JSONObject.toJSONString(wechatReturn));
}
} catch (JAXBException e) {
logger.error("========微信支付对象转XML异常!");
e.printStackTrace();
}
return sginVo;
}
private static void initPayParams(WechatPayParamsVo payParamsVo,String mchApiKey){
try {
WechatSginUtils.getWechatPaySignStr(payParamsVo, mchApiKey);
}catch (Exception ex){
logger.error("========微信支付计算签名异常!");
ex.printStackTrace();
}
}
传入相应的数据之后,调用这个接口之后会返回一系列数据给我们。
字段名 | 变量名 | 必填 | 类型 | 示例值 | 描述 |
---|---|---|---|---|---|
返回状态码 | 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(64) | C380BEC2BFD727A4B6845133519F3AD6 | 微信返回的签名值,详见签名算法 |
业务结果 | result_code | 是 | String(16) | SUCCESS | SUCCESS/FAIL |
错误代码 | err_code | 否 | String(32) | SYSTEMERROR | 详细参见下文错误列表 |
错误代码描述 | 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) | weixin://wxpay/bizpayurl/up?pr=NwY5Mz9&groupid=00 | trade_type=NATIVE时有返回,此url用于生成支付二维码,然后提供给用户进行扫码支付。注意:code_url的值并非固定,使用时按照URL格式转成二维码即可 |
如果这些参数都返回成功的话,我们需要的参数其实就是一个
prepay_id
,通过prepay_id来进行预支付处理,其它的都是一些额外的信息。那么到现在为止,微信支付已经完成了一大半了。加油,奥力给!
小程序调起支付数据签名字段列表:
字段名 | 变量名 | 必填 | 类型 | 示例值 | 描述 |
---|---|---|---|---|---|
小程序ID | appId(这里是大写!!!!) | 是 | String | wxd678efh567hg6787 | 微信分配的小程序ID |
时间戳 | timeStamp | 是 | String | 1490840662 | 时间戳从1970年1月1日00:00:00至今的秒数,即当前的时间 |
随机串 | nonceStr | 是 | String | 5K8264ILTKCH16CQ2502SI8ZNMTM67VS | 随机字符串,不长于32位。推荐随机数生成算法 |
数据包 | package | 是 | String | prepay_id=wx2017033010242291fcfe0db70013231072 | 统一下单接口返回的 prepay_id 参数值,提交格式如:prepay_id=wx2017033010242291fcfe0db70013231072 |
签名方式 | signType | 是 | String | MD5 | 签名类型,默认为MD5,支持HMAC-SHA256和MD5。注意此处需与统一下单的签名类型一致 |
注意!!这里的appId是大写的
I
packge这个关键字也会和java的关键字冲突,需要进行额外处理。内容也需要进行拼接一个前缀
public class WechatSignVo {
private String appId; ///这里是大写!!!!
private String timeStamp = String.valueOf(System.currentTimeMillis());
private String nonceStr;
private String packageStr; //这里需要做额外处理
private String signType = "MD5";
private String paySign;
---get/set方法
与上一次签名基本相同,主要是处理一下下面这个参数,将packge关键字进行处理再进行加密,并在它之前加上一个前缀prepay_id=
字段名 | 变量名 | 必填 | 类型 | 示例值 | 描述 |
---|---|---|---|---|---|
数据包 | package | 是 | String | prepay_id=wx2017033010242291fcfe0db70013231072 | 统一下单接口返回的 prepay_id 参数值,提交格式如:prepay_id=wx2017033010242291fcfe0db70013231072 |
签名处理工具类
/**
* 预支付返回签名
* @param vo
* @param mchApiKey
* @throws Exception
*/
public static void getWechatReturnSgin(WechatSignVo vo , String mchApiKey) throws Exception{
StringBuffer sbuff = new StringBuffer();
//随机字符串
vo.setNonceStr(RandomUtil.randomString(32));
//字典排序
Map<String,Object> paramMap = BeanUtil.beanToMap(vo);
List<String> paramsList = new ArrayList<>(paramMap.keySet());
Collections.sort(paramsList);
for (int i = 0; i < paramsList.size(); i++){
String key = paramsList.get(i);
Object val = paramMap.get(key);
if (val != null){
//将packge关键字进行处理再进行加密,并在它之前加上一个前缀`prepay_id=`
if ((WechatApiContext.WECHAT_PAY_SGIN_PACKAGE_SGINSTR.concat("Str")).equals(key)){
key = WechatApiContext.WECHAT_PAY_SGIN_PACKAGE_SGINSTR;
}
sbuff.append(key);
sbuff.append("=");
sbuff.append(val);
sbuff.append("&");
}
}
//拼接商户APIkey
sbuff.append("key=".concat(mchApiKey));
//签名MD5转大写加密
String sign = MD5Tools.MD5(sbuff.toString()).toUpperCase();
vo.setPaySign(sign);
}
签名处理方法
/**
* 返回预支付信息,给前端进行调用即可
* @param wechatReturn
* @param mchApiKey
* @return
*/
private static WechatSignVo responseSgin(WechatPayReturnVo wechatReturn, String mchApiKey){
String appid = wechatReturn.getAppid();
String prepayId = wechatReturn.getPrepay_id();
String packageStr = WechatApiContext.WECHAT_PAY_SGIN_PACKAGE_PREFIX.concat(prepayId);
WechatSignVo vo = new WechatSignVo();
vo.setAppId(appid);
vo.setPackageStr(packageStr);
try {
//调用小程序预支付签名方法
WechatSginUtils.getWechatReturnSgin(vo, mchApiKey);
} catch (Exception e) {
logger.error("支付验签异常:");
e.printStackTrace();
}
return vo;
}
回调类型 | errMsg | 说明 |
---|---|---|
success | requestPayment:ok | 调用支付成功 |
fail | requestPayment:fail cancel | 用户取消支付 |
fail | requestPayment:fail (detail message) | 调用支付失败,其中 detail message 为后台返回的详细失败原因 |
在我们第六步中会传一个回调地址的参数,而这个参数路径就是填写我们支付回调这个接口。
//设置回调参数
WechatPayParamsVo vo = new WechatPayParamsVo();
vo.setNotify_url(WechatApiContext.notify_url);//url路径保存在常量中
在这个接口中我们可以做一些支付成功之后的操作如:
@RequestMapping(value = "wechatNotify",method = {RequestMethod.POST})
public void wechatNotify(HttpServletResponse response,HttpServletRequest request){
logger.info("=================微信支付回调=================");
try{
lockWeixinnotifyurl.lock();
PrintWriter out = response.getWriter();
//解析request中的xml
Map<String, String> map = this.xmlToMap(request);
logger.info("回调参数:{}",map);
String returnCode = map.get("return_code");
//订单状态码为“SUCCESS”时进行回调处理
if ("SUCCESS".equals(returnCode)){
String orderNo = map.get("out_trade_no");
//处理订单,如修改订单状态
wkWeikeOrdersService.orderPayDeal(orderNo);
}
out.println(" ");
}catch (IOException e) {
e.printStackTrace();
} catch (DocumentException e) {
e.printStackTrace();
}finally {
lockWeixinnotifyurl.unlock();
}
}
到这里,我们的支付流程就走完了。就从订单到微信支付的一个系统的流程,如果这一篇文章有一些本人没有发现的问题,麻烦各位大佬给我指出一下。不甚感激!