支付
业务流程
以下是支付时序图, 相关接口调用都必须在合作方服务器端完成。
支付流程说明1.如时序1预支付前需要先判断用户是否具有相关的权益。参见【判断用户是否有相关权益】2.如时序2会返回当前用户相关权益信息。3.若用户已具有相关权益,直接执行行时序10使用用户自身权益(需带上合作方订单号)。参见【使用用户自身权益】4.若用户没有相关权益,执行时序3预支付下单接口(需带上合作方订单号),开发平台会返回相关信息及合作方订单号。参见【预下单接口】5.时序5将 billno带给WPS 客户端,WPS客户端会完成后续的支付流程。6.时序8 WPS客户端会将支付成功信息同步通知给合作方客户端。7.时序9 开发平台会异步将支付成功信息通知给合作方服务端(是否支付成功以此为准)。8.开发平台会记录用户的购买记录、使用记录,并和合作方订单号关联,后续以此对账。
相关接口说明
1, 判断用户是否有相关权益2, 预下单接口3, 使用用户自身权益4, 零售下单5, 支付完成后的回调6, 支付完成后的回调实例
1, 判断用户是否有相关权益
判断用户是否有相关权益
接口说明
判断用户是否有相关权益
请求说明
[POST] https://openapi.wps.cn/oauthapi/v2/vas/service/usable
Cookie要求
无
参数说明
参数 | 参数类型 | 是否必须 | 说明 |
---|---|---|---|
access_token | string | required | access_token |
appid | string | required | 应用唯一标识 |
openid | string | required | 用户标识openid |
service_id | string | required | 服务id,开发者对接后可用的服务 |
total_num | int64 | required | 查询数量,仅消耗类型的服务需要传对应的数量,其他的传0 |
Header说明
Header名称 | 是否必须 | 说明 |
---|---|---|
Content-type | required | 值为:application/json |
返回
{ "result":0 "msg":"success", "data":{ "docervip":946656000, #说明:稻壳会员过期时间 "supervip":946656000, #说明:超级会员过期时间 "wpsvip":946656000, #说明:WPS会员过期时间 "expire_time":946656000, #说明:特权过期时间 "total":TOTAL #说明:特权剩余总次数 }}
2, 预下单接口
预下单接口
接口说明
预下单接口
请求说明
[POST] https://openapi.wps.cn/oauthapi/v2/vas/pay/preorder
参数说明
参数 | 参数类型 | 是否必须 | 说明 |
---|---|---|---|
access_token | string | required | access_token |
appid | string | required | 应用唯一标识 |
openid | string | required | 用户标识openid |
service_id | string | required | 服务id,开发者对接后可用的服务 |
total_num | int64 | required | 查询数量,仅消耗类型的服务需要传对应的数量,其他的传0 |
billno | string | required | 合作方自己的订单号,需要合作方每次下单时保证唯一未使用的订单号,长度不超过32位字符 |
subject | string | optional | 购买内容,当服务类型为第三方自己的服务时传对应的服务id |
csource | string | required | 购买来源 |
client_ip | string | required | 客户端IP,由接入方获取客户端ip后传过来 |
Header说明
Header名称 | 是否必须 | 说明 |
---|---|---|
Content-type | required | 值为:application/json |
返回
{ "result":0 "msg":"success"}
3, 使用用户自身权益
使用用户自身权益
接口说明
使用用户自身权益
请求说明
[POST] https://openapi.wps.cn/oauthapi/v2/vas/service/use
参数说明
参数 | 参数类型 | 是否必须 | 说明 |
---|---|---|---|
access_token | string | required | access_token |
appid | string | required | 应用唯一标识 |
openid | string | required | 用户标识openid |
service_id | string | required | 服务id,开发者对接后可用的服务 |
total_num | int64 | required | 查询数量,仅消耗类型的服务需要传对应的数量,其他的传0 |
billno | string | required | 合作方自己的订单号,需要合作方每次下单时保证唯一未使用的订单号,长度不超过32位字符 |
Header说明
Header名称 | 是否必须 | 说明 |
---|---|---|
Content-type | required | 值为:application/json |
返回
{ "result":0 "msg":"success"}
4, 零售下单
零售下单
接口说明
零售下单
请求说明
[POST] https://openapi.wps.cn/oauthapi/v2/vas/pay/customorder
参数说明
参数 | 参数类型 | 是否必须 | 说明 |
---|---|---|---|
access_token | string | required | access_token |
appid | string | required | 应用唯一标识 |
billno | string | required | 合作方自己的订单号,需要合作方每次下单时保证唯一未使用的订单号,长度不超过32位字符 |
openid | string | required | 用户标识openid |
payment | string | required | 支付方式,目前只支持 qrcode: 二维码支付 ios: ios支付 (预下单) android_wechat: 安卓微信支付 (预下单) android_alipay: 安卓支付宝支付 (预下单) |
service_id | string | required | 服务id,开发者对接后可用的服务 |
subject | string | required | 购买内容,当服务类型为第三方自己的服务时传对应的服务id |
csource | string | required | 购买来源 |
total_fee | int64 | required | 订单金额(单位: 分) |
count | int64 | required | 购买数量 |
Header说明
Header名称 | 是否必须 | 说明 |
---|---|---|
Content-type | required | 值为:application/json |
返回
{ "result":0 "msg":"success", "data":{ "url":"URL", "billno":"BILLNO" }}
5, 支付完成后的回调
支付完成后的回调
接口说明
支付完成后的回调
请求说明
[POST] redirect_uri?billno=BILLNO&app_id=APPID&service_id=SERVICEID&sig=SIG
参数说明
参数 | 参数类型 | 是否必须 | 说明 |
---|---|---|---|
billno | string | required | 合作方自己的订单号,需要合作方每次下单时保证唯一未使用的订单号,长度不超过32位字符 |
app_id | string | required | 应用唯一标识 |
service_id | string | required | 用户实际购买的服务id |
sig | string | required | 签名值 |
返回
接口需要直接输出”ok”字符串,代表通知处理业务成功,其他输出都会视为回调失败,失败后前三次间隔1s左右,失败三次后间隔半小时重试一次
6, 支付完成后的回调实例
支付完成后的回调实例下载Demo
将请求的参数和对应的值以key、value形式放在数组中,对key按自然序排列后。
对数组循环以key=value形式拼接成字符串str,将screatkey拼接在str后面,再md5加密成32位小写字符串。
java 代码实现:
import java.util.TreeMap;
import java.util.Map.Entry;
public class Sig{
public static void main(String []args){
TreeMap<String, String> params = new TreeMap<String, String>();
params.put("billno", "");
params.put("app_id", "");
params.put("service_id", "");
params.put("sig", "");
System.out.println(validSig(params));
}
private static String getKeySortparamters(TreeMap<String, String> map) {
StringBuffer sb = new StringBuffer();
if (map == null) {
return "";
}
for (Entry<String, String> entry : map.entrySet()) {
sb.append(entry.getKey()).append("=").append(entry.getValue());
}
return sb.toString();
}
public static Boolean validSig(TreeMap<String, String> map){
String RequestSig = map.get("sig");
map.remove("sig");
MD5Util md5 = new MD5Util();
String sort = getKeySortparamters(map);
String sig = md5.getMD5(getKeySortparamters(map) + "YOUR SECRET KEY");
if(sig.equals(RequestSig)){
return true;
}
return false;
}
}
php 代码实现:
$params = array(
'billno' => "",
'app_id' => '',
'sig' => '',
'service_id' => '',
);
$sig_helper = new Sig();
$key = 'xxxxx'; // 支付回调密钥
list($allow_access, $msg) = $sig_helper->wps_valid_invoke($params, $key);
<?php
class Sig
{
public function wps_generate_sig($param_array, $key)
{
$str = '';
//对param_array中的参数名称进行升序排序
if (is_array($param_array)) {
$this->wps_mksort($param_array);
//按照如下格式转换数组为string格式
$str = $this->wps_key_value($param_array);
} else {
$str = $param_array;
}
//string末端补充api_secret密钥
$str .= $key;
//生成32位小写MD5为最终的数据签名
return array(TRUE, md5($str));
}
/**
* 多维数组的 ksort 排序
* @param array $a
*/
private function wps_mksort(& $a)
{
ksort($a);
foreach ($a as &$value) {
if (is_array($value)) {
$this->wps_mksort($value);
}
}
}
private function wps_key_value($data)
{
$str = "";
if (is_array($data)) {
foreach ($data as $k => $v) {
if ($v !== NULL) {
$str .= $k . "=" . $this->wps_key_value($v);
}
}
} else {
$str = $data;
}
return $str;
}
//参数校验
public function wps_valid_invoke($param, $signKey)
{
//加密键值
$sig_keys = array('sig', 'pass');
foreach ($sig_keys as $key) {
if (isset($param[$key])) {
$sig_request = $param[$key];
unset($param[$key]);
}
}
if (!isset($sig_request)) {
return array(FALSE, 'needSigParam');
}
list($success, $sig) = $this->wps_generate_sig($param, $signKey);
if (!$success) {
return array($success, $sig);
}
if ($sig != $sig_request) {
return array(FALSE, 'wrongSigParam');
}
return array(TRUE, 'passed');
}
}