Below is my code which I largely put together based on the Quickstart Guide (https://docs.stripe.com/payments/quickstart) plus the Address Element section of Stripe documentation (https://docs.stripe.com/elements/address-element/collect-addresses).
import UIKit
import StripePaymentSheet
class CheckoutViewController: UIViewController {
private static let backendURL = URL(string: "https://sundealit.com/stripetest")!
private var paymentIntentClientSecret: String?
private var addressViewController: AddressViewController?
private var addressDetails: AddressViewController.AddressDetails?
private lazy var payButton: UIButton = {
let button = UIButton(type: .custom)
button.setTitle("Pay now", for: .normal)
button.backgroundColor = .systemIndigo
button.layer.cornerRadius = 5
button.contentEdgeInsets = UIEdgeInsets(top: 12, left: 12, bottom: 12, right: 12)
button.addTarget(self, action: #selector(pay), for: .touchUpInside)
button.translatesAutoresizingMaskIntoConstraints = false
button.isEnabled = false // Initially disabled
return button
}()
private lazy var addressButton: UIButton = {
let button = UIButton(type: .custom)
button.setTitle("Add Address", for: .normal)
button.backgroundColor = .systemIndigo
button.layer.cornerRadius = 5
button.contentEdgeInsets = UIEdgeInsets(top: 12, left: 12, bottom: 12, right: 12)
button.addTarget(self, action: #selector(address), for: .touchUpInside)
button.translatesAutoresizingMaskIntoConstraints = false
button.isEnabled = true // Enable this button initially so user can add address
return button
}()
private var addressConfiguration: AddressViewController.Configuration {
return AddressViewController.Configuration(
additionalFields: .init(phone: .required),
allowedCountries: ["US", "CA", "GB", "HK"],
title: "Shipping Address"
)
}
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemBackground
view.addSubview(payButton)
view.addSubview(addressButton)
NSLayoutConstraint.activate([
payButton.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 16),
payButton.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -16),
payButton.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -16)
])
NSLayoutConstraint.activate([
addressButton.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 16),
addressButton.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -16),
addressButton.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 16)
])
self.fetchPaymentIntent()
}
func fetchPaymentIntent() {
let url = Self.backendURL.appendingPathComponent("/create-payment-intent")
let shoppingCartContent: [String: Any] = [
"items": [
["id": "xl-shirt"]
]
]
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpBody = try? JSONSerialization.data(withJSONObject: shoppingCartContent)
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")
print(clientSecret)
print(publishableKey)
self?.paymentIntentClientSecret = clientSecret
StripeAPI.defaultPublishableKey = publishableKey
DispatchQueue.main.async {
self?.payButton.isEnabled = true
}
})
task.resume()
}
func displayAlert(title: String, message: String? = nil) {
DispatchQueue.main.async {
let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: "OK", style: .default))
self.present(alertController, animated: true)
}
}
@objc
func pay() {
guard let paymentIntentClientSecret = self.paymentIntentClientSecret else {
return
}
var configuration = PaymentSheet.Configuration()
configuration.merchantDisplayName = "Example, Inc."
let paymentSheet = PaymentSheet(
paymentIntentClientSecret: paymentIntentClientSecret,
configuration: configuration)
paymentSheet.present(from: self) { [weak self] (paymentResult) in
switch paymentResult {
case .completed:
self?.displayAlert(title: "Payment complete!")
case .canceled:
print("Payment canceled!")
case .failed(let error):
self?.displayAlert(title: "Payment failed", message: error.localizedDescription)
}
}
}
@objc
func address() {
self.addressViewController = AddressViewController(configuration: addressConfiguration, delegate: self)
let navigationController = UINavigationController(rootViewController: addressViewController!)
present(navigationController, animated: true)
}
}
extension CheckoutViewController: AddressViewControllerDelegate {
func addressViewControllerDidFinish(_ addressViewController: AddressViewController, with address: AddressViewController.AddressDetails?) {
addressViewController.dismiss(animated: true)
self.addressDetails = address
}
}
My question is that, now that I can collect address from user, the “collected address” seems to be stored in nowhere and is not called in anyway as the user make payment. Such a “collected address” is of no use to me if for each payment I cannot refer to where I am going to ship my product. So how can I modify the code to somehow link the two together and be able to refer to it in Stripe backend? The Stripe documentation does not seem to provide any reference code to that.
Please help… Thanks!