Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

  1. #1
    Futur Membre du Club
    Portée des fonctions, class, variables. Intégration d'une fonction stripe.
    Hello à tous.

    Voilà je débute dans le langage swift, je me débrouille avec plein d'autre langages mais là je ne comprends pas comment passe les variables et les paramètres dans les fonctions...

    Je souhaite rajouter une fonction de contrôle du 3D secure de chez Stripe, voici l'exemple donné par Stripe :
    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    // Assemble the PaymentIntent parameters
    let paymentIntentParams = STPPaymentIntentParams(clientSecret: clientSecret)
    paymentIntentParams.paymentMethodId = paymentResult.paymentMethod.stripeId
     
    // Confirm the PaymentIntent
    STPPaymentHandler.shared().confirmPayment(withParams: paymentIntentParams, authenticationContext: paymentContext) { status, paymentIntent, error in
        switch status {
        case .succeeded:
            // Your backend asynchronously fulfills the customer's order, e.g. via webhook
            completion(.success, nil)
        case .failed:
            completion(.error, error) // Report error
        case .canceled:
            completion(.userCancellation, nil) // Customer cancelled
        @unknown default:
            completion(.error, nil)
        }
    }


    Il s'agit d'une application existante, voici la partie concerné par rapport au paiement :
    PurchaseValidationController.swift :
    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    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
    import UIKit
    import Lottie
    import Firebase
     
    class PurchaseValidationController: UIViewController {
     
        let worker = PurchaseValidationWorker()
     
        @IBOutlet private weak var animationView: AnimationView!
        @IBOutlet private weak var titleLabel: UILabel!
        @IBOutlet private weak var subtitleLabel: UILabel!
        @IBOutlet private weak var purchaseButton: PrimaryButton!
     
        override func viewDidLoad() {
            // ...
        }
     
        private func buyProduct() {
            if worker.product.creator?.id == worker.user.id {
                displayUserOwnProductError()
                return
            }
     
            purchaseButton.displayActivity(true)
     
            worker.buyProduct {
                [weak self] (error) in
                if self == nil { return }
     
                print("Purchase validation controller")
     
     
                self?.purchaseButton.displayActivity(false)
     
                if error != nil {
                    self?.displayPurchaseError()
                } else {
                    if let id = self?.worker.product.id,
                        let title = self?.worker.product.title,
                        let order = self?.worker.order {
                        print(order)
                        Analytics.logEvent(AnalyticsEventBeginCheckout, parameters: [
                            AnalyticsParameterItemID: id,
                            AnalyticsParameterItemName: title,
                            ])
                    }
                    self?.replaceByPurchaseConfirmationController()
                }
            }
        }
     
    }


    On appel donc la fonction buyProduct du worker qui est dans "PuschaseValidationWorker.swift"
    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    import Foundation
    import Stripe
     
    class PurchaseValidationWorker {
     
        // Input
        var product: Product!
        var user: Account!
     
        // Ouput
        var order: Order?
     
        func buyProduct(completion: @escaping (_ error: AppError?) -> Void) {
     
            Product.buyProduct(id: product.id!){
                [weak self] (order, error) in
                self?.order = order
                print("Purchase Validation Worker")
                completion(error)
            }
        }
     
    }


    Qui appel lui la fonction buyProduct de Product soit du fichier "Product Networking.swift"
    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    import Alamofire
    import CoreLocation
    import Stripe
     
    extension Product {
     
        static func buyProduct(id: String,
                               completion: @escaping (_ order: Order?, _ error: AppError?) -> Void) {
            ProductAPIRouter.buyProduct(productId: id).performRequest { (result, networkError) in
                processOrderResponse(result: result,
                                     networkError: networkError,
                                     completion: completion)
     
                // On envoi la demande à une API nodeJS qui retourne le "result"
                // J'ai essayé de mettre la fonction ici mais il me met une erreur à cause du "paymentContext"
            }
        }
     
    }


    J'ai essayé de mettre la fonction Stripe dans chacun des 3 fichiers mais rien à faire. Je comprends pas où je dois la placer.
    En tout logique vu que je récupère la réponse de l'API dans la fonction "buyProduct" du fichier "Product Networking.swift" je dois la mettre à cette endroit ?
    Je l'ai donc mise dans cette fonction mais il ne trouve pas le "paymentContext" de la fonction stripe (authenticationContext: paymentContext)

    Certain utilise "self" comme paymentContext, je dois donc déclaré le paymentContext quelque part mais où ?

    Merci d'avance, car je galère depuis des jours dessus!

  2. #2
    Membre régulier
    Réponse
    Hello Blinkers,

    Personnellement, je n'ai jamais utilisé la libraire stripe, mais je placerais la fonction dans ton controller "PurchaseValidationController.swift"

    Concernant le "paymentContext", est-ce qu'il est défini ?

    Voici un exemple de code trouvé sur https://cocoapods.org/pods/Stripe:
    Le paymentContext est défini dans le init

    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    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
    92
    93
    94
    95
    96
    init(products: [Product], settings: Settings) {
            if let stripePublishableKey = UserDefaults.standard.string(forKey: "StripePublishableKey") {
                self.stripePublishableKey = stripePublishableKey
            }
            if let backendBaseURL = UserDefaults.standard.string(forKey: "StripeBackendBaseURL") {
                self.backendBaseURL = backendBaseURL
            }
            let stripePublishableKey = self.stripePublishableKey
            let backendBaseURL = self.backendBaseURL
     
            assert(stripePublishableKey.hasPrefix("pk_"), "You must set your Stripe publishable key at the top of CheckoutViewController.swift to run this app.")
            assert(backendBaseURL != nil, "You must set your backend base url at the top of CheckoutViewController.swift to run this app.")
     
            self.products = products
            self.theme = settings.theme
            MyAPIClient.sharedClient.baseURLString = self.backendBaseURL
     
            // This code is included here for the sake of readability, but in your application you should set up your configuration and theme earlier, preferably in your App Delegate.
            Stripe.setDefaultPublishableKey(self.stripePublishableKey)
            let config = STPPaymentConfiguration.shared()
            config.appleMerchantIdentifier = self.appleMerchantID
            config.companyName = self.companyName
            config.requiredBillingAddressFields = settings.requiredBillingAddressFields
            config.requiredShippingAddressFields = settings.requiredShippingAddressFields
            config.shippingType = settings.shippingType
            config.additionalPaymentOptions = settings.additionalPaymentOptions
            self.country = settings.country
            self.paymentCurrency = settings.currency
     
            let customerContext = STPCustomerContext(keyProvider: MyAPIClient.sharedClient)
            let paymentContext = STPPaymentContext(customerContext: customerContext,
                                                   configuration: config,
                                                   theme: settings.theme)
            let userInformation = STPUserInformation()
            paymentContext.prefilledInformation = userInformation
            paymentContext.paymentAmount = products.reduce(0) { result, product in
                return result + product.price
            }
            paymentContext.paymentCurrency = self.paymentCurrency
     
            self.tableView = UITableView()
     
            let paymentSelectionFooter = PaymentContextFooterView(text:
                """
    The sample backend attaches some test cards:
     
    • 4242 4242 4242 4242
        A default VISA card.
     
    • 4000 0000 0000 3220
        Use this to test 3D Secure 2 authentication.
     
    See https://stripe.com/docs/testing.
    """)
            paymentSelectionFooter.theme = settings.theme
            paymentContext.paymentOptionsViewControllerFooterView = paymentSelectionFooter
     
            let addCardFooter = PaymentContextFooterView(text: "You can add custom footer views to the add card screen.")
            addCardFooter.theme = settings.theme
            paymentContext.addCardViewControllerFooterView = addCardFooter
     
            self.paymentContext = paymentContext
     
            self.paymentRow = CheckoutRowView(title: "Pay from", detail: "Select payment method")
            if let requiredFields = config.requiredShippingAddressFields, !requiredFields.isEmpty {
                var shippingString = "Contact"
                if requiredFields.contains(.postalAddress) {
                    shippingString = config.shippingType == .shipping ? "Ship to" : "Deliver to"
                }
                self.shippingRow = CheckoutRowView(title: shippingString,
                                                   detail: "Select address")
            } else {
                self.shippingRow = nil
            }
            self.totalRow = CheckoutRowView(title: "Total", detail: "", tappable: false)
            self.buyButton = BuyButton(enabled: false, title: "Buy")
            let numberFormatter = NumberFormatter()
            numberFormatter.locale = settings.currencyLocale
            numberFormatter.numberStyle = .currency
            numberFormatter.usesGroupingSeparator = true
            self.numberFormatter = numberFormatter
            super.init(nibName: nil, bundle: nil)
            self.paymentContext.delegate = self
            paymentContext.hostViewController = self
            self.tableView.delegate = self
            self.tableView.dataSource = self
        }
     
        required init?(coder aDecoder: NSCoder) {
            fatalError("init(coder<img src="images/smilies/icon_smile.gif" border="0" alt="" title=":)" class="inlineimg" /> has not been implemented")
        }
     
    @objc func didTapBuy() {
            self.paymentInProgress = true
            self.paymentContext.requestPayment()
        }


    et ensuite l'extension

    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    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
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    // MARK: STPPaymentContextDelegate
    extension CheckoutViewController: STPPaymentContextDelegate {
        enum CheckoutError: Error {
            case unknown
     
            var localizedDescription: String {
                switch self {
                case .unknown:
                    return "Unknown error"
                }
            }
        }
        func paymentContext(_ paymentContext: STPPaymentContext, didCreatePaymentResult paymentResult: STPPaymentResult, completion: @escaping STPPaymentStatusBlock) {
            // Create the PaymentIntent on the backend
            // To speed this up, create the PaymentIntent earlier in the checkout flow and update it as necessary (e.g. when the cart subtotal updates or when shipping fees and taxes are calculated, instead of re-creating a PaymentIntent for every payment attempt.
            MyAPIClient.sharedClient.createPaymentIntent(products: self.products, shippingMethod: paymentContext.selectedShippingMethod, country: self.country) { result in
                switch result {
                case .success(let clientSecret):
                    // Confirm the PaymentIntent
                    let paymentIntentParams = STPPaymentIntentParams(clientSecret: clientSecret)
                    paymentIntentParams.configure(with: paymentResult)
                    paymentIntentParams.returnURL = "payments-example://stripe-redirect"
                    STPPaymentHandler.shared().confirmPayment(withParams: paymentIntentParams, authenticationContext: paymentContext) { status, paymentIntent, error in
                        switch status {
                        case .succeeded:
                            // Our example backend asynchronously fulfills the customer's order via webhook
                            // See https://stripe.com/docs/payments/payment-intents/ios#fulfillment
                            completion(.success, nil)
                        case .failed:
                            completion(.error, error)
                        case .canceled:
                            completion(.userCancellation, nil)
                        @unknown default:
                            completion(.error, nil)
                        }
                    }
                case .failure(let error):
                    // A real app should retry this request if it was a network error.
                    print("Failed to create a Payment Intent: \(error)")
                    completion(.error, error)
                    break
                }
            }
        }
     
        func paymentContext(_ paymentContext: STPPaymentContext, didFinishWith status: STPPaymentStatus, error: Error?) {
            self.paymentInProgress = false
            let title: String
            let message: String
            switch status {
            case .error:
                title = "Error"
                message = error?.localizedDescription ?? ""
            case .success:
                title = "Success"
                message = "Your purchase was successful!"
            case .userCancellation:
                return()
            @unknown default:
                return()
            }
            let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert)
            let action = UIAlertAction(title: "OK", style: .default, handler: nil)
            alertController.addAction(action)
            self.present(alertController, animated: true, completion: nil)
        }
     
        func paymentContextDidChange(_ paymentContext: STPPaymentContext) {
            self.paymentRow.loading = paymentContext.loading
            if let paymentOption = paymentContext.selectedPaymentOption {
                self.paymentRow.detail = paymentOption.label
            } else {
                self.paymentRow.detail = "Select Payment"
            }
            if let shippingMethod = paymentContext.selectedShippingMethod {
                self.shippingRow?.detail = shippingMethod.label
            } else {
                self.shippingRow?.detail = "Select address"
            }
            self.totalRow.detail = self.numberFormatter.string(from: NSNumber(value: Float(self.paymentContext.paymentAmount)/100))!
            buyButton.isEnabled = paymentContext.selectedPaymentOption != nil && (paymentContext.selectedShippingMethod != nil || self.shippingRow == nil)
        }
     
        func paymentContext(_ paymentContext: STPPaymentContext, didFailToLoadWithError error: Error) {
            let alertController = UIAlertController(
                title: "Error",
                message: error.localizedDescription,
                preferredStyle: .alert
            )
            let cancel = UIAlertAction(title: "Cancel", style: .cancel, handler: { action in
                // Need to assign to _ because optional binding loses @discardableResult value
                // https://bugs.swift.org/browse/SR-1681
                _ = self.navigationController?.popViewController(animated: true)
            })
            let retry = UIAlertAction(title: "Retry", style: .default, handler: { action in
                self.paymentContext.retryLoading()
            })
            alertController.addAction(cancel)
            alertController.addAction(retry)
            self.present(alertController, animated: true, completion: nil)
        }
     
        // Note: this delegate method is optional. If you do not need to collect a
        // shipping method from your user, you should not implement this method.
        func paymentContext(_ paymentContext: STPPaymentContext, didUpdateShippingAddress address: STPAddress, completion: @escaping STPShippingMethodsCompletionBlock) {
            let upsGround = PKShippingMethod()
            upsGround.amount = 0
            upsGround.label = "UPS Ground"
            upsGround.detail = "Arrives in 3-5 days"
            upsGround.identifier = "ups_ground"
            let upsWorldwide = PKShippingMethod()
            upsWorldwide.amount = 10.99
            upsWorldwide.label = "UPS Worldwide Express"
            upsWorldwide.detail = "Arrives in 1-3 days"
            upsWorldwide.identifier = "ups_worldwide"
            let fedEx = PKShippingMethod()
            fedEx.amount = 5.99
            fedEx.label = "FedEx"
            fedEx.detail = "Arrives tomorrow"
            fedEx.identifier = "fedex"
     
            DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
                if address.country == nil || address.country == "US" {
                    completion(.valid, nil, [upsGround, fedEx], fedEx)
                } else if address.country == "AQ" {
                    let error = NSError(domain: "ShippingError", code: 123, userInfo: [NSLocalizedDescriptionKey: "Invalid Shipping Address",
                                                                                       NSLocalizedFailureReasonErrorKey: "We can't ship to this country."])
                    completion(.invalid, error, nil, nil)
                } else {
                    fedEx.amount = 20.99
                    fedEx.identifier = "fedex_world"
                    completion(.valid, nil, [upsWorldwide, fedEx], fedEx)
                }
            }
        }
     
    }


    Bien à toi

    Benjamin