PAYATHON 2026

如何在 iOS(Objective-C)中集成 PayFort SDK?

支付小周

结论

PayFort(现 Amazon Payment Services)的 iOS SDK 用于展示支付界面和采集支付信息。SDK Token、请求签名和订单金额必须由业务服务器生成。不要将 SHA Request Phrase、SHA Response Phrase 等密钥写入 Objective-C 应用。

支付流程如下:

  1. iOS SDK 获取 device_id。
  2. App 将 device_id 和订单标识发送给业务服务器。
  3. 服务器创建订单,并向 PayFort 请求 SDK_TOKEN。
  4. 服务器将 sdk_token 和订单支付参数返回 App。
  5. App 调用 PayFort SDK 发起 PURCHASE 或 AUTHORIZATION。
  6. 服务器通过回调和查询接口确认最终支付状态。
  7. 服务器生成账单、收据或业务订单记录。

SDK 的类名和方法签名可能随版本变化,请以当前 SDK 包中的头文件和官方文档为准。

一、准备商户配置

先从 PayFort/Amazon Payment Services 商户后台取得以下配置:

  • merchant_identifier
  • access_code
  • SHA Request Phrase
  • SHA Response Phrase
  • SHA 算法配置,例如 SHA-256
  • 测试环境和生产环境各自的接口地址

SHA Request Phrase 和 SHA Response Phrase 只能保存在服务器端。App 通常只需要配置 SDK,不应保存任何签名密钥。

测试环境与生产环境的商户配置不能混用。切换到生产环境时,要一并检查接口地址、凭据、SDK 环境和回调地址。

二、集成 iOS SDK

按照下载到的 SDK 包说明,将 framework 或 xcframework 添加到 Xcode 项目,并在对应 target 的构建设置中完成链接和嵌入。

在 Objective-C 文件中导入 SDK:

#import <PayFortSDK/PayFortSDK.h>

不同版本使用的模块名可能不同。如果上述导入方式不可用,请直接查看 SDK 中的公开头文件。

初始化控制器:

@property (nonatomic, strong) PayFortController *payFortController;
self.payFortController = [[PayFortController alloc] init];

如果当前版本要求指定测试或生产环境,请使用该版本公开的配置方法,不要根据旧版示例猜测枚举值。

三、取得 device_id

申请 SDK Token 时,需要提交 SDK 提供的设备标识。常见版本的调用方式如下:

NSString *deviceId = [self.payFortController getUDID];

if (deviceId.length == 0) {
    // 无法继续请求 SDK Token
    return;
}

取得设备标识后,将 deviceId 发送给自己的服务器:

NSURL *url = [NSURL URLWithString:@"https://api.example.com/payments/payfort/session"];

NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
request.HTTPMethod = @"POST";
[request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];

NSDictionary *body = @{
    @"order_id": self.orderId,
    @"device_id": deviceId
};

request.HTTPBody = [NSJSONSerialization dataWithJSONObject:body
                                                   options:0
                                                     error:nil];

NSURLSessionDataTask *task =
[[NSURLSession sharedSession] dataTaskWithRequest:request
                               completionHandler:^(NSData *data,
                                                   NSURLResponse *response,
                                                   NSError *error) {
    if (error != nil) {
        return;
    }

    NSDictionary *result =
        [NSJSONSerialization JSONObjectWithData:data
                                        options:0
                                          error:nil];

    NSString *sdkToken = result[@"sdk_token"];
    if (sdkToken.length == 0) {
        return;
    }

    dispatch_async(dispatch_get_main_queue(), ^{
        [self startPayFortPaymentWithSession:result];
    });
}];

[task resume];

生产项目还需要检查 HTTP 状态码、JSON 类型、登录状态和服务器返回的业务错误码。

四、服务器生成请求签名

签名必须由服务器计算。常见规则如下:

  1. 删除值为空的字段。
  2. 排除 signature 字段本身。
  3. 按参数名称升序排列。
  4. 将参数连续拼接成 key=value 字符串。
  5. 在字符串开头和结尾加上 SHA Request Phrase。
  6. 使用商户后台配置的 SHA 算法计算摘要。
  7. 通常以十六进制字符串提交。

公式可以写成:

signature = SHA(
    request_phrase
    + key1=value1
    + key2=value2
    + ...
    + request_phrase
)

例如,需要签名的参数为:

access_code=ACCESS_CODE
device_id=DEVICE_ID
language=en
merchant_identifier=MERCHANT_ID
service_command=SDK_TOKEN

按参数名称排序后,待签名字符串为:

REQUEST_PHRASEaccess_code=ACCESS_CODEdevice_id=DEVICE_IDlanguage=enmerchant_identifier=MERCHANT_IDservice_command=SDK_TOKENREQUEST_PHRASE

以下是使用 SHA-256 的 Node.js 示例:

import crypto from "node:crypto";

function createPayFortSignature(params, requestPhrase) {
  const payload = Object.keys(params)
    .filter((key) => key !== "signature")
    .filter((key) => params[key] !== null && params[key] !== undefined)
    .filter((key) => String(params[key]).length > 0)
    .sort()
    .map((key) => `${key}=${params[key]}`)
    .join("");

  return crypto
    .createHash("sha256")
    .update(`${requestPhrase}${payload}${requestPhrase}`, "utf8")
    .digest("hex");
}

生成 SDK_TOKEN 请求:

const tokenParams = {
  service_command: "SDK_TOKEN",
  access_code: process.env.PAYFORT_ACCESS_CODE,
  merchant_identifier: process.env.PAYFORT_MERCHANT_IDENTIFIER,
  language: "en",
  device_id: deviceId
};

tokenParams.signature = createPayFortSignature(
  tokenParams,
  process.env.PAYFORT_REQUEST_PHRASE
);

服务器随后按照 PayFort 当前接口要求的格式,将这些参数提交到 SDK Token 接口。接口域名和请求格式可能调整,测试地址与生产地址也不同,因此应从当前商户文档中读取地址,不要硬编码旧教程中的 URL。

PayFort 返回成功响应后,服务器提取 sdk_token 并返回给 App。不要将未经筛选的完整商户响应直接转发给客户端。

五、创建服务器订单

这里所说的“生成 bill”,通常不是让 PayFort iOS SDK 创建账单,而是先在自己的服务器上创建订单。服务器至少应保存:

order_id
merchant_reference
amount_minor
currency
status
customer_id
created_at
payfort_fort_id
payfort_response_code

merchant_reference 必须唯一,并由服务器生成。例如:

ORDER-20260913-8F2A19C4

最终金额不能由客户端决定。可以采用以下流程:

  1. App 只提交商品、套餐或订单 ID。
  2. 服务器从数据库重新计算金额。
  3. 服务器将金额转换为支付接口要求的最小货币单位。
  4. 服务器保存订单,再将支付参数返回 App。

例如,某币种采用两位小数:

100.50 → 10050

不要直接用浮点数计算金额乘法。服务器应使用十进制定点类型,或者先将字符串解析后再转换为整数。不同币种采用的小数位数可能不同,必须按照 PayFort 当前的货币规则处理,不能假设所有币种都乘以 100。

服务器返回给 App 的支付会话可以采用以下格式:

{
  "sdk_token": "TOKEN_FROM_PAYFORT",
  "merchant_reference": "ORDER-20260913-8F2A19C4",
  "amount": "10050",
  "currency": "AED",
  "customer_email": "customer@example.com",
  "command": "PURCHASE"
}

六、调用 PayFort SDK 发起支付

收到服务器返回的数据后,构造 SDK 请求:

- (void)startPayFortPaymentWithSession:(NSDictionary *)session {
    NSMutableDictionary *paymentRequest = [NSMutableDictionary dictionary];

    paymentRequest[@"command"] = session[@"command"];
    paymentRequest[@"merchant_reference"] = session[@"merchant_reference"];
    paymentRequest[@"amount"] = session[@"amount"];
    paymentRequest[@"currency"] = session[@"currency"];
    paymentRequest[@"language"] = @"en";
    paymentRequest[@"customer_email"] = session[@"customer_email"];
    paymentRequest[@"sdk_token"] = session[@"sdk_token"];

    [self.payFortController
        callPayFortWithRequest:paymentRequest
        currentViewController:self
        success:^(NSDictionary *requestDictionary,
                  NSDictionary *responseDictionary) {

            NSString *merchantReference =
                responseDictionary[@"merchant_reference"];

            [self verifyPaymentOnServer:merchantReference];
        }
        canceled:^(NSDictionary *requestDictionary,
                   NSDictionary *responseDictionary) {

            // 用户取消支付,不应标记为支付成功
        }
        faild:^(NSDictionary *requestDictionary,
                NSDictionary *responseDictionary,
                NSString *message) {

            // 展示通用错误,并把必要信息发送到服务端日志
        }];
}

部分 PayFort SDK 版本使用 callPayFortWithRequest:currentViewController:success:canceled:faild:,其中 faild 可能确实采用了这个拼写。如果当前版本无法编译,请以实际头文件公开的方法为准。

常用的支付命令有:

  • PURCHASE:授权后直接请款,适合即时支付。
  • AUTHORIZATION:先完成授权,之后再由服务器执行捕获或请款。

具体选择取决于商户账户配置和业务流程。不能只修改客户端参数,就假定商户已经取得对应命令的使用权限。

七、不能只依赖客户端成功回调

SDK 的 success 回调只能说明客户端收到了成功响应,不能直接作为发货依据。客户端响应可能被篡改,也可能发生支付已经成功,但 App 在收到回调前退出的情况。

应按以下流程确认支付结果:

  1. App 收到回调后,只显示“正在确认支付”。
  2. App 通知服务器查询对应的 merchant_reference。
  3. 服务器验证 PayFort 回调签名,或调用官方查询接口核实交易。
  4. 服务器确认金额、币种、订单号和交易状态全部匹配。
  5. 服务器使用数据库事务将订单更新为已支付。
  6. App 再从服务器取得最终订单状态。

服务器验证响应签名时,应使用 SHA Response Phrase。签名拼接规则通常相同,但响应中的 signature 字段必须排除:

function verifyPayFortResponse(params, responsePhrase) {
  const receivedSignature = String(params.signature || "").toLowerCase();

  const expectedSignature = createPayFortSignature(
    params,
    responsePhrase
  ).toLowerCase();

  if (receivedSignature.length !== expectedSignature.length) {
    return false;
  }

  return crypto.timingSafeEqual(
    Buffer.from(receivedSignature, "utf8"),
    Buffer.from(expectedSignature, "utf8")
  );
}

除了签名,还必须检查以下字段:

merchant_reference
amount
currency
response_code
status
fort_id

哪些 status 或 response_code 表示最终成功,要以当前账户、支付命令和官方响应码表为准。不要只因为返回字符串中含有“Success”,就将交易判断为成功。

八、账单或收据如何生成

PayFort SDK 返回的是支付结果,不一定符合业务或税务上的账单要求。

业务服务器通常会在确认支付后生成收据:

收据编号:RCPT-20260913-000123
商户订单号:ORDER-20260913-8F2A19C4
交易号:PayFort 返回的 fort_id
支付金额:AED 100.50
支付状态:已支付
支付时间:服务器确认时间

如果需要正式发票,还要按照经营所在地的税务规则生成。不能将 SDK 返回页面的截图直接作为正式发票。

九、常见问题

SDK Token 请求返回签名错误

可以依次检查:

  • 参数名称是否完全一致。
  • 参数是否按名称升序排列。
  • 是否误将 signature 字段加入签名。
  • 是否遗漏 device_id。
  • 是否加入了 &、空格或换行。
  • 请求和响应是否使用了错误的 Phrase。
  • 商户后台配置的算法是否为 SHA-256。
  • 是否误将测试凭据发送到生产环境。
  • 字符串编码和十六进制大小写是否符合当前接口要求。

测试环境可以记录参与签名的参数名和最终摘要,但不要记录 Phrase、银行卡信息、CVV 或完整的敏感数据。

Token 可以长期保存吗

不要假定 sdk_token 永久有效。应按照官方规定管理它的有效期。如果无法确认有效期,更安全的做法是在每次支付会话开始时,由服务器重新获取 Token。

能否在 Objective-C 中直接生成签名

从技术上说,Objective-C 可以计算 SHA 摘要,但不应在 App 中这样做。只要 Phrase 被写入 App,就可能通过反编译或运行时分析泄露。攻击者取得 Phrase 后,便可能伪造请求。

为什么支付成功却没有更新订单

常见原因有客户端中途退出、回调没有到达服务器、回调重复到达,或者订单更新没有实现幂等处理。服务器应使用 merchant_reference 或交易标识建立唯一约束,避免同一支付通知重复到达时造成重复发货。

十、安全检查清单

上线前至少确认以下事项:

  • App 中不包含 SHA Request Phrase 和 SHA Response Phrase。
  • 金额、币种和订单号由服务器决定。
  • 每个 merchant_reference 都是唯一的,且不能随意复用。
  • 服务器会验证响应签名。
  • 服务器会核对支付金额与订单金额。
  • 支付回调和订单更新支持幂等处理。
  • 日志不记录卡号、CVV、Phrase 或完整支付凭据。
  • 测试环境与生产环境配置完全隔离。
  • 只有服务器确认最终状态后,系统才发货或开通服务。
  • SDK 类名、接口参数和状态码已经与当前版本的官方文档核对。

备注:内容仅供参考。