0
votes

The goal is to just make a successful Apple Pay payment and be done with this API integration. I'm literally on the brink of success but I have just one more bug I can't figure out.

func startApplePayCheckout() {
    let backendUrlForIntent = "https://us-central1-xxxxx-41f12.cloudfunctions.net/createPaymentIntent"
    guard let user = Auth.auth().currentUser else { return }
    getUsersStripeCustomerID { (customerid) in
        if let id = customerid {
            // Create a PaymentIntent as soon as the view loads
            let costAsAnInt = self.actualCostOfEvent.text?.replacingOccurrences(of: "$", with: "").replacingOccurrences(of: ".", with: "")
            let costForStripe = Int(costAsAnInt!)
            let url = URL(string: backendUrlForIntent)!
            let json: [String: Any] = [
                "amount": costForStripe! + (self.gothereFee * Int(self.stepperValue.value)),
                "currency": "CAD",
                "customer": id,
                "setup_future_usage": "on_session",
            ]
            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 else {
                    let message = error?.localizedDescription ?? "Failed to decode response from server."
                    self?.displayCancelledAlert(title: "Error Loading Page", message: message)
                    return
                }
                guard let paymentIntentID = json["paymentIntentID"] as? String else { return }
                
                self?.db.document("student_users/\(user.uid)/events_bought/\(self?.nameToUseInAPICall)").setData(["stripePaymentIntentID": paymentIntentID], merge: true, completion: { (error) in
                    if let error = error {
                        print("There was an error setting the paymentIntentID in the document: \(error)")
                    } else {
                        print("PaymentIntentID successfully stored!")
                    }
                })
                print("Created PaymentIntent")
                self?.paymentIntentClientSecret = clientSecret
            })
            task.resume()
        }
    }
    

I call this right when the Apple Pay sheet is presented. The print statements work fine, the clientSecret gets retrieved. Now when I actually try to pay, I still get a "Payment Not Completed", this is what I have in the didCreatePaymentMethod function:

func applePayContext(_ context: STPApplePayContext, didCreatePaymentMethod paymentMethod: STPPaymentMethod, paymentInformation: PKPayment, completion: @escaping STPIntentClientSecretCompletionBlock) {
    guard let paymentIntentClient = paymentIntentClientSecret else {
        print("There is an issue with the clientSecret")
        return
    }
    
    let error = NSError(domain: backendUrl, code: 400, userInfo: [NSLocalizedDescriptionKey: "The payment cannot go through for some reason!"])
    
    let paymentIntentParams = STPPaymentIntentParams(clientSecret: paymentIntentClient)
    paymentIntentParams.paymentMethodId = paymentMethod.stripeId
    
    let paymentHandler = STPPaymentHandler.shared()
    paymentHandler.confirmPayment(paymentIntentParams, with: self) { (status, intent, error) in
        switch status {
        case .canceled:
            print("Payment Canceled")
            break
        case .failed:
            print("Payment Failed")
            break
        case .succeeded:
            print("Payment Succeeded")
            break
        default:
            break
        }
    }
    
    completion(paymentIntentClient, error)

}

EDIT So with this implemented, the payment actually charges now when I check in the logs, but for the actual Apple Pay itself, it never shows the success message in the Apple Pay sheet. Here is what I have in the didCompleteWithStatus method:

 func applePayContext(_ context: STPApplePayContext, didCompleteWith status: STPPaymentStatus, error: Error?) {
    
    guard status == .success else {
        print("Payment couldn't go through")
        return
    }
}

So the weird thing that's going on is that, the first method ends up being successful and the charge is shown in the API Call logs, but the didCompleteWith... method always finishes with an error status and a "Payment Not Completed" on the Apple sheet and for the life of me I cannot understand why.

1
What is the status of the Payment Intent after you go through this process? Are there any hints as to what's happening in the Stripe Dashboard under Developer > Logs and Developer > Events?Justin Michael
Hold on, let me edit the post, I added a couple things. @Justin Michaeldante
just finished editing, take a look now @Justin Michaeldante
In your applePayContext:didCompleteWithStatus:error: delegate method try logging both status and error and add the details to your message.Justin Michael
Oh, wait a second, I think I see the issue. You're creating an actual error and feeding it to the completion handler. If you remove the let error = NSError... line does it work as expected?Justin Michael

1 Answers

0
votes

I believe the issue is that you're creating an error on this line:

let error = NSError(domain: backendUrl, code: 400, userInfo: [NSLocalizedDescriptionKey: "The payment cannot go through for some reason!"])

And then feeding that error into the completion handler here:

completion(paymentIntentClient, error)

That results in the "Payment not complete" error being shown in the Apple Pay sheet.

The idea with the completion handler is to provide it a Payment Intent's client secret or an error if you can't provide it with a client secret, not both. The documentation explains this:

Call this with the PaymentIntent’s client secret, or the error that occurred creating the PaymentIntent.

If you call completion without the error you should be all set.