前言
之前研究微信云支付的扫码支付,我将认为重要的内容在本文中分享给大家。
微信云支付相关介绍
微信云支付的相关特点文档中已经介绍的很清楚了,这里就不在介绍了。
微信云支付文档
何为扫码支付
1.“商户主动下单,为每个订单生成一个二维码,顾客打开扫一扫进行扫码后,完成支付”
2.“每个订单一个单独的二维码”
3.“收银机具需要有给顾客展示二维码的屏幕,顾客扫码支付”
4.“希望用户可以在收银机上自助点餐的商户,如肯德基等”
注意:扫码支付的二维码是微信和支付各生成一个,并不是共用同一个二维码
本文介绍扫码支付的支付和退款后台接口的编写,其余功能可根据这两个功能中参数的拼接方法,基本都可以实现
扫码支付
1.准备工作
注册微信支付和支付宝支付的相关服务商账号以及微信云支付的账号,并配置好门店信息,如device_infos和staff_infos(这些信息在请求门店信息中要返回给客户端,可以见微信云支付的请求门店信息的API:请求门店信息)等,具体可以见微信支付、支付宝支付以及微信云支付的相关文档。
2.计算认证码
微信云支付官方文档中提供的是C++版的示例代码,计算认证码的算法是HMAC-SHA256。如果使用C++开发,请查看C++示例代码
请求必须传认证或签名信息。其中退款请求,传签名和签名算法,其他请求传认证码和认证算法。
计算认证码的方法:
public static String caculateAuthenCode(String key, String data) {
Mac sha256_HMAC = null;
String hash = null;
try {
sha256_HMAC = Mac.getInstance("HmacSHA256");
SecretKeySpec secret_key = new SecretKeySpec(key.getBytes(), "HmacSHA256");
sha256_HMAC.init(secret_key);
hash = DatatypeConverter.printHexBinary(sha256_HMAC.doFinal(data.getBytes("UTF-8")));
} catch (Exception e) {
e.printStackTrace();
}
return hash;
}
其中参数key是云支付提供的“认证秘钥”里的字符串,data是你要加密的数据,也就是组织好的json格式的参数。
3.请求门店信息
因为扫码支付的有些请求参数需要在请求门店信息接口返回的数据中获取,例如调用微信云支付的请求门店信息的接口,来获取门店账号(out_shop_id)、终端设备号(device_id)、店员ID(staff_id)以及终端类型(terminal_type)。
根据查询门店信息接口文档,编写请求参数request_content和authen_info,如下
{
"request_content": "{\"out_mch_id\":\"云支付分配给服务商的帐号\",\"out_sub_mch_id\":\"云支付分配给子商户的帐号\",\"nonce_str\":\"26f827a651761f7d\",\"page_num\":1,\"page_size\":100}",
"authen_info": {
"a": {
"authen_code": "计算得到的认证码",
"authen_type": 1
}
}
}
请求示例代码
public String request(String url, String requestContent, AuthenInfo authenInfo) {
String responseContent = null;
try {
CloseableHttpClient client = HttpClients.createDefault();
HttpPost post = new HttpPost();
post.setURI(new URI(url));
JSONObject jsonObject = new JSONObject();
jsonObject.put("request_content", "\"" + requestContent + "\"");//json字符串
jsonObject.put("authen_info", authenInfo);//json对象
StringEntity urlEncodedFormEntity = new StringEntity(jsonObject.toString(), "utf-8");//解决中文乱码问题
urlEncodedFormEntity.setContentEncoding("UTF-8");
urlEncodedFormEntity.setContentType("application/json");//发送json数据需要设置contentType
post.setEntity(urlEncodedFormEntity);
CloseableHttpResponse response = client.execute(post);
HttpEntity entity = response.getEntity();
responseContent = EntityUtils.toString(entity, "UTF-8");
} catch (Exception e) {
e.printStackTrace();
}
return responseContent;
}
值得注意的是参数request_content的结构是json字符串,参数authen_info是json对象,见上面的代码。
status为0代表请求成功
从返回的内容中获取门店账号(out_shop_id)、终端设备号(device_id)、店员ID(staff_id)以及终端类型(terminal_type)
3.根据微信云支付的扫码支付接口文档,编写请求参数
我们可以根据文档中给的参数的类型,编写javabean 然后组织成json字符串,例如
请求内容:
{
"pay_mch_key": {
"pay_platform": 1,
"out_mch_id": "云支付分配给服务商的帐号,固定20个数字或者字母",
"out_sub_mch_id": "云支付分配给子商户的帐号,固定20个数字或者字母",
"out_shop_id": "云支付唯一标识门店的账号"
},
"pay_content": {
"out_trade_no": "云支付订单前缀46f827a751761fff",
"total_fee": 60,
"fee_type": "CNY",
"body": "订单介绍",
"wxpay_pay_content_ext": {
"attach": "1",
"goods_tag": "1234",
"product_id": "spidSPBH129I902",
"limit_pay": "no_credit"
}
},
"order_client": {
"device_id": "终端设备号",
"staff_id": "店员ID",
"terminal_type": 终端类型,
"sdk_version": "SDK",
"spbill_create_ip": "调用云支付API的机器IP"
},
"nonce_str": "16f827a651761f77"
}
认证信息
{
"a": {
"authen_code": "计算得到的认证码",
"authen_type": 1
}
}
完成的请求参数结构
{
"request_content": "{\"pay_mch_key\":{\"pay_platform\":1,\"out_mch_id\":\"云支付分配给服务商的帐号\",\"out_sub_mch_id\":\"云支付分配给子商户的帐号\",\"out_shop_id\":\"云支付唯一标识门店的账号\"},\"pay_content\":{\"out_trade_no\":\"云支付订单前缀46f827a751761fff\",\"total_fee\":60,\"fee_type\":\"CNY\",\"body\":\"订单介绍\",\"wxpay_pay_content_ext\":{\"attach\":\"1\",\"goods_tag\":\"1234\",\"product_id\":\"spidSPBH129I902\",\"limit_pay\":\"no_credit\"}},\"order_client\":{\"device_id\":\"终端设备号\",\"staff_id\":\"店员ID\",\"terminal_type\":终端类型,\"sdk_version\":\"SDK\",\"spbill_create_ip\":\"调用云支付API的机器IP\"},\"nonce_str\":\"16f827a651761f77\"}",
"authen_info": {
"a": {
"authen_code": "通过计算得到的认证码",
"authen_type": 1
}
}
}
值得注意的是,
为了保护不同商户的订单号不重复,云支付为每个服务商录入的子商户分配了“云支付订单前缀”,在云支付后台的商户详情中可以看到,该商户的订单和退款单必须以云支付子商户号做前缀。
out_trade_no是云支付订单前缀加自定义字符串构成,比如,我的云支付订单前缀是ABCdfFS,则我可以设置订单号为
ABCdfFS46f827a7517,订单前缀跟自定义字符串之前不能有特殊字符“+”或者“-”等
请求示例代码
public String request(String url, String requestContent, AuthenInfo authenInfo) {
String responseContent = null;
try {
CloseableHttpClient client = HttpClients.createDefault();
HttpPost post = new HttpPost();
post.setURI(new URI(url));
JSONObject jsonObject = new JSONObject();
jsonObject.put("request_content", "\"" + requestContent + "\"");//json字符串
jsonObject.put("authen_info", authenInfo);//json对象
StringEntity urlEncodedFormEntity = new StringEntity(jsonObject.toString(), "utf-8");//解决中文乱码问题
urlEncodedFormEntity.setContentEncoding("UTF-8");
urlEncodedFormEntity.setContentType("application/json");//发送json数据需要设置contentType
post.setEntity(urlEncodedFormEntity);
CloseableHttpResponse response = client.execute(post);
HttpEntity entity = response.getEntity();
responseContent = EntityUtils.toString(entity, "UTF-8");
} catch (Exception e) {
e.printStackTrace();
}
return responseContent;
}
请求成功,返回数据示例:
根据请求得到的code_url,Android或iOS端就可以生成二维码进行展示了
值得注意的是,订单是有过期时间的,可以自己指定过期时间(见文档),也可以不指定,不指定过期时间的话,默认过期时间是1分半钟,也就是说,如果此时没有扫码支付,这个订单就过期了,得重新生成一个订单并请求获取二维码。