Node Js error Cannot read property 'reduce' of undefined with SwiftUI

63 views Asked by At

I have node js server hosted into Glitch and I am using express version . I also added the packages for Stripe and cros . I am making HTTP request form IOS app which is developed into swiftUI (Shopping cart app). I have added the stripe into my SwiftUI project. I am following this tutorial https://www.youtube.com/watch?v=De7EL_1jv0c. Here is the glitch server link https://glitch.com/ to host the node js server . I using the public key during the request to exchange the secret key. The problem is I have having this error into Glitch server console ..

(node:1314) UnhandledPromiseRejectionWarning: TypeError: Cannot read property 'reduce' of undefined at calculateOrderAmount (/app/server.js:11:23)

Tis function produce the error saying the reduce not be read . This function calculating the total .

const calculateOrderAmount = items => {

  const total = items.reduce((previous, current) => {
    return previous.price + current.price
  })
  return total * 100;
};

Here is the node js server code ..

const express = require("express");
const app = express();
// This is your test secret API key.
const stripe = require("stripe")("your public key form stripe");

app.use(express.static("public"));
app.use(express.json());

const calculateOrderAmount = items => {
  
  const total = items.reduce((previous, current) => {
    return previous.price + current.price
  })
  return total * 100;
};

app.post("/create-payment-intent", async (req, res) => {
  const { items } = req.body;

  // Create a PaymentIntent with the order amount and currency
  const paymentIntent = await stripe.paymentIntents.create({
    amount: calculateOrderAmount(items),
    currency: "gbp",
    // In the latest version of the API, specifying the `automatic_payment_methods` parameter is optional because Stripe enables its functionality by default.
    automatic_payment_methods: {
      enabled: true,
    },
  });

 res.send({
    clientSecret: paymentIntent.client_secret,
  });
});

app.listen(8080, () => console.log("Node server listening on port 8080!"));

Here is my Swiftui main.app code ..

import SwiftUI
import Firebase
import Stripe

@main
struct DemoEcommerceApp: App {
    @StateObject var viewModel = AuthViewModel()
    @StateObject var order = Order()

    init() {
        FirebaseApp.configure()
        StripeAPI.defaultPublishableKey = "Stripe public key"
    }
    var body: some Scene {
        WindowGroup {
           ContentViewOne()
                .environmentObject(viewModel)
                .environmentObject(order)
        }
    }
}

Here is the product.

 @Published var products = [Product]()

Json.

// MARK: - Product
struct Product: Codable, Hashable, Identifiable {
    let id: Int
    let title: String
    var price, quantity, total: Int
    let discountPercentage: Double
    let discountedPrice: Int
    let thumbnail: String
}

Here is my private function to start making payment ..

extension OrderView {

    private func startCheckout(completion: @escaping (String?) -> Void) {

        let url = URL(string: "https://rogue-quartz-ounce.glitch.me/create-payment-intent")!

        var request = URLRequest(url: url)
        request.httpMethod = "POST"
        request.setValue("application/json", forHTTPHeaderField: "Content-Type")
        request.httpBody = try! JSONEncoder().encode(order.products)

        URLSession.shared.dataTask(with: request) { data, response, error in

            guard let data = data, error == nil,
                  (response as? HTTPURLResponse)?.statusCode == 200
            else {
                completion(nil)
                print(error?.localizedDescription)
                return
            }

            let checkoutIntentResponse = try? JSONDecoder().decode(CheckoutIntentResponse.self, from: data)
            completion(checkoutIntentResponse?.clientSecret)

        }.resume()

    }
}

Here is my pay function ..

extension OrderView {

    private func pay() {

        guard let clientSecret = PaymentConfig.shared.paymentIntentClientSecret else {
            return
        }

        let paymentIntentParams = STPPaymentIntentParams(clientSecret: clientSecret)
        paymentIntentParams.paymentMethodParams = paymentMethodParams

        paymentGatewayController.submitPayment(intent: paymentIntentParams) { status, intent, error in

            switch status {
                case .failed:
                    message = "Failed"
                case .canceled:
                    message = "Cancelled"
                case .succeeded:
                    message = "Your payment has been successfully completed!"
            }

        }
    }
}

Here is the API response code ..

import Foundation


struct CheckoutIntentResponse: Decodable {
    let clientSecret: String
}

Here is the singleton class ..

import Foundation

class PaymentConfig {

    var paymentIntentClientSecret: String?
    static var shared: PaymentConfig = PaymentConfig()

    private init() { }
}

Here is the gateway controller ..

import Foundation
import UIKit
import Stripe

class PaymentGatewayController: UIViewController {


    func submitPayment (intent : STPPaymentIntentParams, completion: @escaping (STPPaymentHandlerActionStatus, STPPaymentIntent?, NSError?) -> Void) {
        let paymentHandler = STPPaymentHandler.shared()
        paymentHandler.confirmPayment(intent, with: self) { (status, intent, error) in
            completion(status, intent, error)
        }
    }
}

extension PaymentGatewayController: STPAuthenticationContext {
    func authenticationPresentingViewController() -> UIViewController {
        return self
    }
}

Here is the swiftui view code ..

@State private var message: String = ""
    @State private var isSuccess: Bool = false
    @State private var paymentMethodParams: STPPaymentMethodParams?
    let paymentGatewayController = PaymentGatewayController()

       var body: some View {

        NavigationStack {
            
            List {
                Section {
// Stripe Credit Card TextField Here
             STPPaymentCardTextField.Representable.init(paymentMethodParams:        $paymentMethodParams)
                    } header: {
                        Text("Payment Information")
                    }
                    HStack {
                        Spacer()
                        Button("Pay") {
                            startCheckout { clientSecret in

                                PaymentConfig.shared.paymentIntentClientSecret = clientSecret
                                self.pay()
                            }

                        }.buttonStyle(.plain)
                        Spacer()
                    }

                }
                HStack {
                    Text(message)
                        .font(.headline)
                }
}
}
}
1

There are 1 answers

3
Nolan H On

Cannot read property 'reduce' of undefined at calculateOrderAmount means that your items is undefined when passed into calculateOrderAmount, likely because items is undefined in the body of your request:

const { items } = req.body

this is where you need to debug, and determine what your req.body contains, and how to change it.

I'm not familiar with Swift HTTP requests, but looking at the code you shared it looking like the body is set here:

request.httpBody = try! JSONEncoder().encode(order.products)

It's not obvious if/how this would set items so I suggest looking here first. Perhaps this should be something like { items: order.products }?