diff --git a/pay/pubspec.yaml b/pay/pubspec.yaml index 3bcb958..0652450 100644 --- a/pay/pubspec.yaml +++ b/pay/pubspec.yaml @@ -35,7 +35,11 @@ dependencies: flutter_localizations: sdk: flutter pay_android: ^3.1.1 - pay_ios: ^1.1.0 + pay_ios: + git: + url: https://github.com/dibsyhq/flutter-plugin.git + ref: main + path: pay_ios pay_platform_interface: ^2.0.0 meta: ^1.10.0 diff --git a/pay_ios/ios/Classes/PaymentExtensions.swift b/pay_ios/ios/Classes/PaymentExtensions.swift index 3fe1a9d..cfb24e5 100644 --- a/pay_ios/ios/Classes/PaymentExtensions.swift +++ b/pay_ios/ios/Classes/PaymentExtensions.swift @@ -147,6 +147,9 @@ extension PKPaymentNetwork { case "girocard": guard #available(iOS 14.0, *) else { return nil } return .girocard + case "himyan": + guard #available(iOS 18.4, *) else { return nil } + return .himyan case "idCredit": return .idCredit case "interac": diff --git a/pay_ios/ios/Classes/PaymentHandler.swift b/pay_ios/ios/Classes/PaymentHandler.swift index 887aadf..97c3d71 100644 --- a/pay_ios/ios/Classes/PaymentHandler.swift +++ b/pay_ios/ios/Classes/PaymentHandler.swift @@ -36,7 +36,12 @@ enum PaymentHandlerStatus { /// paymentHandler.canMakePayments(stringArguments) /// ``` class PaymentHandler: NSObject { + + // CHANGE 1: Define the variable here so the class owns it. + // This stores the original prices without any surcharges. + var baseSummaryItems: [PKPaymentSummaryItem] = [] + var creditSurchargeRate: NSDecimalNumber = .zero /// Holds the current status of the payment process. var paymentHandlerStatus: PaymentHandlerStatus! @@ -74,12 +79,25 @@ class PaymentHandler: NSObject { // Reset payment handler status paymentHandlerStatus = .started + self.creditSurchargeRate = .zero // Reset to 0 before every new payment + + if let configDict = PaymentHandler.extractPaymentConfiguration(from: paymentConfiguration) { + if let processingFee = configDict["processingFee"] as? [String: Any], + let creditRate = processingFee["credit"] as? Double { + + // Convert "2.5" (percentage) to "0.025" (multiplier) + let multiplier = creditRate / 100.0 + self.creditSurchargeRate = NSDecimalNumber(value: multiplier) + } + } + // Deserialize payment configuration. guard let paymentRequest = PaymentHandler.createPaymentRequest(from: paymentConfiguration, paymentItems: paymentItems) else { result(FlutterError(code: "invalidPaymentConfiguration", message: "It was not possible to create a payment request from the provided configuration. Review your payment configuration and run again", details: nil)) return } + self.baseSummaryItems = paymentRequest.paymentSummaryItems // Display the payment selector with the request created. let paymentController = PKPaymentAuthorizationController(paymentRequest: paymentRequest) paymentController.delegate = self @@ -166,6 +184,13 @@ class PaymentHandler: NSObject { if let supportedNetworks = supportedNetworks(from: paymentConfigurationString) { paymentRequest.supportedNetworks = supportedNetworks } + + // Add supported countries if available (iOS 11+). + if #available(iOS 11.0, *) { + if let supportedCountries = paymentConfiguration["supportedCountries"] as? Array { + paymentRequest.supportedCountries = Set(supportedCountries) + } + } return paymentRequest } @@ -174,8 +199,57 @@ class PaymentHandler: NSObject { /// Extension that implements the completion methods in the delegate to respond to user selection. extension PaymentHandler: PKPaymentAuthorizationControllerDelegate { - func paymentAuthorizationControllerWillAuthorizePayment(_ controller: PKPaymentAuthorizationController) { - paymentHandlerStatus = .authorizationStarted + func paymentAuthorizationController(_ controller: PKPaymentAuthorizationController, didSelectPaymentMethod paymentMethod: PKPaymentMethod, handler completion: @escaping (PKPaymentRequestPaymentMethodUpdate) -> Void) { + +let isCredit = paymentMethod.type == .credit + + // 1. Start with the clean, original list of items + var currentItems = self.baseSummaryItems + + if isCredit && self.creditSurchargeRate.compare(NSDecimalNumber.zero) == .orderedDescending { + + if let originalTotalItem = currentItems.last { + + let originalAmount = originalTotalItem.amount + + // 2. Define Rounding Behavior (Bankers rounding or Plain) + let behavior = NSDecimalNumberHandler(roundingMode: .plain, + scale: 2, + raiseOnExactness: false, + raiseOnOverflow: false, + raiseOnUnderflow: false, + raiseOnDivideByZero: false) + + // 3. Calculate the Surcharge Amount (e.g., 100 * 0.025 = 2.5) + let rawSurcharge = originalAmount.multiplying(by: self.creditSurchargeRate) + let surchargeAmount = rawSurcharge.rounding(accordingToBehavior: behavior) + + // 4. Create the Surcharge Line Item + // Optional: Format the percentage for the label (e.g. "Credit Card Fee (2.5%)") + let percentage = self.creditSurchargeRate.multiplying(byPowerOf10: 2) + let surchargeLabel = "Credit Card Fee (\(percentage.stringValue)%)" + + let surchargeItem = PKPaymentSummaryItem(label: surchargeLabel, amount: surchargeAmount) + // Note: If you want to show it as an additional cost, usually types are final. + surchargeItem.type = .final + + // 5. Calculate New Grand Total (Original + Surcharge) + let newTotalAmount = originalAmount.adding(surchargeAmount) + let newTotalItem = PKPaymentSummaryItem(label: originalTotalItem.label, amount: newTotalAmount) + + // 6. Construct the new list + // Remove the old total + currentItems.removeLast() + + // Add the surcharge line item + currentItems.append(surchargeItem) + + // Add the new Grand Total at the very end (Apple Pay requirement) + currentItems.append(newTotalItem) + } + } + + completion(PKPaymentRequestPaymentMethodUpdate(paymentSummaryItems: currentItems)) } func paymentAuthorizationController(_: PKPaymentAuthorizationController, didAuthorizePayment payment: PKPayment, handler completion: @escaping (PKPaymentAuthorizationResult) -> Void) { @@ -208,3 +282,18 @@ extension PaymentHandler: PKPaymentAuthorizationControllerDelegate { } } } + + +extension PKPaymentMethodType { + var stringValue: String { + switch self { + case .debit: return "debit" + case .credit: return "credit" + case .prepaid: return "prepaid" + case .store: return "store" + case .eMoney: return "emoney" + default: return "unknown" + } + } +} +