Skip to content

基本说明

接口地址

环境

环境链接
联调环境https://test.ematecard.com
生产环境https://gateway.ematecard.com

接口调用

后台权限开通

请求前需配置API权限

  1. 白名单验证: 请求API前需要给商户分配允许访问白名单IP(未设置则无法访问)
  2. 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流水号重复
0452token验证错误
0454请求签名验证失败
1000参数不合法
4000请求处理失败

请求参数签名

用户请求接口时需要在请求头中传递sign字段,值为当前请求参数签名。getToken接口除外。
post请求和get请求签名方式不同。

post请求

在请求头中添加字段:

请求头说明
timestamp请求秒级时间戳
sign签名

sign生成方式为

  1. 使用.拼接 请求头中的timestamp、请求体,即: data = "{timestamp}.{rawBody}"
    如 timestamp为 12345698
    rawBody为 aaa
    则data = "12345698.aaa"

  2. 使用merchant_secret,对data进行SHA256运算。
    HMAC-SHA256(data, merchant_secret)

get请求

在请求头中添加字段:

请求头说明
timestamp请求秒级时间戳
sign签名
  1. 按键名对请求参数进行 ASCII 升序排序

  2. 使用&拼接排序后参数。 如 type=1&uid=1001

  3. 拼接timestamp和排序后字符串 data = "12345698.type=1&uid=1001"

  4. 使用merchant_secret,对data进行SHA256运算。
    HMAC-SHA256(data, merchant_secret)

响应签名

如果请求被正常处理,服务端会在响应头求头中添加字段:

请求头说明
timestamp响应秒级时间戳
sign签名

调用方可用于验证响应的可靠性。 注意:异常情况下,服务端可能无法完成签名,因此调用方应该先判断timestamp,sign响应头有值才进行签名验证。

sign生成方式为

  1. 使用.拼接 响应头中的timestamp、响应体,即: data = "{timestamp}.{rawBody}"
    如 timestamp为 12345698
    rawBody为 aaa 则data = "12345698.aaa"

  2. 使用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);
}