Appearance
基本说明
接口地址
环境
| 环境 | 链接 |
|---|---|
| 联调环境 | https://test.ematecard.com |
| 生产环境 | https://gateway.ematecard.com |
接口调用
后台权限开通
请求前需配置API权限
- 白名单验证: 请求API前需要给商户分配允许访问白名单IP(未设置则无法访问)
- API权限验证: 请求API前需要给商户分配vcc2 API访问权限(未设置则无法访问)
接口调用凭据
调用接口需要在请求头中携带Authorization字段。服务端通过该值验证用户身份。Authorization字段值通过getToken接口获取。
接口限流
根据客户端维度进行限流,所有接口共用限流份额。 响应头中会返回限流情况,如:
X-RateLimit-Limit 60
X-RateLimit-Remaining 53
返回结果
只要请求被服务端正常处理了,响应的 HTTP 状态码均为200,响应体为json格式。 业务若发生错误,会返回http状态码200业务错误码位于响应体的code字段中。
其他情况,返回非200的http状态码,已知场景在下表列出。 注意:非200状态码时,响应体可能为非标准json,调用方不应该依赖响应体。
| 场景 | http状态码 |
|---|---|
| token验证失败 | 401 |
| 不在白名单或无权限调用 | 403 |
| 接口不存在 | 404 |
| 请求方法有误 | 405 |
| 限流 | 429 |
| 服务异常 | 500 |
| 服务异常 | 502 |
| 其他未知异常 | 其他非200状态码 |
正确返回结果
若业务处理成功,返回结构如下,code固定为"0000"
json
{
"code": "0000",
"message": "成功",
"data": {
}
}业务错误返回结果
若业务出错,其返回值示例如下:
json
{
"code": "1003",
"message": "业务处理失败",
"data": {}
}code 非"0000"代表着该请求调用失败。 Code 表示具体出错的错误码,当请求出错时可以先根据该错误码在公共错误码和当前接口对应的错误码列表里面查找对应原因和解决方案。 Message 显示出了这个错误发生的具体原因,随着业务发展或体验优化,此文本可能会经常保持变更或更新,用户不应依赖这个返回值。
常用错误码
| 错误码 | 场景 |
|---|---|
| 0000 | 请求成功 |
| 0348 | 无效商户 |
| 0349 | 流水号重复 |
| 0452 | token验证错误 |
| 0454 | 请求签名验证失败 |
| 1000 | 参数不合法 |
| 4000 | 请求处理失败 |
请求参数签名
用户请求接口时需要在请求头中传递sign字段,值为当前请求参数签名。getToken接口除外。
post请求和get请求签名方式不同。
post请求
在请求头中添加字段:
| 请求头 | 说明 |
|---|---|
| timestamp | 请求秒级时间戳 |
| sign | 签名 |
sign生成方式为
使用.拼接 请求头中的timestamp、请求体,即: data = "{timestamp}.{rawBody}"
如 timestamp为 12345698
rawBody为 aaa
则data = "12345698.aaa"使用merchant_secret,对data进行SHA256运算。
HMAC-SHA256(data, merchant_secret)
get请求
在请求头中添加字段:
| 请求头 | 说明 |
|---|---|
| timestamp | 请求秒级时间戳 |
| sign | 签名 |
按键名对请求参数进行 ASCII 升序排序
使用&拼接排序后参数。 如 type=1&uid=1001
拼接timestamp和排序后字符串 data = "12345698.type=1&uid=1001"
使用merchant_secret,对data进行SHA256运算。
HMAC-SHA256(data, merchant_secret)
响应签名
如果请求被正常处理,服务端会在响应头求头中添加字段:
| 请求头 | 说明 |
|---|---|
| timestamp | 响应秒级时间戳 |
| sign | 签名 |
调用方可用于验证响应的可靠性。 注意:异常情况下,服务端可能无法完成签名,因此调用方应该先判断timestamp,sign响应头有值才进行签名验证。
sign生成方式为
使用.拼接 响应头中的timestamp、响应体,即: data = "{timestamp}.{rawBody}"
如 timestamp为 12345698
rawBody为 aaa 则data = "12345698.aaa"使用merchant_secret,对data进行SHA256运算。
HMAC-SHA256(data, merchant_secret)
签名,验签代码示例
go
package sign
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"net/url"
"sort"
"strconv"
"strings"
"time"
)
func hmacSign(data, secret string) string {
h := hmac.New(sha256.New, []byte(secret))
h.Write([]byte(data))
return hex.EncodeToString(h.Sum(nil))
}
// GET 查询串:键名 ASCII 升序,数组展开为重复键。值用原始值(url.Values 已是解码后的值)
func buildGetPayload(params url.Values) string {
keys := make([]string, 0, len(params))
for k := range params {
keys = append(keys, k)
}
sort.Strings(keys)
var pairs []string
for _, k := range keys {
for _, v := range params[k] {
pairs = append(pairs, k+"="+v)
}
}
return strings.Join(pairs, "&")
}
// 生成请求签名(GET 传 params,POST 传 rawBody),返回需写入请求头的 timestamp、sign
func SignRequest(method string, params url.Values, rawBody, secret string) (timestamp, sign string) {
timestamp = strconv.FormatInt(time.Now().Unix(), 10)
payload := rawBody
if strings.ToUpper(method) == "GET" {
payload = buildGetPayload(params)
}
return timestamp, hmacSign(timestamp+"."+payload, secret)
}
// 验证响应签名(timestamp、sign 取自响应头,rawBody 为原始响应体)
func VerifyResponse(timestamp, sign, rawBody, secret string) bool {
expected := hmacSign(timestamp+"."+rawBody, secret)
return hmac.Equal([]byte(expected), []byte(sign))
}js
const crypto = require('crypto');
function hmac(data, secret) {
return crypto.createHmac('sha256', secret).update(data, 'utf8').digest('hex');
}
// GET 查询串:键名 ASCII 升序,数组展开为重复键。值用原始值(不要 URL 编码)
function buildGetPayload(params) {
const pairs = [];
for (const key of Object.keys(params).sort()) {
const value = params[key];
for (const item of Array.isArray(value) ? value : [value]) {
pairs.push(`${key}=${item}`);
}
}
return pairs.join('&');
}
// 生成请求签名,返回需写入请求头的 timestamp、sign
function signRequest(method, { params = {}, rawBody = '' } = {}, secret) {
const timestamp = Math.floor(Date.now() / 1000).toString();
const payload = method.toUpperCase() === 'GET' ? buildGetPayload(params) : rawBody;
return { timestamp, sign: hmac(`${timestamp}.${payload}`, secret) };
}
// 验证响应签名(timestamp、sign 取自响应头,rawBody 为原始响应体文本)
function verifyResponse(timestamp, sign, rawBody, secret) {
const expected = hmac(`${timestamp}.${rawBody}`, secret);
return expected.length === sign.length &&
crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(sign));
}php
function hmacSign(string $data, string $secret): string {
return hash_hmac('sha256', $data, $secret);
}
// GET 查询串:键名 ASCII 升序,数组展开为重复键。值用原始值(不要 URL 编码)
function buildGetPayload(array $params): string {
ksort($params, SORT_STRING);
$pairs = [];
foreach ($params as $key => $value) {
foreach ((array) $value as $item) {
$pairs[] = $key . '=' . $item;
}
}
return implode('&', $pairs);
}
// 生成请求签名,返回需写入请求头的 timestamp、sign
function signRequest(string $method, array $params, string $rawBody, string $secret): array {
$timestamp = (string) time();
$payload = strtoupper($method) === 'GET' ? buildGetPayload($params) : $rawBody;
return ['timestamp' => $timestamp, 'sign' => hmacSign("$timestamp.$payload", $secret)];
}
// 验证响应签名(timestamp、sign 取自响应头,rawBody 为原始响应体)
function verifyResponse(string $timestamp, string $sign, string $rawBody, string $secret): bool {
return hash_equals(hmacSign("$timestamp.$rawBody", $secret), $sign);
}