设置API身份验证
API 身份验证
要调用 FuturePay API,您需要一个 API 密钥。这个密钥是您帐户的专属信息,必须保密,绝不能公开或在客户端使用。
您可以在管理平台的 开发者信息 页面中找到并获取您的 API 密钥。
为了确保安全,您还需要配置 IP 白名单。您可以在管理平台的开发者菜单中设置允许访问 API 的 IP 地址列表,只有在这些 IP 地址范围内的请求才能访问您的 API。
每次调用 API 时,您必须在请求中包含您的 API 密钥,并将其作为 Bearer 令牌 放在 HTTP 请求头的 Authorization 字段中。
此外,还需要在 HTTP 请求头中包含以下信息:
MerchantId(商户 ID)
appId(应用 ID)
curTime(当前时间)
这些信息都是 API 调用时必需的,确保您的请求能够正确处理。
沙盒与实时 API
要开始处理通过真实货币流动进行的付款,必须激活您的帐户,并且您需要使用 api.futurepay.global 主机而不是带有实时凭证的沙盒。
实时 API: https://api.futurepay.global
提供无效的凭证将导致 401 未授权的响应状态代码。
{
"message": "Unauthorized"
}
步骤 1:准备待签名参数
假设我们有以下待签名的参数(JSON 格式):
{
"reference": "9B6F974D3DB8436AA2B139551933FF08",
"amount": {
"currency": "USD",
"value": 100
},
"countryCode": "CN",
"origin": "fffmall.com",
"paymentMethod": {
"holderName": "John Doe",
"shopperEmail": "[email protected]",
"type": "alipaycn"
},
"lineItems": [
{
"description": "订单描述"
}
],
"returnUrl": "https://wallet.futurepay-develop.com/api/PayNotify/paymentSynchronous/business_merchant_id/1/order_id/2115",
"shopperReference": "FP3d9bcb7a0cf84b80bfc814e1cc43c613"
}
步骤 2:移除不需要签名的参数
移除 lineItems 参数不需要参与签名计算。移除后,待签名的参数变为:
{
"reference": "9B6F974D3DB8436AA2B139551933FF08",
"amount": {
"currency": "USD",
"value": 100
},
"countryCode": "CN",
"origin": "fffmall.com",
"paymentMethod": {
"holderName": "John Doe",
"shopperEmail": "[email protected]",
"type": "alipaycn"
},
"returnUrl": "https://wallet.futurepay-develop.com/api/PayNotify/paymentSynchronous/business_merchant_id/1/order_id/2115",
"shopperReference": "FP3d9bcb7a0cf84b80bfc814e1cc43c613"
}
步骤 3:排序参数
接下来,参数会按字典顺序进行排序。排序规则是按照 ASCII 字符的值进行比较。参数排序后的顺序如下:
amount={"currency":"USD","value":100}
countryCode=CN
origin=fffmall.com
paymentMethod={"holderName":"John Doe","shopperEmail":"[email protected]","type":"alipaycn"}
reference=9B6F974D3DB8436AA2B139551933FF08
returnUrl=https://wallet.futurepay-develop.com/api/PayNotify/paymentSynchronous/business_merchant_id/1/order_id/2115
shopperReference=FP3d9bcb7a0cf84b80bfc814e1cc43c613
步骤 4:格式化参数
然后,将每个参数以 key=value 的形式连接起来,并使用 & 符号连接所有的参数。这里的格式化结果如下:
amount={"currency":"USD","value":100}&countryCode=CN&origin=fffmall.com&paymentMethod={"holderName":"John Doe","shopperEmail":"[email protected]","type":"alipaycn"}&reference=9B6F974D3DB8436AA2B139551933FF08&returnUrl=https://wallet.futurepay-develop.com/api/PayNotify/paymentSynchronous/business_merchant_id/1/order_id/2115&shopperReference=FP3d9bcb7a0cf84b80bfc814e1cc43c613
步骤 5:附加认证密钥
接下来,将API 密钥
附加到格式化后的字符串的末尾。假设认证密钥为 "secret123
",那么待签名的字符串变为:
amount={"currency":"USD","value":100}&countryCode=CN&origin=fffmall.com&paymentMethod={"holderName":"John Doe","shopperEmail":"[email protected]","type":"alipaycn"}&reference=9B6F974D3DB8436AA2B139551933FF08&returnUrl=https://wallet.futurepay-develop.com/api/PayNotify/paymentSynchronous/business_merchant_id/1/order_id/2115&shopperReference=FP3d9bcb7a0cf84b80bfc814e1cc43c613secret123
步骤 6:计算 SHA-256 哈希值
最后,待签名的字符串通过 SHA-256 哈希算法生成签名。生成的签名为:
f6e21134b5fbb06d6b668115d9370d0efa9b7c4dff2b56cb5c0670dbafa1510d
步骤 7:将签名结果放到 HTTP 请求头的 Authorization
POST /v1/payment-charges HTTP/1.1
Host: api.futurepay-develop.com
Content-Type: application/json
Authorization: f6e21134b5fbb06d6b668115d9370d0efa9b7c4dff2b56cb5c0670dbafa1510d
appId: 1801233382194151424
merchantId:1760141409517584384
curTime:2024-01-01 14:24:24
{..}
签名方法
package com.futurebank.pojo.utils;
import com.fasterxml.jackson.databind.ObjectMapper;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
import java.util.*;
/**
* @author ben
* @date 2024/3/11 12:15
**/
public class SingUtils {
public static String getSign(Map<String, Object> params, String authKey) throws Exception {
Map<String, Object> sortMap = new HashMap<>();
sortMap.putAll(params);
sortMap.remove("lineItems");
// 排序参数并附加认证密钥
String param = sortAndFormatParams(sortMap) + authKey;
// 返回SHA-256哈希值
return sha256(param);
}
public static String sha256(String str) {
String encodeStr = "";
try {
MessageDigest digest = MessageDigest.getInstance("SHA-256");
byte[] encodedhash = digest.digest(str.getBytes(StandardCharsets.UTF_8));
encodeStr = bytesToHex(encodedhash);
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("algorithm not supported");
}
return encodeStr;
}
public static String sortAndFormatParams(Map<String, Object> params) throws Exception {
// 创建排序的 TreeMap
Map<String, Object> sortedMap = new TreeMap<>(params);
StringBuilder sb = new StringBuilder();
ObjectMapper mapper = new ObjectMapper();
// 遍历排序后的Map
for (Map.Entry<String, Object> entry : sortedMap.entrySet()) {
String key = entry.getKey();
Object value = entry.getValue();
if (value == null) {
continue;
}
String valueStr;
if (value instanceof Map) {
// 对嵌套的Map进行递归排序和格式化
valueStr = mapper.writeValueAsString(sortAndFormatNestedMap((Map<String, Object>) value));
} else if (value instanceof List) {
// 对List进行排序和格式化
valueStr = mapper.writeValueAsString(sortAndFormatList((List<Object>) value));
} else {
valueStr = objectToString(value);
}
// 将键值对添加到StringBuilder中
sb.append(key).append("=").append(valueStr).append("&");
}
// 移除最后一个 '&' 字符
if (sb.length() > 0) {
sb.setLength(sb.length() - 1);
}
return sb.toString();
}
// 排序嵌套的Map对象的方法
private static Map<String, Object> sortAndFormatNestedMap(Map<String, Object> nestedMap) {
Map<String, Object> sortedNestedMap = new TreeMap<>();
for (Map.Entry<String, Object> entry : nestedMap.entrySet()) {
String key = entry.getKey();
Object value = entry.getValue();
if (value instanceof Map) {
// 对嵌套的Map进行递归排序
sortedNestedMap.put(key, sortAndFormatNestedMap((Map<String, Object>) value));
} else if (value instanceof List) {
// 对List进行递归排序
sortedNestedMap.put(key, sortAndFormatList((List<Object>) value));
} else {
sortedNestedMap.put(key, value);
}
}
return sortedNestedMap;
}
private static List<Object> sortAndFormatList(List<Object> list) {
List<Object> sortedList = new ArrayList<>();
for (Object item : list) {
if (item instanceof Map) {
sortedList.add(sortAndFormatNestedMap((Map<String, Object>) item));
} else if (item instanceof List) {
sortedList.add(sortAndFormatList((List<Object>) item));
} else {
sortedList.add(item);
}
}
return sortedList;
}
// 将对象转换为字符串的方法
private static String objectToString(Object obj) {
if (obj instanceof BigDecimal) {
return ((BigDecimal) obj).toPlainString();
} else if (obj instanceof Double) {
return obj.toString();
} else if (obj instanceof Integer) {
return obj.toString();
} else if (obj instanceof String) {
return (String) obj;
} else {
return obj.toString();
}
}
// 将字节数组转换为十六进制字符串
private static String bytesToHex(byte[] hash) {
StringBuilder hexString = new StringBuilder(2 * hash.length);
for (byte b : hash) {
String hex = Integer.toHexString(0xff & b);
if (hex.length() == 1) {
hexString.append('0');
}
hexString.append(hex);
}
return hexString.toString();
}
}
<?php
class SignUtils {
public static function getSign(array $params, string $authKey): string {
// 移除 lineItems
if (isset($params['lineItems'])) {
unset($params['lineItems']);
}
// 排序参数并附加认证密钥
$param = self::sortAndFormatParams($params) . $authKey;
// 返回SHA-256哈希值
return self::sha256($param);
}
public static function sha256(string $str): string {
return hash('sha256', $str);
}
public static function sortAndFormatParams(array $params): string {
// 使用 ksort 按键排序
ksort($params);
$result = [];
foreach ($params as $key => $value) {
if ($value === null) {
continue;
}
if (is_array($value)) {
if (self::isAssocArray($value)) {
// 递归处理关联数组
$valueStr = json_encode(self::sortAndFormatNestedMap($value));
} else {
// 处理列表
$valueStr = json_encode(self::sortAndFormatList($value));
}
} else {
$valueStr = self::objectToString($value);
}
$result[] = "$key=$valueStr";
}
// 使用 & 连接键值对
return implode('&', $result);
}
private static function sortAndFormatNestedMap(array $nestedMap): array {
$sortedNestedMap = [];
ksort($nestedMap);
foreach ($nestedMap as $key => $value) {
if (is_array($value)) {
if (self::isAssocArray($value)) {
$sortedNestedMap[$key] = self::sortAndFormatNestedMap($value);
} else {
$sortedNestedMap[$key] = self::sortAndFormatList($value);
}
} else {
$sortedNestedMap[$key] = $value;
}
}
return $sortedNestedMap;
}
private static function sortAndFormatList(array $list): array {
$sortedList = [];
foreach ($list as $item) {
if (is_array($item)) {
if (self::isAssocArray($item)) {
$sortedList[] = self::sortAndFormatNestedMap($item);
} else {
$sortedList[] = self::sortAndFormatList($item);
}
} else {
$sortedList[] = $item;
}
}
return $sortedList;
}
private static function objectToString($obj): string {
if (is_float($obj)) {
return number_format($obj, 8, '.', ''); // 避免科学计数法
} elseif (is_int($obj) || is_string($obj)) {
return (string)$obj;
} else {
return json_encode($obj);
}
}
private static function isAssocArray(array $arr): bool {
// 判断是否是关联数组
return array_keys($arr) !== range(0, count($arr) - 1);
}
}