可以使用 UIWebView 处理 IAP 以外的支付吗?
结论
非 IAP 支付可以通过网页完成,但能否使用第三方支付,取决于用户购买的内容,与是否使用 UIWebView 无关:
- 实体商品或线下服务,如外卖、酒店、交通、门票和实物电商,通常可以使用第三方支付网关。
- 如果用户购买的是在 App 内使用的数字内容、会员权益或虚拟功能,通常必须使用 Apple 的 In-App Purchase。将结账页面放入 WebView 无法绕过这项要求。
- 部分 App 类型可能适用单独规则或特定 entitlement,具体应以提交审核时有效的 App Store Review Guidelines 为准。
新项目不应继续使用 UIWebView。它已经废弃,应迁移到 WKWebView。对于涉及敏感信息的支付流程,也可以考虑使用系统浏览器或 SFSafariViewController。
推荐实现方式
建议采用以下流程:
- App 请求自己的服务器创建订单。
- 服务器生成支付平台的托管结账地址。
- App 通过
WKWebView、SFSafariViewController或系统浏览器打开结账页面。 - 用户完成支付后,支付平台通过服务端 webhook 通知服务器。
- App 向服务器查询并确认订单状态,不直接采信网页回跳参数。
不要在 App 中保存支付网关的私钥,也不能仅根据 success=true 之类的 URL 参数判断支付成功。
使用 WKWebView 打开结账页面
下面的示例会加载服务器返回的托管支付地址,并拦截支付完成后的回跳 URL:
import UIKit
import WebKit
final class CheckoutViewController: UIViewController, WKNavigationDelegate {
private let checkoutURL: URL
private lazy var webView: WKWebView = {
let configuration = WKWebViewConfiguration()
let view = WKWebView(frame: .zero, configuration: configuration)
view.navigationDelegate = self
return view
}()
init(checkoutURL: URL) {
self.checkoutURL = checkoutURL
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func loadView() {
view = webView
}
override func viewDidLoad() {
super.viewDidLoad()
var request = URLRequest(url: checkoutURL)
request.cachePolicy = .reloadIgnoringLocalCacheData
webView.load(request)
}
func webView(
_ webView: WKWebView,
decidePolicyFor navigationAction: WKNavigationAction,
decisionHandler: @escaping (WKNavigationActionPolicy) -> Void
) {
guard let url = navigationAction.request.url else {
decisionHandler(.cancel)
return
}
if url.scheme == "myapp", url.host == "payment-complete" {
decisionHandler(.cancel)
// 回跳只表示网页流程结束。
// 这里仍应向自己的服务器查询订单状态。
verifyOrderWithServer()
return
}
decisionHandler(.allow)
}
private func verifyOrderWithServer() {
// 调用自己的订单查询接口。
// 只有服务器确认支付成功后,才更新 App 内的订单状态。
}
}
服务端创建支付会话时,可以将回跳地址设置为:
myapp://payment-complete?order_id=12345
order_id 只能用来定位订单,不能证明支付已经成功。最终状态必须由服务器根据支付平台签名后的 webhook 或订单查询接口确认。
使用系统浏览器完成支付
如果支付流程包含银行跳转、3-D Secure 验证或其他跨站操作,系统浏览器通常具有更好的兼容性:
import SafariServices
func presentCheckout(from viewController: UIViewController, url: URL) {
let safariViewController = SFSafariViewController(url: url)
viewController.present(safariViewController, animated: true)
}
如需从外部页面返回 App,可以使用 Universal Links 或自定义 URL Scheme。建议优先使用 Universal Links,因为它具有更完整的域名归属验证机制。
注意事项
App Store 审核看的是购买内容
即使第三方支付页面完全在网页中运行,只要用户购买的是 App 内数字功能或数字内容,Apple 仍可能要求使用 IAP。商品属性不会因技术实现方式而改变。
不同国家或地区、App 类型以及 Apple 当前政策可能存在例外。提交审核前,应检查最新的 App Store Review Guidelines,特别是支付和外部购买链接的相关条款。
不要收集或保存银行卡信息
优先使用支付服务商提供的托管结账页面,让银行卡数据直接提交给支付服务商。这样可以减少进入 App 或服务器的敏感数据,但不会自动免除 PCI DSS 等合规责任。具体责任范围应向支付服务商或合规人员确认。
必须由服务端验证支付结果
安全的支付状态链路如下:
App 创建订单
→ 服务器创建支付会话
→ 用户完成支付
→ 支付平台发送 webhook
→ 服务器验证签名并更新订单
→ App 查询服务器获取最终结果
网页回跳可能被伪造、重复触发,也可能发生在支付最终确认之前,因此不能替代 webhook 和服务端验证。
提前测试跳转兼容性
应重点测试以下情况:
- 3-D Secure 或银行验证页面能否正常打开;
- Cookie、跨站跳转和弹窗是否受到限制;
- 用户取消支付后如何处理状态;
- App 切换到后台或终止后能否恢复订单;
- webhook 重复发送时能否进行幂等处理;
- 网络中断后能否重新查询订单。
在非 IAP 场景中,可以继续使用现有的网站支付网关,但应迁移到 WKWebView 或系统浏览器,并根据商品类型判断第三方支付是否符合 App Store 规则。
备注:内容仅供参考。
