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)
}
}
}
}
Cannot read property 'reduce' of undefined at calculateOrderAmount
means that youritems
is undefined when passed intocalculateOrderAmount
, likely becauseitems
is undefined in thebody
of your request: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:
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 }
?