写在前面
- 文中将${seq}定义为参数名为seq的参数,相关的表中有参数描述
- 文中将${timeout*}定义为可选的参数timeout
- 本文当中所有涉及到时间的参数都使用精确到毫秒的时间戳,例1535595007983表示Thu Aug 30 2018 10:10:07 GMT+0800 (中国标准时间)
- 本文中涉及到分页的接口使用skip、limit参数,表示跳过skip行取至多limit行,返回头X-Total-Count参数表示查询总行数
- 所有接口使用HTTP状态码200表示成功,其他状态码表示失败,如有特例将在具体接口中说明
资源属性描述
对于接口中出现的资源属性将不再说明
PAYMENT结构
| 参数名 | 类型 | 说明 |
|---|---|---|
| id | string | 服务器订单号 |
| seq | string(36) | 客户端订单号 |
| title | string(128) | 订单描述 |
| status | 订单状态,见订单状态转换图 | |
| accepted | 订单处理中,结果未知,需要继续查询订单状态 | |
| normal | 已支付,就是支付成功,此时钱已经入服务商账户 | |
| refunding | 退款中,结果未知,需要继续查询订单状态 | |
| refund | 部分退款,此时钱部分回到用户账户 | |
| closed | 关闭,此时钱已经回到用户账户 | |
| status_msg | 状态信息,如果有需要输出的时候,例如错误的时候,有值不代表有错误
|
|
| create_time | timestamp | 订单创建时间戳(毫秒) |
| notify_uri | string | 扫码支付时二维码信息 |
| account_id | 支付通道 | 见支持的支付通道定义 |
| trade_id | string | 业务订单号,如果存在 |
| detail | list | 明细,如果存在则输出 |
| └ tags | list | 明细标签组合,这是一个集合,取值不仅限于所列出值,见支持的标签 |
| └ card | string | 如果是卡支付的话则输出为卡号,非卡支付不输出 |
| └ amount | int | 实付金额 |
| └ balance | int | 交易后余额,wallet时给出该值 |
| └ time_end(部分渠道输出) | timestamp | 支付完成时间 |
支持的支付通道定义
| 定义名 | 说明 |
|---|---|
| 微信 | |
| alipay | 支付宝 |
| mipay | 小米支付 |
| xqpos | 享钱 |
| kqpay | 快钱 |
| lklpay | 拉卡拉 |
| unionpay | 银联 |
| ums | 银联商务 |
| icbc | 工商银行 |
| jsb | 江苏银行 |
| cmb | 招商银行 |
| swiftpay | 兴业银行(威富通) |
| third | 本地第三方系统 |
| child | 子订单 |
支持的标签
| 定义名 | 说明 |
|---|---|
| wx | 微信 |
| ali | 支付宝 |
| union | 银联 |
| jd | 京东 |
| sh | 大众闪惠 |
| wing | 翼支付 |
| scan | 刷卡支付,B扫C,商户扫用户 |
| qr | 扫码支付,C扫B,用户扫商户 |
| h5 | 公众号支付 |
| app | APP支付 |
| card | 卡支付 |
| wallet | 储值消费 |
| helpbuy | 代付优惠 |
| credit | 透支消费 |
| subsidy | 福利消费 |
| coupon | 券 |
| settle | 结算,一般不计入总金额 |
| child | 子订单 |
订单状态转换图
graph LR
accepted(accepted)
accepted --> |支付失败| closed
closed --> |退款请求| refunding(refunding)
refunding --> |退款成功| closed
accepted --> |发起了撤单| refunding5(refunding)
refunding5 --> |撤单成功| closed
accepted --> |支付成功| normal
normal --> |退款请求| refunding2(refunding)
refunding2 --> |退款失败| normal
refunding2 --> |部分退款成功| refund
refunding2 --> |全额退款成功| closed
refund --> |退款请求| refunding4(refunding)
refunding4 --> |部分退款成功| refund
refunding4 --> |部分退款失败| refund
refunding4 --> |全额退款成功| closed
接口描述
- 测试环境地址
http://pre.sovell.com/sovellpay/v2
- 生产环境地址
http://pass.sovell.com/sovellpay/v2
接口中所有token参数都是通过OAuth2授权取得
R1.查询交易单
- 调用频率同一订单不超过1次/秒
- 对于不存在的seq,限制10次/每分钟
- 系统内只有一个订单,退款与支付同属一个订单
GET http://${domain}/sovellpay/v2/trade/${seq}
?timeout=${timeout*}
Authorization: Bearer ${token}
| 参数名 | 类型 | 说明 |
|---|---|---|
| seq | string(36) | 客户端订单号 |
| timeout | string | 阻塞查询时用,传0则立即返回结果,否则在timeout时间内若交易单状态发生变化时返回结果,格式为xxxs,默认10s |
| 其他 | 见其他请求参数说明 |
{
"id": ${id},
"status": ${status},
"status_msg": ${status_msg*},
"create_time": ${create_time},
"title": ${title},
"account_id": ${account_id*},
"notify_uri": ${notify_uri},
"seq": ${seq},
"trade_id": ${trade_id*},
"detail": [
{"tags": [${tags}...], "amount": ${amount}, "time_end": ${time_end*}}
]
}
- 错误返回
|参数名|类型|说明|
|:---|:---|:---|:---|
|code |int |结果状态码|
| |404 |不存在指定订单|
| |408 |超出限制,需要重新调用,接口会阻塞|
|msg |string |结果消息|
R2.查询可用账户
GET http://${domain}/sovellpay/v2/accounts
Authorization: Bearer ${token}
- 返回
[{
"name": ${name},
"account_id": ${account_id},
"id": ${mch_account},
"user_account": ${user_account},
"status": ${status},
"balance": ${balance*}
...
}...]
| 参数名 | 类型 | 说明 |
|---|---|---|
| name | string | 账户名 |
| mch_account | string | 商户账户 |
| user_account | string | 用户账户 |
| status | 账户状态 | |
| normal | 正常 | |
| disabled | 禁用 | |
| account_id | 支付通道 | 见支持的支付通道定义 |
- 储值账户附加内容
| 参数名 | 类型 | 说明 |
|---|---|---|
| balance | int | 余额(分) |
| property | 属性,可选 | |
| └ name | 姓名,可选 | |
| └ corp_id | 企业id,可选 | |
| └ depart | 部门路径,/分割,不以/起始 | |
| └ no | 工号 | |
| what | 一定为wallet |
- 信用账户附加内容
| 参数名 | 类型 | 说明 |
|---|---|---|
| balance | int | 余额(分),表示剩余透支余额 |
| what | 一定为credit |
- 代付优惠账户附加内容
一般不需要体现在界面上
| 参数名 | 类型 | 说明 |
|---|---|---|
| balance | int | 优惠余额 |
| what | 一定为helpbuy |
- 券附加内容
| 参数名 | 类型 | 说明 |
|---|---|---|
| balance | int | 券余额(分),即券面额 |
| what | 一定为coupon |
- 错误返回
|参数名|类型|说明| |:---|:---|:---|:---| |code | |结果状态码| ||204|查询成功,但找不到| |msg |string |结果消息|
C1.单用户统一交易接口
- 当用于刷卡支付时,即商户扫用户的码,调用时传入
pay_code即可- 当用于扫码支付时,即用户扫商户的码,传入
app参数,然后使用返回的notify_uri生成二维码- 当用于公众号支付时,页面直接跳转至
notify_uri,交易完成后会跳转至redirect_uri- 当用于支付订单部分退款时,将原订单的
id传入original_id
POST http://${domain}/sovellpay/v2/trade
?pay_code={pay_code*}
&redirct_uri={redirect_uri*}
&app={app*}
&expire={expire*}
&sub={sub*}
&timeout=${timeout*}
&sign=${sign}
Authorization: Bearer ${token}
Content-Type: application/json
{
"intent": ${intent},
"title": ${title*},
"seq": ${seq},
"attach": ${attach*},
"device": {
"id": ${device_id},
"name": ${device_name},
},
"goods": [{
"id": ${id},
"wechat_id": ${wechat_id},
"alipay_id": ${alipay_id},
"name": ${name},
"quantity": ${quantity},
"price": ${price}
}*...],
"amount_due": ${amount_due*},
"original_id": ${original_id*},
"tags": [${tags}...]
}
- 支付参数说明
| 参数名 | 类型 | 说明 |
|---|---|---|
| pay_code(B扫C时必选) | string | 支付码,支持微信反扫、支付宝反扫 |
| redirect_uri(可选) | string | 交易完成后的重定向地址 |
| app(可选) | ||
| 微信扫码支付 | ||
| alipay | 支付宝扫码支付 | |
| expire(可选) | string | 过期时间,由于各交易通道对过期的定义的差异,该时间定义为最大过期时间,实际过期时间会存在差异,没有单位时默认毫秒,格式为xxxs,默认300s,最大600s |
| sub(可选) | 用户id | |
| timeout | string | 阻塞查询时用,只在B扫C时有效,传0则立即返回结果,否则在timeout时间内若交易单状态发生变化时返回结果,格式为xxxs,默认10s |
| sign | string | 签名,请看如何签名 |
| 其他 | 见其他请求参数说明 |
- 订单参数说明
| 参数名 | 类型 | 说明 |
|---|---|---|
| seq | string | 客户端订单号(商户订单号、下位订单号)当退款时请使用和原订单不同的seq |
| title(可选) | string(128) | 订单描述 |
| intent | string | 交易意图,参数说明见下文 |
| original_id(退款时必选) | string | 原单据${id},退款时该参数必须提供 |
| goods(可选) | 商品,如果有 | |
| └ id | 商品id | |
| └ wechat_id | 微信支付定义的统一商品编号(如果有) | |
| └ alipay_id | 支付宝支付定义的统一商品编号(如果有) | |
| └ name | 商品名称 | |
| └ quantity | 商品数量 | |
| └ price | 商品单价(分) | |
| operator(可选) | string | 操作员,可空 |
| attach(可选) | string | 附加字段,可空,支付宝赋值store_id,微信赋值device_info |
| device(可选) | object | 设备附加属性 |
| └ device_id | string | 设备id,统计时会使用这个id作为拆分凭据 |
| └ device_name | string | 设备名 |
| amount_due(可选) | int | 整单的应付金额,仅用作记录,为空则自动计算 |
| tags(可选) | 交易描述标签,用于策略消费 | |
| breakfast | 早餐 | |
| lunch | 午餐 | |
| dinner | 晚餐 | |
| snack | 夜宵 |
- 关于intent字符串的说明
| 格式 | 说明 |
|---|---|
| pay ${amount} | 支付指定金额(分) |
| pay ${amount} use #${user_account} | 支付指定金额(分),使用用户的指定账户 |
| pay ${amount} via ${seq} | 用于合并支付时使用,不发生支付,使用父订单结果 |
| charge ${amount} | 充值指定金额(分)(例如 charge 1) |
| refund ${amount} | 退款指定金额(分)(例如 refund 1) |
| refund * | 全额退款 |
-
返回
PAYMENT结构 -
错误返回
|参数名|类型|说明|
|:---|:---|:---|:---|
|code |int |结果状态码|
| |400 |请求参数错误|
| |402 |余额不足|
| |404 |不存在指定订单|
| |406 |支付异常,可能是通道错误|
| |408 |超出限制,需要重新调用,接口会阻塞|
| |409 |订单已创建,需要查询订单状态|
|msg |string |结果消息|
D1.全额撤单
- 必须由发起交易所在终端发起撤单
- 对于不存在的seq,调用次数不超过10次/分钟
- 对于撤单一直失败的订单,失败次数不超过5次/小时
DELETE http://${domain}/sovellpay/v2/trade/${seq}?sign=${sign}
Authorization: Bearer ${token}
| 参数名 | 类型 | 说明 |
|---|---|---|
| seq | string(36) | 客户端订单号 |
| sign | string | 签名,请看如何签名 |
| 其他 | 见其他请求参数说明 |
-
返回
PAYMENT结构 -
错误返回
|参数名|类型|说明|
|:---|:---|:---|:---|
|code |int |结果状态码|
| |404 |不存在指定订单|
| |408 |超出限制,需要重新调用,接口会阻塞|
| |409 |已经退款,不能重复退款,可以查询原单据状态|
|msg |string |结果消息|
其他说明
其他请求参数说明
| 参数名 | 类型 | 说明 |
|---|---|---|
| domain | string | 接口域名 |
| token | string | 访问令牌,通过OAuth2授权取得 |
推荐的支付流程
H5支付流程
- 需要从前端页面跳转至notify_uri,用户在该页面完成相关业务操作后再回跳到商户指定页面。
sequenceDiagram
用户->>商户系统: 下单
商户系统->>PasS: 调用交易接口C1
note right of PasS: 填写商户单号seq <br/>填写支付金额to <br/>填写回调地址redirect_uri
PasS->>商户系统: 返回结果,取到notify_uri
opt 失败重试
商户系统->>PasS: 检查参数并重新发起交易C1
PasS->>商户系统: 返回结果
end
商户系统->>用户: 跳转至notify_uri
用户->>PasS: 进入支付页面
PasS->>商户系统: 完成支付,进入redirect_uri
商户系统-->>用户: 结果展示页面
用户-->>商户系统: 查询支付结果
loop status!=accepted
商户系统->>PasS: 查询交易单状态R1
PasS->>商户系统: 返回结果
end
商户系统-->>用户: 结果展示页面
opt 如果需要撤单
商户系统->>PasS: 发起撤单D1
PasS->>商户系统: 返回结果
loop status!=refudning
商户系统->>PasS: 查询交易单状态R1
PasS->>商户系统: 返回结果
end
end
如何签名
-
设所有发送或者接收到的数据为集合M,将集合M内非空参数值的参数按照参数名ASCII码从小到大排序(字典序),使用URL键值对的格式(即key1=value1&key2=value2…)拼接成字符串。 特别注意以下重要规则:
- 参数名ASCII码从小到大排序(字典序);
- 如果参数的值为空不参与签名;
- 参数名区分大小写;
- 验证时,传送的sign参数不参与签名,将生成的签名与该sign值作校验。
- 接口可能增加字段,验证签名时必须支持增加的扩展字段
-
最后拼接上key得到stringSignTemp字符串,并对stringSignTemp进行MD5运算,再将得到的字符串所有字符转换为大写,得到sign值signValue。
举例: 假设传送的参数如下:
{
"title": "这是演习",
"seq": "15",
"to": "alipay:1",
"expire": "60s",
"app": "alipay",
"goods": [
{
"name": "hello",
"id": "123",
"quantity": 1,
"price": 1
}
]
}
第一步:对参数按照key=value的格式,并按照参数名ASCII字典序排序如下:
app=alipay&expire=60s&goods=[{"name":"hello","id":"123","quantity":1,"price":1}]&seq=15&title=这是演习&to=alipay:1
第二步:拼接API密钥,key为终端密钥(即client_secret):
app=alipay&expire=60s&goods=[{"name":"hello","id":"123","quantity":1,"price":1}]&seq=15&title=这是演习&to=alipay:1&key=3AjtoC3NpW
第三步:计算签名
//注:MD5签名方式
sign = MD5(stringSignTemp)="8b8c383ad3c10bc3e22cdd1aff4e86f2"
最终得到最终发送的数据:
{
"title": "这是演习",
"seq": "15",
"to": "alipay:1",
"expire": "60s",
"app": "alipay",
"goods": [
{
"name": "hello",
"id": "123",
"quantity": 1,
"price": 1
}
],
"sign": "8b8c383ad3c10bc3e22cdd1aff4e86f2"
}
调用示例
支付码反扫成功示例
POST http://dev.sovell.com/sovellpay/v1/trade HTTP/1.1
Authorization: Bearer ee3ddf8ef03fa549028ff18a7d314116
Content-Type: application/json; charset=utf-8
Host: dev.sovell.com
Content-Length: 75
Connection: Keep-Alive
{
"pay_code": "280978609094625751",
"seq": 636207894193718264,
"to": "cmbcpay:1"
}
HTTP/1.1 200 OK
Server: nginx/1.6.3
Date: Mon, 23 Jan 2017 09:31:08 GMT
Content-Type: application/json; charset=utf-8
Content-Length: 12
Connection: keep-alive
{
"id": "203b4954eeb949b183c94f859397de2a",
"status": "normal",
"create_timestamp": 1487908305815,
"title": "这是演习",
"seq": "636207894193718264"
}