You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Stripe PaymentSheet中添加收货地址收集功能?

解决Stripe PaymentSheet收货地址收集问题

我基于Stripe提供的基础模板编写了Swift代码,但该模板无法让用户输入收货地址,导致付款后无法获取商品配送地址。查阅官方文档后对步骤4的代码插入位置感到困惑,现有代码如下:

import UIKit
import StripePaymentSheet

class CheckoutViewController: UIViewController {

    private static let backendURL = URL(string: "https://sundealit.com/stripetest")!

    private var paymentIntentClientSecret: String?

    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
        return button
    }()

    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .systemBackground
        view.addSubview(payButton)

        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)
        ])

        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)
            }
        }
    }

}

修改方案

要实现收货地址收集,只需在pay()方法中修改PaymentSheet.Configuration的配置,添加收货地址相关设置,并配置回调获取用户输入的地址。

1. 配置收货地址收集规则

在创建configuration后,添加以下代码:

// 设置收货类型为配送
configuration.shippingType = .shipping
// 配置允许的收货国家(按需修改)
configuration.shippingAddressCollection = .init(countries: ["US", "CA", "CN"])

2. 添加地址回调获取用户输入的地址

首先让CheckoutViewController遵循PaymentSheetShippingDetailsDelegate协议:

class CheckoutViewController: UIViewController, PaymentSheetShippingDetailsDelegate {

然后在pay()方法中绑定代理:

// 绑定配送地址代理
paymentSheet.shippingDetailsDelegate = self

最后实现协议方法,获取并处理用户输入的地址:

// 当用户提交收货地址时触发
func paymentSheet(_ paymentSheet: PaymentSheet, didUpdateShippingDetails shippingDetails: PaymentSheet.ShippingDetails, completion: @escaping (PaymentSheet.ShippingDetailsUpdateResult) -> Void) {
    // 可在此添加地址验证、运费计算等逻辑
    // 直接确认地址有效,继续支付流程
    completion(.success(.init(shippingOptions: [])))
    
    // 保存用户输入的地址信息(按需存储到本地或后端)
    print("用户收货地址:")
    print("姓名:\(shippingDetails.name)")
    print("地址行1:\(shippingDetails.address.line1)")
    if let line2 = shippingDetails.address.line2 {
        print("地址行2:\(line2)")
    }
    print("城市:\(shippingDetails.address.city)")
    if let state = shippingDetails.address.state {
        print("州/省:\(state)")
    }
    print("邮编:\(shippingDetails.address.postalCode)")
    print("国家:\(shippingDetails.address.country)")
    if let phone = shippingDetails.phone {
        print("电话:\(phone)")
    }
}

3. 后端配合(重要)

确保后端在创建PaymentIntent时,允许收集收货地址信息。支付完成后,你可以通过Stripe Dashboard查看该PaymentIntent的收货地址,或通过后端Webhook获取地址详情并存储到自身数据库。


修改后的完整代码

import UIKit
import StripePaymentSheet

class CheckoutViewController: UIViewController, PaymentSheetShippingDetailsDelegate {

    private static let backendURL = URL(string: "https://sundealit.com/stripetest")!

    private var paymentIntentClientSecret: String?

    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
        return button
    }()

    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .systemBackground
        view.addSubview(payButton)

        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)
        ])

        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."
        // 新增:配置收货地址收集
        configuration.shippingType = .shipping
        configuration.shippingAddressCollection = .init(countries: ["US", "CA", "CN"])

        let paymentSheet = PaymentSheet(
            paymentIntentClientSecret: paymentIntentClientSecret,
            configuration: configuration)
        // 新增:绑定配送地址代理
        paymentSheet.shippingDetailsDelegate = self

        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)
            }
        }
    }

    // 新增:实现配送地址代理方法
    func paymentSheet(_ paymentSheet: PaymentSheet, didUpdateShippingDetails shippingDetails: PaymentSheet.ShippingDetails, completion: @escaping (PaymentSheet.ShippingDetailsUpdateResult) -> Void) {
        // 验证地址或计算运费逻辑可在此添加
        completion(.success(.init(shippingOptions: [])))
        
        // 打印并保存用户地址信息
        print("用户收货地址:")
        print("姓名:\(shippingDetails.name)")
        print("地址行1:\(shippingDetails.address.line1)")
        if let line2 = shippingDetails.address.line2 {
            print("地址行2:\(line2)")
        }
        print("城市:\(shippingDetails.address.city)")
        if let state = shippingDetails.address.state {
            print("州/省:\(state)")
        }
        print("邮编:\(shippingDetails.address.postalCode)")
        print("国家:\(shippingDetails.address.country)")
        if let phone = shippingDetails.phone {
            print("电话:\(phone)")
        }
    }

}

内容的提问来源于stack exchange,提问作者Bosco Tsin

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.20 15:54:55