跳到主要内容

JWTs

本 API 使用 Bearer(令牌)认证 来验证任何请求。这些令牌是 JSON Web Tokens (JWT),需要由您的应用程序在服务器端创建。目前创建 JWT 最简单的方法是使用我们的服务端 SDK,但您也可以在不使用我们 SDK 的情况下,使用任何开源 JWT 库来生成 JWT。

本文档定义了我们 API 的认证规范、JWT 声明和签名机制。

验证 API 调用

每个需要认证的 API 端点都需要在 HTTP 头中包含 Authorization 字段,其值为签名的 JWT 令牌(以 Bearer 为前缀)。支付 API 还需要同时携带 x-merchant-account-id Header。

curl -i -X GET "https://api.efundpay.com/v4/transactions?limit=20" \
-H "Authorization: Bearer <jwt>" \
-H "x-merchant-account-id: 202507131000347001"

创建 API Secret

要签署 JWT,您需要创建 API Secret。在 EFundPay Dashboard 中,API Secret 就是 开发工具 > API 凭证 页面生成的商户 RSA 私钥

要创建或轮换 API Secret,请前往 Dashboard 的 开发工具 > API 凭证 页面,点击 生成 RSA 凭证。请将 API Secret 与服务端代码一同存放,或存储在应用可访问的安全环境中。

商户访问权限

x-merchant-account-id Header 用于标识本次请求对应的商户号。如果您的 Dashboard 账号可以访问多个商户,请使用当前对接商户的商户号。

权限

通过在 JWT 上设置限制性 Scope,可以进一步减少 API 访问权限。

算法

支付 API 使用 RSA 签名的 JWT。请使用商户 RSA 私钥签名 JWT,并将 alg 设置为 RS512

生成 JWT

您可能不想使用我们的 SDK,或者您的语言可能没有可用的 SDK。在这些情况下,您可以使用 jwt.io 上提供的众多库之一来构建和签署 JWT。

在高层次上,JWT 由 3 个部分组成:

  • 定义用于创建 JWT 的算法和密钥的 header(头部)。
  • 定义令牌范围和其他权限的一组 claims(声明)。
  • 基于头部和声明的加密 signature(签名),使用您的私钥签署。

结合这 3 个部分构成 JSON Web Token (JWT)。有关规范和生成 JWT 的可用库的更多详细信息,请参见 jwt.io。

JWT 头部

JWT 头部定义签名算法和站点商户号。

{
"typ": "JWT",
"alg": "RS512",
"kid": "sk_live_{站点商户号}"
}

typalg 是固定的。kid 用于标识站点商户号;生产环境格式为 sk_live_{站点商户号}。站点商户号可在 Dashboard 的 站点管理 > 站点列表 中查看。

JWT 声明

声明定义了令牌的创建时间和访问权限。

{
"iss": "My JWT Generation Tool",
"nbf": 1607976645,
"exp": 1607977245,
"jti": "0fe1fb1b-2f7e-4c8d-b0eb-aae5d0ec98f7",
"scopes": ["transactions.read"],
"merchantId": "202507131000347001",
"embed": {
"amount": "200",
"currency": "AUD",
"buyer_id": "d757c76a-cbd7-4b56-95a3-40125b51b29c",
"metadata": { "key": "value" },
"cart_items": [
{
"name": "Joust Duffle Bag",
"quantity": "1",
"unit_amount": "9000",
"tax_amount": "0"
}
]
}
}

声明字段

API 支持以下 JWT 声明。

字段名描述是否必需
iss代表您的代码进行此调用的唯一 ID。这有助于识别哪个库向 EFundFlow 发出了 API 调用。
nbf此令牌创建时的 UNIX 时间戳(以秒为单位)。
exp此令牌过期时的 UNIX 时间戳(以秒为单位)。
iat可选的 UNIX 时间戳(以秒为单位),供您内部使用以指示令牌的签发时间。
jti用于加密熵的随机唯一 ID。每个 JWT 都需要唯一。
scopes授予此令牌访问 API 权限的范围列表。
embed用于在 Embed 中固定金额、货币和买家信息的键值对字典。
checkout_session_id结账会话的 ID。这可以用来将多个交易绑定在一起,表示它们来自同一个会话。
merchantId商户唯一标识。如填写,必须和 x-merchant-account-id Header 一致。

时间戳

请注意,nbfexpiat 值是 UNIX 时间戳,定义为自 1970 年 1 月 1 日(UTC)以来的秒数。某些编程语言返回的 UNIX 时间戳是毫秒,需要删除最后 3 位数字。

范围

API 支持 scopes 声明的以下值。

范围描述
*.read允许对任何资源进行读取访问。这是 SDK 中的默认设置
*.write允许对任何资源进行写入访问。这是 SDK 中的默认设置。这不包括读取访问权限。
{resource_name}.read允许对某类型或资源进行读取访问。例如,payment-services.read 启用对买家数据的读取访问。
{resource_name}.write允许对某类型或资源进行写入访问。例如,payment-services.write 启用对买家数据的写入访问。这不包括读取访问权限。
embed代表 Embed 所需的所有访问权限的范围。

识别以下资源名称。有关每个端点需要什么范围的更多详细信息,请参阅参考文档。

  • anti-fraud-services
  • api-logs
  • buyers
  • buyers.billing-details
  • card-scheme-definitions
  • checkout-sessions
  • connections
  • digital-wallets
  • flows
  • payment-methods
  • payment-method-definitions
  • payment-options
  • payment-service-definitions
  • payment-services
  • reports
  • transactions

签名和组装

最后,JWT 签名是通过连接 Base64 编码的头部和声明(用 . 分隔)并使用商户 RSA 私钥签名生成的。

RSA

SHA512withRSA(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
private_key
)

组装后的 JWT 通过连接 Base64 编码的头部、声明和签名(用句号分隔)形成。

base64UrlEncode(header) + "." + base64UrlEncode(payload) + "." + base64UrlEncode(signature)

Java 代码示例

import java.nio.charset.StandardCharsets;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.Signature;
import java.security.spec.PKCS8EncodedKeySpec;
import java.time.Instant;
import java.util.Base64;
import java.util.UUID;

public class JwtExample {
static String createJwt(String privateKeyBase64, String merchantId, String siteMerchantId) throws Exception {
long now = Instant.now().getEpochSecond();
String header = "{\"typ\":\"JWT\",\"alg\":\"RS512\",\"kid\":\"sk_live_" + siteMerchantId + "\"}";
String payload = "{"
+ "\"iss\":\"efundflow.com\","
+ "\"nbf\":" + (now - 60) + ","
+ "\"exp\":" + (now + 3600) + ","
+ "\"jti\":\"" + UUID.randomUUID() + "\","
+ "\"scopes\":[\"transactions.read\"],"
+ "\"merchantId\":\"" + merchantId + "\""
+ "}";

String encodedHeader = base64UrlEncode(header.getBytes(StandardCharsets.UTF_8));
String encodedPayload = base64UrlEncode(payload.getBytes(StandardCharsets.UTF_8));
String signingInput = encodedHeader + "." + encodedPayload;

Signature signature = Signature.getInstance("SHA512withRSA");
signature.initSign(loadRsaPrivateKey(privateKeyBase64));
signature.update(signingInput.getBytes(StandardCharsets.UTF_8));

return signingInput + "." + base64UrlEncode(signature.sign());
}

static PrivateKey loadRsaPrivateKey(String base64Key) throws Exception {
byte[] keyBytes = Base64.getDecoder().decode(base64Key);
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
return KeyFactory.getInstance("RSA").generatePrivate(keySpec);
}

static String base64UrlEncode(byte[] bytes) {
return Base64.getUrlEncoder().withoutPadding().encodeToString(bytes);
}
}
Powered by Docusaurus