如何在 iOS 应用中集成 PayWay API?
结论
你的理解基本正确:iOS 应用不应携带 PayWay 商户凭据直接调用支付网关。交易应由后端服务器创建和确认,服务器保存权威的交易状态,再将必要的结果返回应用。
信用卡信息最好通过 PayWay 提供的托管支付页面、托管表单或令牌化方案收集,避免卡号和安全码经过你的应用和服务器。如果所选的 PayWay API 要求服务器接收完整卡信息,系统会进入更严格的 PCI DSS 合规范围,不能只做一层普通的 API 转发。
用户通常不需要提供所谓的支付 “Password”。支付表单一般只收集持卡人姓名、卡号、有效期和 CVV/CVC。商户密码、API key、证书等凭据必须保存在服务器端。
还要先确认支付场景。如果用户购买的是应用内数字内容、功能或订阅,通常应使用 Apple 的 In-App Purchase,不能直接使用 PayWay。PayWay 更适合实体商品、线下消费或在应用外履约的服务。具体适用范围应以提交应用时有效的 App Review Guidelines 和目标地区规则为准。
推荐的系统结构
建议采用下面的流程:
iOS App
│
│ 1. 请求创建订单,不包含商户密钥
▼
Your Server
│
│ 2. 校验商品、价格和用户,创建待支付订单
│ 3. 调用 PayWay 创建支付会话或获得 token
▼
PayWay
▲
│ 4. PayWay 托管页面/表单收集卡信息
│ 或将卡信息直接转换成一次性 token
▼
iOS App
│
│ 5. 将 payment token / session ID 交给服务器
▼
Your Server
│
│ 6. 使用服务器端商户凭据确认或执行交易
▼
PayWay
│
│ 7. 返回交易标识和状态
▼
Your Server
│
│ 8. 验证响应、更新订单、处理回调
▼
iOS App
9. 查询订单结果并显示给用户
如果 PayWay 当前提供移动端托管页面、iframe、SDK 或 client-side tokenization,应优先使用官方支持的方案。PHP、Java 或 .NET 示例通常用于服务器端,并不表示需要把这些代码移植到 iOS。
为什么不能让 iOS 直接调用 PayWay API
移动应用无法可靠地保护秘密。即使将 API key、商户密码或证书写入二进制文件并加以混淆,攻击者仍有可能通过反编译、代理或运行时分析提取这些信息。
让应用直接调用支付 API 还会产生以下问题:
- 攻击者可以篡改商品编号、金额或币种。
- 商户凭据泄露后,可能被用于伪造交易请求。
- 应用难以可靠处理重试、超时和重复扣款。
- 订单状态分散在客户端,不能作为结算和发货依据。
- 如果应用或服务器接触完整卡号,PCI DSS 合规范围会明显扩大。
订单金额必须由服务器根据商品信息计算,不能直接采用客户端提交的金额。
信用卡信息应该在哪里收集
可以按照以下顺序选择收集方式。
1. PayWay 托管支付页面
应用先向服务器申请支付会话,再通过 ASWebAuthenticationSession、系统浏览器或 PayWay 明确支持的安全 Web 流程打开托管页面。用户直接在 PayWay 页面输入卡信息,支付完成后通过回调 URL 返回应用。
这种方式通常更容易控制敏感信息的暴露范围。不要为了视觉效果,使用支付服务商不支持的自制 WKWebView 注入脚本,也不要读取托管表单中的内容。
2. PayWay 托管字段或客户端令牌化
如果 PayWay 提供托管输入组件或移动端 tokenization,卡信息可以直接提交给 PayWay,应用只会取得一次性 token。服务器随后使用该 token 发起扣款。
这个 token 只是卡信息的替代标识,并不表示支付已经成功。
3. 自建信用卡表单并提交到自己的服务器
只有当 PayWay 的产品能力、商户协议和 PCI DSS 合规方案明确允许时,才应使用这种方式。此时需要满足以下要求:
- 卡号、有效期和 CVV 只能通过 TLS 传输。
- 授权完成后不得保存 CVV。
- 卡信息不得写入日志、崩溃报告、埋点或分析平台。
- 卡信息不得出现在 URL、查询字符串、通知内容或截图中。
- 是否可以在数据库中保存卡号,以及如何加密和限制访问,都需要按照 PCI DSS 设计。
- 开启 HTTPS 并不表示系统已经符合合规要求。
不要收集用户的银行网银密码、PayWay 商户密码或其他账户登录密码。
后端处理步骤
创建订单
应用只提交服务器能够验证的信息:
{
"productId": "product_123",
"quantity": 1
}
服务器查询价格并创建内部订单:
{
"orderId": "order_8f3a...",
"amount": 1999,
"currency": "AUD",
"status": "pending"
}
金额最好按最小货币单位保存。例如,1999 表示 AUD 19.99,这样可以避免浮点数计算误差。
创建支付会话或执行交易
后端应使用 PayWay 官方服务器端库或 HTTPS API。不同 PayWay 产品和账户配置可能采用不同的接口、认证方式和字段,下面的代码只用于说明整体结构,不代表真实的 PayWay endpoint 或参数名称:
app.post("/api/orders/:orderId/pay", async (req, res) => {
const order = await loadOrder(req.params.orderId, req.user.id);
if (!order || order.status !== "pending") {
return res.status(409).json({ error: "ORDER_NOT_PAYABLE" });
}
// token 应由 PayWay 托管页面或令牌化组件生成。
const paymentToken = req.body.paymentToken;
const result = await payway.charge({
merchantReference: order.id,
amount: order.amount,
currency: order.currency,
paymentToken,
idempotencyKey: order.paymentAttemptId
});
await saveGatewayResult(order.id, {
gatewayTransactionId: result.transactionId,
status: mapGatewayStatus(result.status)
});
res.json({
orderId: order.id,
status: mapGatewayStatus(result.status)
});
});
示例中的 payway.charge、paymentToken 和 idempotencyKey 都是说明性名称。实际开发时,必须改用商户账户所对应 PayWay 产品的官方接口。
确认最终结果
客户端不能根据跳转 URL 中的 success=true、页面文字或本地状态决定是否发货。服务器应通过 PayWay 的服务端响应确认交易结果。
如果 PayWay 支持异步通知或 webhook,还需要:
- 验证通知的签名、证书或其他认证信息。
- 使用 PayWay 交易编号关联内部订单号。
- 必要时调用服务端查询接口,再次核实交易状态。
- 以幂等方式更新订单,避免重复通知导致重复发货。
- 保存必要的审计信息,同时过滤卡号、CVV 和凭据。
客户端超时不代表付款失败。遇到这种情况,应显示 “正在确认”,并向自己的服务器查询订单状态,而不是立即再次发起扣款。
iOS 端示例
iOS 端只负责创建订单、提交 token 和读取订单状态,不保存 PayWay 商户密码。
struct CreateOrderRequest: Encodable {
let productId: String
let quantity: Int
}
struct OrderResponse: Decodable {
let orderId: String
let amount: Int
let currency: String
let status: String
}
func createOrder(productId: String) async throws -> OrderResponse {
let url = URL(string: "https://api.example.com/api/orders")!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpBody = try JSONEncoder().encode(
CreateOrderRequest(productId: productId, quantity: 1)
)
let (data, response) = try await URLSession.shared.data(for: request)
guard let http = response as? HTTPURLResponse,
(200..<300).contains(http.statusCode) else {
throw PaymentError.serverRejected
}
return try JSONDecoder().decode(OrderResponse.self, from: data)
}
取得 PayWay 生成的一次性 token 后,再将它提交给自己的服务器:
struct PayOrderRequest: Encodable {
let paymentToken: String
}
func payOrder(orderId: String, paymentToken: String) async throws -> OrderResponse {
let url = URL(
string: "https://api.example.com/api/orders/\(orderId)/pay"
)!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpBody = try JSONEncoder().encode(
PayOrderRequest(paymentToken: paymentToken)
)
let (data, response) = try await URLSession.shared.data(for: request)
guard let http = response as? HTTPURLResponse,
(200..<300).contains(http.statusCode) else {
throw PaymentError.serverRejected
}
return try JSONDecoder().decode(OrderResponse.self, from: data)
}
enum PaymentError: Error {
case serverRejected
}
如果用户通过托管网页完成支付,应用收到回调后仍要调用自己的查询接口:
func fetchOrderStatus(orderId: String) async throws -> OrderResponse {
let url = URL(
string: "https://api.example.com/api/orders/\(orderId)"
)!
let (data, response) = try await URLSession.shared.data(from: url)
guard let http = response as? HTTPURLResponse,
(200..<300).contains(http.statusCode) else {
throw PaymentError.serverRejected
}
return try JSONDecoder().decode(OrderResponse.self, from: data)
}
交易状态不要只设计成成功或失败
网关请求可能遇到网络中断、超时或异步处理,因此至少要区分以下状态:
pending
processing
authorized
paid
declined
cancelled
failed
unknown
refunded
unknown 和 processing 尤其重要。服务器已经提交请求,但还没有得到明确结果时,不能马上重试扣款。应先使用商户参考号或网关交易编号查询原交易。
应用向用户显示错误信息前,也应先做转换。不要直接返回 PayWay 的完整原始响应,其中可能包含内部字段或敏感信息。
安全与上线注意事项
- PayWay API 凭据只能保存在服务器的密钥管理系统或受保护的环境配置中。
- iOS 与服务器之间、服务器与 PayWay 之间都要使用有效的 TLS 连接。
- 服务端需要对用户、订单和付款操作进行身份认证与授权。
- 服务器应重新计算金额、税费、优惠和币种。
- 每次支付尝试都应使用唯一的订单引用,并实施幂等控制。
- 日志中的卡号、token、Authorization header 和网关响应需要脱敏。
- 不得保存 CVV,也不能将其写入 Keychain、
UserDefaults或数据库。 - token 仍属于敏感数据,应限制访问,不能公开记录。
- 测试环境和生产环境必须使用不同的凭据、地址与数据。
- 只有服务器确认付款后,才能发货、增加余额或解锁服务。
- 退款、撤销、拒付和对账也应由后端围绕同一个订单模型处理。
- 上线前需要确认 PayWay 商户协议、PCI DSS 责任范围和 Apple 支付政策。
正确的调用方向是 “应用 → 自有服务器 → PayWay”,同时还要尽量让信用卡信息直接进入 PayWay 的托管或令牌化系统。自有服务器负责订单、认证、金额校验和交易状态,iOS 应用只负责安全地启动支付,并展示服务器确认后的结果。
备注:内容仅供参考。
