前言
Stripe 是一家国外的提供支付服务的平台,可以让商户在自己的应用和网站方便快捷地集成信用卡支付、第三方(Apple Pay、Alipay、微信pay、三星pay、微软pay 等)支付方式等服务,具体参考官网介绍。

本文主要记录总结一下在 iOS 平台如何集成 Stripe 相关的服务。主要包含一下内容:
- 申请账号,获得测试秘钥和生产秘钥
- 信用卡支付
- Apple Pay
- 模拟测试
- 提现
- Stripe 管理后台介绍
- 注意事项
申请账号
Stripe 官网 -> 管理平台 -> 注册账号 -> 登录 -> 获得测试秘钥和生产秘钥

信用卡支付
集成 Stripe iOS SDK ,参考 github 上的项目 https://github.com/stripe/stripe-ios 和支付文档 https://stripe.com/docs/connect/creating-a-payments-page
简单支付
简单支付直接可以使用 Stripe 封装好的一个 STPPaymentCardTextField 输入框,添加支付页面的 UI 上,Stripe 处理好了信用卡格式校验,安全传输敏感数据等事情。
参考 https://github.com/stripe-samples/accept-a-card-payment,有两种模式,带 webhook 的和不带 webhook 的,区别如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
|
lazy var paymentCardTextField: STPPaymentCardTextField = {
let textField = STPPaymentCardTextField()
textField.isHidden = false
textField.delegate = self
textField.backgroundColor = STPTheme.default().secondaryBackgroundColor
textField.textColor = STPTheme.default().primaryForegroundColor
textField.placeholderColor = STPTheme.default().secondaryForegroundColor
textField.borderColor = UIColor.gxd_themeColor()
textField.borderWidth = 1.0
textField.textErrorColor = STPTheme.default().errorColor
textField.postalCodeEntryEnabled = true
return textField
}()
Stripe.setDefaultPublishableKey("\(PublishableKey)")
func startCheckout() {
let url = URL(string: BackendUrl + "create-payment-intent")!
let json: [String: Any] = [
"currency": "usd",
"items": [
["id": "photo_subscription"]
]
]
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpBody = try? JSONSerialization.data(withJSONObject: json)
let task = URLSession.shared.dataTask(with: request, completionHandler: { [weak self] (data, response, error) in
guard let response = response as? HTTPURLResponse,
response.statusCode == 200,
let data = data,
let json = try? JSONSerialization.jsonObject(with: data, options: []) as? [String : Any],
let clientSecret = json["clientSecret"] as? String,
let publishableKey = json["publishableKey"] as? String else {
let message = error?.localizedDescription ?? "Failed to decode response from server."
self?.displayAlert(title: "Error loading page", message: message)
return
}
print("Created PaymentIntent")
self?.paymentIntentClientSecret = clientSecret
Stripe.setDefaultPublishableKey(publishableKey)
})
task.resume()
}
guard let paymentIntentClientSecret = paymentIntentClientSecret else {
return;
}
let cardParams = cardTextField.cardParams
let paymentMethodParams = STPPaymentMethodParams(card: cardParams, billingDetails: nil, metadata: nil)
let paymentIntentParams = STPPaymentIntentParams(clientSecret: paymentIntentClientSecret)
paymentIntentParams.paymentMethodParams = paymentMethodParams
let paymentHandler = STPPaymentHandler.shared()
paymentHandler.confirmPayment(withParams: paymentIntentParams, authenticationContext: self) { (status, paymentIntent, error) in
switch (status) {
case .failed:
self.displayAlert(title: "Payment failed", message: error?.localizedDescription ?? "")
break
case .canceled:
self.displayAlert(title: "Payment canceled", message: error?.localizedDescription ?? "")
break
case .succeeded:
self.displayAlert(title: "Payment succeeded", message: paymentIntent?.description ?? "", restartDemo: true)
break
@unknown default:
fatalError()
break
}
}
extension CheckoutViewController: STPAuthenticationContext {
func authenticationPresentingViewController() -> UIViewController {
return self
}
}
|
绑卡支付
绑卡支付要必要每次用户都需要输入一遍卡号的问题,就是记录用户之前支付过的卡,和某个具体用户关联起来,以后该用户发起支付时候就可以选择自己以前绑定过的卡。通过 Stripe 提供的 API,用户可以对信用卡做新增、删除、修改等操作。
这些在 Stripe iOS SDK 提供的 UI 素材里面已经默认实现了。服务端借助 heroku ,可以将 https://github.com/stripe/example-mobile-backend/tree/v18.1.0 后端服务替换我们自己在 Stripe 上新建的账号秘钥,部署运行。



我们设置的 key 将会在服务端代码里面用来调用 Stripe 的 API。实例代码服务端是用 Ruby 写的,代码在这里:https://github.com/stripe/example-mobile-backend/blob/v18.1.0/web.rb, 服务端可以参考一下里面的逻辑。
客户端集成流程如下:
一、向服务端请求用户的绑卡信息
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
func createCustomerKey(withAPIVersion apiVersion: String, completion: @escaping STPJSONResponseCompletionBlock) {
let url = self.baseURL.appendingPathComponent("ephemeral_keys")
var urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: false)!
urlComponents.queryItems = [URLQueryItem(name: "api_version", value: apiVersion)]
var request = URLRequest(url: urlComponents.url!)
request.httpMethod = "POST"
let task = URLSession.shared.dataTask(with: request, completionHandler: { (data, response, error) in
guard let response = response as? HTTPURLResponse,
response.statusCode == 200,
let data = data,
let json = ((try? JSONSerialization.jsonObject(with: data, options: []) as? [String : Any]) as [String : Any]??) else {
completion(nil, error)
return
}
completion(json, nil)
})
task.resume()
}
|

通过向服务端拿到的 Stripe iOS SDK 能识别的用户信息,就能够获取该用户所关联的支付方式相关信息(包含绑定的卡和其他第三方支付方式)

二、客户端发起预支付请求
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
func createPaymentIntent(products: [Product], shippingMethod: PKShippingMethod?, country: String? = nil, completion: @escaping ((Result<String, Error>) -> Void)) {
let url = self.baseURL.appendingPathComponent("create_payment_intent")
var params: [String: Any] = [
"metadata": [
"payment_request_id": "B3E611D1-5FA1-4410-9CEC-00958A5126CB",
],
]
params["products"] = products.map({ (p) -> String in
return p.emoji
})
if let shippingMethod = shippingMethod {
params["shipping"] = shippingMethod.identifier
}
params["country"] = country
let jsonData = try? JSONSerialization.data(withJSONObject: params)
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpBody = jsonData
let task = URLSession.shared.dataTask(with: request, completionHandler: { (data, response, error) in
guard let response = response as? HTTPURLResponse,
response.statusCode == 200,
let data = data,
let json = ((try? JSONSerialization.jsonObject(with: data, options: []) as? [String : Any]) as [String : Any]??),
let secret = json?["secret"] as? String else {
completion(.failure(error ?? APIError.unknown))
return
}
completion(.success(secret))
})
task.resume()
}
|

就是客户端简单支付里面服务端下发的 paymentIntentClientSecret,客户端拿到这个,就可以发起支付请求了。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
|
MyAPIClient.sharedClient.baseURLString = self.backendBaseURL
Stripe.setDefaultPublishableKey(self.stripePublishableKey)
let config = STPPaymentConfiguration.shared()
config.appleMerchantIdentifier = self.appleMerchantID
config.additionalPaymentOptions = settings.additionalPaymentOptions
config.cardScanningEnabled = true
self.paymentCurrency = settings.currency
let customerContext = STPCustomerContext(keyProvider: MyAPIClient.sharedClient)
let paymentContext = STPPaymentContext(customerContext: customerContext,
configuration: config,
theme: settings.theme)
paymentContext.paymentCurrency = self.paymentCurrency
self.paymentContext.requestPayment()
func paymentContext(_ paymentContext: STPPaymentContext, didCreatePaymentResult paymentResult: STPPaymentResult, completion: @escaping STPPaymentStatusBlock) {
}
func paymentContext(_ paymentContext: STPPaymentContext, didFinishWith status: STPPaymentStatus, error: Error?) {
}
func paymentContextDidChange(_ paymentContext: STPPaymentContext) {
}
func paymentContext(_ paymentContext: STPPaymentContext, didFailToLoadWithError error: Error) {
}
|
Apple Pay
Apple Pay 只是支付方式的一种,Stripe 和绑卡支付封装在一起,只需要做一些相关的配置。参考 https://stripe.com/docs/apple-pay,相关需要配置的地方如下:
一、Stripe 后台下载 cert 文件

下载完:

二、苹果开发者网站,新建 Merchant IDs 类型 Identifiers
上传第一步中从 Stripe 后台获取到的 certSingingRequest 文件


下载下来:

三、上传上一步中下载好的证书

四、Xcode -> workspace -> project -> target -> Singing & Capabilities 中新增 Apple Pay

五、Apple develop 后台,将需要支持 Apple pay 的应用 ID,编辑勾选 Apple Pay 能力,将第二步中新增的 Merchant ID 编辑进去

注意事项:
- 测试模式下(即 Stripe 后台使用的是测试模式可发布的秘钥),需在沙盒环境下模拟,添加沙盒测试员;
- 如果配置的支付环境证书是在国外,那么测试的时候需要将测试手机的手机 Region 设置成该国家,不然会一直 processing 转圈支付失败;
提现
提现是商户平台的客户向该商户发起提现申请,商户通过 Stripe 提供的 API 从商户账户里面的钱转账到商户平台的客户,但是客户也需要有一个 Stripe 的账号。流程大概如下:
一、商户去到 Stripe 管理后台设置账号类型
有三种类型:标准版、Express、Custom,不同的账号类型区别如下:


二、商户去到 Stripe 管理后台设置用户注册 Stripe 的 OAuth 账户类型和重定向地址

三、客户端向服务端发起提现请求
- 判断用户是否已经授权过,如果已经授权过,就已经存在 Stripe Account,直接将需要提现的金额上报给服务端,由服务端直接向该 Stripe 账户转账就行;
- 如果用户没有授权,用户需要走 OAuth 授权注册流程,弹出授权网页,让用户去注册 Stripe 账号,绑定自己需要提现的信用卡信息;
- 用户注册完成后会回调之前在 Stripe 管理后台配置的重定向地址,并携带回到参数 code 和 state;
- 客户端拿到重定向地址后面回调回来的参数 code 和 state,上报给服务端,服务端就能通过 code 查到刚才注册用户的 Stripe 账号信息,服务端向该用户转账,即用户完成提现;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
let authorizeURL = "https://connect.stripe.com/express/oauth/authorize" + "?" + "redirect_uri=\(redirectUri)&" + "client_id=\(clientId)&" + "state=\(state)&" + "stripe_user[business_type]=individual"
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
guard let urlString = navigationAction.request.url?.absoluteString,
let redirectUrl = model?.redirectUri else {
decisionHandler(WKNavigationActionPolicy.cancel)
return
}
if urlString.hasPrefix(redirectUrl) {
print("发生重定向请求:\(urlString)")
guard let queryParames = navigationAction.request.url?.queryParameters else {
decisionHandler(WKNavigationActionPolicy.cancel)
return
}
let code = queryParames.filter{ $0.key == "code" }.first?.value ?? ""
let state = queryParames.filter{ $0.key == "state" }.first?.value ?? ""
decisionHandler(WKNavigationActionPolicy.cancel)
} else {
decisionHandler(WKNavigationActionPolicy.allow)
}
}
|
Stripe 后台
支付记录,查看详细的支付信息

参考链接
- https://stripe.com/docs/apple-pay
- https://developer.apple.com/apple-pay/sandbox-testing/
- https://github.com/stripe/example-mobile-backend/tree/v18.1.0
- https://github.com/stripe/stripe-ios
- https://github.com/stripe-samples/accept-a-card-payment
- https://stripe.com/docs/connect/express-accounts
「梦想一旦被付诸行动,就会变得神圣,如果觉得我的文章对您有用,请帮助本站成长」