diff --git a/Sample/lib/feature/home/home_screen.dart b/Sample/lib/feature/home/home_screen.dart index 9560f8b..ef60b5a 100644 --- a/Sample/lib/feature/home/home_screen.dart +++ b/Sample/lib/feature/home/home_screen.dart @@ -263,6 +263,17 @@ class _HomeScreenState extends State { ), const Text('Auto debit'), ], + ),Row( + children: [ + Checkbox( + checkColor: Colors.white, + value: state.useCustomText ?? false, + onChanged: (bool? value) { + context.read().onUseCustomTextChecked(value); + }, + ), + const Text('Use custom text'), + ], ), Row( children: [ diff --git a/Sample/lib/feature/home/home_screen_cubit.dart b/Sample/lib/feature/home/home_screen_cubit.dart index d58c204..91a87f8 100644 --- a/Sample/lib/feature/home/home_screen_cubit.dart +++ b/Sample/lib/feature/home/home_screen_cubit.dart @@ -15,7 +15,10 @@ import 'package:ottu_flutter_checkout_sample/api/ottu_api.dart'; import 'package:ottu_flutter_checkout_sample/feature/home/home_screen_state.dart'; import 'package:ottu_flutter_checkout_sample/main.dart'; -const merchantId = String.fromEnvironment('MERCHANT_ID', defaultValue: "add your merchant Id there"); +const merchantId = String.fromEnvironment( + 'MERCHANT_ID', + defaultValue: "add your merchant Id there", +); const apiKey = String.fromEnvironment('API_KEY', defaultValue: "add your Api key there"); const customerId = "john2"; const currencyCode = "KWD"; @@ -175,6 +178,10 @@ class HomeScreenCubit extends Cubit { emit(state.copyWith(isAutoDebit: isChecked)); } + void onUseCustomTextChecked(bool? isChecked) { + emit(state.copyWith(useCustomText: isChecked)); + } + void onAmountChanged(String amount) async { emit(state.copyWith(amount: amount)); } @@ -250,6 +257,9 @@ class HomeScreenCubit extends Cubit { setupPreload: state.preloadPayload == true ? _apiTransactionDetails : null, formsOfPayment: formOfPayments?.isNotEmpty == true ? formOfPayments : null, theme: _theme, + payButtonText: state.useCustomText == true + ? PayButtonText(en: "Checkout", ar: "الدفع") + : null, ); _navigator.push( "/checkout", diff --git a/Sample/lib/feature/home/home_screen_state.dart b/Sample/lib/feature/home/home_screen_state.dart index a9cca9c..0d42789 100644 --- a/Sample/lib/feature/home/home_screen_state.dart +++ b/Sample/lib/feature/home/home_screen_state.dart @@ -21,6 +21,7 @@ final class HomeScreenState extends Equatable { final bool? isAutoDebit; final bool hasSessionLoaded; final bool? failPaymentValidation; + final bool useCustomText; final PaymentOptionsDisplayMode? paymentOptionsDisplayMode; final Map? formsOfPaymentChecked; final Map? pgCodesChecked; @@ -45,6 +46,7 @@ final class HomeScreenState extends Equatable { this.paymentOptionsDisplayMode, this.formsOfPaymentChecked, this.pgCodesChecked, + this.useCustomText = false, }); HomeScreenState copyWith({ @@ -64,6 +66,7 @@ final class HomeScreenState extends Equatable { bool? isAutoDebit, bool? hasSessionLoaded, bool? failPaymentValidation, + bool? useCustomText, PaymentOptionsDisplayMode? paymentOptionsDisplayMode, Map? formsOfPaymentChecked, Map? pgCodesChecked, @@ -84,6 +87,7 @@ final class HomeScreenState extends Equatable { preloadPayload: preloadPayload ?? this.preloadPayload, isAutoDebit: isAutoDebit ?? this.isAutoDebit, failPaymentValidation: failPaymentValidation ?? this.failPaymentValidation, + useCustomText: useCustomText ?? this.useCustomText, hasSessionLoaded: hasSessionLoaded ?? this.hasSessionLoaded, paymentOptionsDisplayMode: paymentOptionsDisplayMode ?? this.paymentOptionsDisplayMode, formsOfPaymentChecked: formsOfPaymentChecked ?? this.formsOfPaymentChecked, @@ -107,6 +111,7 @@ final class HomeScreenState extends Equatable { preloadPayload, isAutoDebit, failPaymentValidation, + useCustomText, phoneNumber, hasSessionLoaded, paymentOptionsDisplayMode?.name, diff --git a/android/src/main/kotlin/com/ottu/flutter/checkout/CheckoutArguments.kt b/android/src/main/kotlin/com/ottu/flutter/checkout/CheckoutArguments.kt index ef1c6a4..d48d99f 100644 --- a/android/src/main/kotlin/com/ottu/flutter/checkout/CheckoutArguments.kt +++ b/android/src/main/kotlin/com/ottu/flutter/checkout/CheckoutArguments.kt @@ -15,4 +15,5 @@ data class CheckoutArguments( val formsOfPayment: List?, val theme: CustomerTheme?, val paymentOptionsDisplaySettings: PaymentOptionsDisplaySettings, + val payButtonText: PayButtonText?, ) diff --git a/android/src/main/kotlin/com/ottu/flutter/checkout/CheckoutView.kt b/android/src/main/kotlin/com/ottu/flutter/checkout/CheckoutView.kt index ecd0652..cbfd07d 100644 --- a/android/src/main/kotlin/com/ottu/flutter/checkout/CheckoutView.kt +++ b/android/src/main/kotlin/com/ottu/flutter/checkout/CheckoutView.kt @@ -22,6 +22,7 @@ import com.ottu.flutter.checkout.ext.toCheckoutRipple import com.ottu.flutter.checkout.ext.toCheckoutSwitch import com.ottu.flutter.checkout.ext.toCheckoutText import com.ottu.flutter.checkout.ext.toCheckoutTextField +import com.ottu.flutter.checkout.ext.toPayButtonText import com.squareup.moshi.JsonAdapter import io.flutter.plugin.common.BinaryMessenger import io.flutter.plugin.common.MethodChannel @@ -202,7 +203,8 @@ internal class CheckoutView( errorData.toString() ) }, - verifyPayment = ::verifyPayment + verifyPayment = ::verifyPayment, + payButtonText = arguments.payButtonText?.toPayButtonText(), ) onInitialized(sdkFragment) diff --git a/android/src/main/kotlin/com/ottu/flutter/checkout/PayButtonText.kt b/android/src/main/kotlin/com/ottu/flutter/checkout/PayButtonText.kt new file mode 100644 index 0000000..b26753c --- /dev/null +++ b/android/src/main/kotlin/com/ottu/flutter/checkout/PayButtonText.kt @@ -0,0 +1,6 @@ +package com.ottu.flutter.checkout + +import kotlinx.serialization.Serializable + +@Serializable +data class PayButtonText(val en: String, val ar: String) diff --git a/android/src/main/kotlin/com/ottu/flutter/checkout/ext/Extensions.kt b/android/src/main/kotlin/com/ottu/flutter/checkout/ext/Extensions.kt index bf0b14d..948dfd2 100644 --- a/android/src/main/kotlin/com/ottu/flutter/checkout/ext/Extensions.kt +++ b/android/src/main/kotlin/com/ottu/flutter/checkout/ext/Extensions.kt @@ -7,12 +7,14 @@ import com.ottu.checkout.ui.theme.CheckoutTheme import com.ottu.flutter.checkout.ButtonComponent import com.ottu.flutter.checkout.ColorState import com.ottu.flutter.checkout.Margins +import com.ottu.flutter.checkout.PayButtonText import com.ottu.flutter.checkout.RippleColor import com.ottu.flutter.checkout.SwitchComponent import com.ottu.flutter.checkout.TextFieldStyle import com.ottu.flutter.checkout.TextStyle import java.lang.Exception import com.ottu.checkout.ui.theme.style.Margins as CheckoutMargins +import com.ottu.checkout.data.model.localization.PayButtonText as pbt fun String.toColor() = this.let { try { @@ -73,4 +75,6 @@ fun SwitchComponent.toCheckoutSwitch() = CheckoutTheme.Switch( uncheckedTrackTintColor = this.uncheckedTrackTintColor?.toColor(), checkedTrackDecorationColor = this.checkedTrackDecorationColor?.toColor(), uncheckedTrackDecorationColor = this.uncheckedTrackDecorationColor?.toColor() -) \ No newline at end of file +) + +fun PayButtonText.toPayButtonText() = pbt(en = en, ar = ar) \ No newline at end of file diff --git a/ios/ottu_flutter_checkout/Sources/ottu_flutter_checkout/CheckoutArguments.swift b/ios/ottu_flutter_checkout/Sources/ottu_flutter_checkout/CheckoutArguments.swift index e6697aa..2adcdc8 100644 --- a/ios/ottu_flutter_checkout/Sources/ottu_flutter_checkout/CheckoutArguments.swift +++ b/ios/ottu_flutter_checkout/Sources/ottu_flutter_checkout/CheckoutArguments.swift @@ -15,4 +15,5 @@ struct CheckoutArguments: Decodable { let apiTransactionDetails: String? let formsOfPayment: [String]? let theme: CustomerTheme? + let payButtonText: PayButtonText? } diff --git a/ios/ottu_flutter_checkout/Sources/ottu_flutter_checkout/CheckoutPlatformView.swift b/ios/ottu_flutter_checkout/Sources/ottu_flutter_checkout/CheckoutPlatformView.swift index a579846..4935e9b 100644 --- a/ios/ottu_flutter_checkout/Sources/ottu_flutter_checkout/CheckoutPlatformView.swift +++ b/ios/ottu_flutter_checkout/Sources/ottu_flutter_checkout/CheckoutPlatformView.swift @@ -1,8 +1,8 @@ import Flutter import Foundation +import OSLog import SwiftUI import ottu_checkout_sdk -import OSLog // // CheckoutPlatformView.swift @@ -12,7 +12,8 @@ import OSLog // private let checkoutChannelName = "com.ottu.sample/checkout" -private let checkoutVerifyPaymentChannelName = "com.ottu.sample/checkout/payment/verify" +private let checkoutVerifyPaymentChannelName = + "com.ottu.sample/checkout/payment/verify" private let methodCheckoutHeight = "METHOD_CHECKOUT_HEIGHT" private let _methodPaymentSuccessResult = "METHOD_PAYMENT_SUCCESS_RESULT" private let _methodPaymentErrorResult = "METHOD_PAYMENT_ERROR_RESULT" @@ -27,22 +28,23 @@ public class CheckoutPlatformView: NSObject, FlutterPlatformView { private let _view: CheckoutContainerView private weak var paymentViewController: UIViewController? private var checkout: Checkout? - + private func deinitCheckout() { guard let pvc = paymentViewController, - let parentVC = UIApplication.shared.delegate?.window??.rootViewController as? FlutterViewController + let parentVC = UIApplication.shared.delegate?.window?? + .rootViewController as? FlutterViewController else { return } - + if parentVC.children.contains(pvc) { pvc.willMove(toParent: nil) pvc.view.removeFromSuperview() pvc.removeFromParent() - print("Removed from parent!") + Logger.sdk.info("Removed from parent!") } else { - print("No parent relationship found, nothing to remove.") + Logger.sdk.debug("No parent relationship found, nothing to remove.") } } - + @MainActor init( messenger: FlutterBinaryMessenger, @@ -50,7 +52,7 @@ public class CheckoutPlatformView: NSObject, FlutterPlatformView { viewId: Int64, args: Any? ) { - + self.viewId = viewId self.channel = FlutterMethodChannel( name: checkoutChannelName, @@ -62,7 +64,7 @@ public class CheckoutPlatformView: NSObject, FlutterPlatformView { ) _view = CheckoutContainerView() super.init() - + self.channel.setMethodCallHandler { [weak self] call, _ in guard let self else { return } if call.method == _methodOnWidgetDetached { @@ -70,94 +72,129 @@ public class CheckoutPlatformView: NSObject, FlutterPlatformView { } } let jsonData: Data? = - if args != nil { - if let dictionaryArgs = args as? [String: Any] { - (dictionaryArgs["args"] as? String)?.data(using: .utf8) - } else { - nil - } - } else { nil } - + if args != nil { + if let dictionaryArgs = args as? [String: Any] { + (dictionaryArgs["args"] as? String)?.data(using: .utf8) + } else { + nil + } + } else { nil } + if jsonData != nil { do { let arguments: CheckoutArguments? = try? JSONDecoder().decode( CheckoutArguments.self, from: jsonData! ) - + self._view.onHeightChanged = { [weak self] (height: Int) in guard let self else { return } - debugPrint( - "CheckoutPlatformView, onHeightChanged: \(height)") + Logger.sdk.debug( + "CheckoutPlatformView, onHeightChanged: \(height)" + ) self.channel.invokeMethod( - methodCheckoutHeight, arguments: height) + methodCheckoutHeight, + arguments: height + ) } - + if arguments != nil { createNativeView(arguments: arguments!) } } catch { - debugPrint("Unexpected error: \(error).") + Logger.sdk.warning("Unexpected error: \(error).") } } } - + public func view() -> UIView { return _view } - + fileprivate func showSdkError() { - if let parentVC = UIApplication.shared.delegate?.window??.rootViewController as? FlutterViewController { - let title = NSLocalizedString("failed", bundle: Bundle.module, comment: "title of the dialog") - let message = NSLocalizedString("failed_start_payment", bundle: Bundle.module, comment: "messafe of the dialog") - let ok = NSLocalizedString("ok", bundle: Bundle.module, comment: "button label") - - - let alert = UIAlertController(title: title, message: message, preferredStyle: .alert) - alert.addAction(UIAlertAction(title: ok, style: .default, handler: { action in - switch action.style{ - case .default: - print("default") - - case .cancel: - print("cancel") - - case .destructive: - print("destructive") - - } - })) + if let parentVC = UIApplication.shared.delegate?.window?? + .rootViewController as? FlutterViewController + { + let title = NSLocalizedString( + "failed", + bundle: Bundle.module, + comment: "title of the dialog" + ) + let message = NSLocalizedString( + "failed_start_payment", + bundle: Bundle.module, + comment: "messafe of the dialog" + ) + let ok = NSLocalizedString( + "ok", + bundle: Bundle.module, + comment: "button label" + ) + + let alert = UIAlertController( + title: title, + message: message, + preferredStyle: .alert + ) + alert.addAction( + UIAlertAction( + title: ok, + style: .default, + handler: { action in + switch action.style { + case .default: + Logger.sdk.info("default") + + case .cancel: + Logger.sdk.info("cancel") + + case .destructive: + Logger.sdk.info("destructive") + + } + } + ) + ) parentVC.present(alert, animated: true, completion: nil) } } - + @MainActor func createNativeView(arguments: CheckoutArguments) { - debugPrint("createNativeView") + Logger.sdk.info("createNativeView") let theme = getCheckoutTheme( - arguments.theme, showPaymentDetails: arguments.showPaymentDetails) + arguments.theme, + showPaymentDetails: arguments.showPaymentDetails + ) let formsOfPayment = - arguments.formsOfPayment?.map( - { code in FormOfPayment(rawValue: code) - }).compactMap { $0 } ?? [] - - debugPrint("formsOfPayment") - - let paymentOptionsDisplaySettings:ottu_checkout_sdk.PaymentOptionsDisplaySettings = - if arguments.paymentOptionsDisplaySettings.mode == "list" { - ottu_checkout_sdk.PaymentOptionsDisplaySettings( - mode: .list, - visibleItemsCount:UInt(arguments.paymentOptionsDisplaySettings.visibleItemsCount), - defaultSelectedPgCode:arguments.paymentOptionsDisplaySettings.defaultSelectedPgCode, - ) - } else { - ottu_checkout_sdk.PaymentOptionsDisplaySettings( - mode: .bottomSheet, - defaultSelectedPgCode: arguments.paymentOptionsDisplaySettings.defaultSelectedPgCode, - ) - } - - + arguments.formsOfPayment?.map( + { code in FormOfPayment(rawValue: code) + }).compactMap { $0 } ?? [] + + Logger.sdk.info("formsOfPayment") + + let paymentOptionsDisplaySettings: + ottu_checkout_sdk.PaymentOptionsDisplaySettings = + if arguments.paymentOptionsDisplaySettings.mode == "list" { + ottu_checkout_sdk.PaymentOptionsDisplaySettings( + mode: .list, + visibleItemsCount: UInt( + arguments.paymentOptionsDisplaySettings + .visibleItemsCount + ), + defaultSelectedPgCode: arguments + .paymentOptionsDisplaySettings + .defaultSelectedPgCode, + ) + } else { + ottu_checkout_sdk.PaymentOptionsDisplaySettings( + mode: .bottomSheet, + defaultSelectedPgCode: arguments + .paymentOptionsDisplaySettings + .defaultSelectedPgCode, + ) + } + var apiTransactionDetails: TransactionDetailsResponse? if let transactionDetails: String? = arguments.apiTransactionDetails { if let td = transactionDetails { @@ -166,194 +203,223 @@ public class CheckoutPlatformView: NSObject, FlutterPlatformView { } do { let transactionDetails: TransactionDetails? = try - apiTransactionDetails?.transactionDetails - debugPrint("setupPreload") + apiTransactionDetails?.transactionDetails + Logger.sdk.info("setupPreload") self.checkout = try Checkout( formsOfPayments: formsOfPayment, theme: theme, - displaySettings:paymentOptionsDisplaySettings, + displaySettings: paymentOptionsDisplaySettings, sessionId: arguments.sessionId, merchantId: arguments.merchantId, apiKey: arguments.apiKey, setupPreload: transactionDetails, delegate: self, verifyPayment: verifyPaymentCallback, + payButtonText: arguments.payButtonText?.toPayButtonText() ) if let cht = checkout { self.paymentViewController = cht.paymentViewController() tryAttachController() } } catch { - debugPrint(error) + Logger.sdk.warning("Unable to get TransactionDetails") showSdkError() } } - + func tryAttachController() { guard let pvc = self.paymentViewController else { return } - - if let parentVC = UIApplication.shared.delegate?.window??.rootViewController as? FlutterViewController { + + if let parentVC = UIApplication.shared.delegate?.window?? + .rootViewController as? FlutterViewController + { parentVC.addChild(pvc) _view.addCheckoutView(pvc.view) pvc.didMove(toParent: parentVC) - print("Added to parent!") + Logger.sdk.info("Added to parent!") } else { - print("Waiting for parent...") + Logger.sdk.info("Waiting for parent...") DispatchQueue.main.asyncAfter(deadline: .now() + 0.05) { self.tryAttachController() } } } - + private func getApiTransactionDetails(_ transactionDetails: String) throws - -> TransactionDetailsResponse? + -> TransactionDetailsResponse? { if let jsonData = transactionDetails.data(using: .utf8) { let decoder = JSONDecoder() do { return try decoder.decode( - TransactionDetailsResponse.self, from: jsonData) + TransactionDetailsResponse.self, + from: jsonData + ) } catch let DecodingError.keyNotFound(key, context) { - debugPrint( - "Decoding error (keyNotFound): \(key) not found in \(context.debugDescription)" + Logger.sdk.debug( + "Decoding error (keyNotFound): \(key.stringValue) not found in \(context.debugDescription)" ) - debugPrint("Coding path: \(context.codingPath)") + Logger.sdk.debug("Coding path: \(context.codingPath)") } catch let DecodingError.dataCorrupted(context) { - debugPrint( + Logger.sdk.debug( "Decoding error (dataCorrupted): data corrupted in \(context.debugDescription)" ) - debugPrint("Coding path: \(context.codingPath)") + Logger.sdk.debug("Coding path: \(context.codingPath)") } catch let DecodingError.typeMismatch(type, context) { - debugPrint( + Logger.sdk.debug( "Decoding error (typeMismatch): type mismatch of \(type) in \(context.debugDescription)" ) - debugPrint("Coding path: \(context.codingPath)") + Logger.sdk.debug("Coding path: \(context.codingPath)") } catch let DecodingError.valueNotFound(type, context) { - debugPrint( + Logger.sdk.debug( "Decoding error (valueNotFound): value not found for \(type) in \(context.debugDescription)" ) - debugPrint("Coding path: \(context.codingPath)") + Logger.sdk.debug("Coding path: \(context.codingPath)") } - + return nil } - + return nil } - - private func getCheckoutTheme(_ theme: CustomerTheme?, showPaymentDetails: Bool) -> CheckoutTheme { + + private func getCheckoutTheme( + _ theme: CustomerTheme?, + showPaymentDetails: Bool + ) -> CheckoutTheme { let cht = CheckoutTheme() cht.showPaymentDetails = showPaymentDetails - + if theme == nil { return cht } - + if let color = theme?.sdkBackgroundColor?.toUIColors() { if let cc = color.color { cht.backgroundColor = cc } } - + if let color = theme?.modalBackgroundColor?.toUIColors() { if let cc = color.color { cht.backgroundColorModal = cc } } - - if let color = theme?.selectPaymentMethodHeaderBackgroundColor?.toUIColors() { + + if let color = theme?.selectPaymentMethodHeaderBackgroundColor? + .toUIColors() + { if let cc = color.color { cht.selectPaymentMethodTitleBackgroundColor = cc } } - - if let comp = theme?.mainTitleText?.toLabelComponent(ofSize: 20, weight: .semibold) { + + if let comp = theme?.mainTitleText?.toLabelComponent( + ofSize: 20, + weight: .semibold + ) { cht.mainTitle = comp } - - if let comp = theme?.selectPaymentMethodHeaderText?.toLabelComponent(ofSize: 20, weight: .semibold) { + + if let comp = theme?.selectPaymentMethodHeaderText?.toLabelComponent( + ofSize: 20, + weight: .semibold + ) { cht.selectPaymentMethodTitleLabel = comp } - - if let comp = theme?.titleText?.toLabelComponent(ofSize: 17, weight: .semibold) { + + if let comp = theme?.titleText?.toLabelComponent( + ofSize: 17, + weight: .semibold + ) { cht.title = comp } - + if let comp = theme?.subtitleText?.toLabelComponent(ofSize: 15) { cht.subtitle = comp } - + if let comp = theme?.feesTitleText?.toLabelComponent(ofSize: 17) { cht.feesTitle = comp } - + if let comp = theme?.feesSubtitleText?.toLabelComponent(ofSize: 15) { cht.feesSubtitle = comp } - - if let comp = theme?.dataLabelText?.toLabelComponent(ofSize: 14, weight: .semibold) { + + if let comp = theme?.dataLabelText?.toLabelComponent( + ofSize: 14, + weight: .semibold + ) { cht.dataLabel = comp } - - if let comp = theme?.dataValueText?.toLabelComponent(ofSize: 16, weight: .semibold) { + + if let comp = theme?.dataValueText?.toLabelComponent( + ofSize: 16, + weight: .semibold + ) { cht.dataValue = comp } - - if let comp = theme?.errorMessageText?.toLabelComponent(ofSize: 13, weight: .semibold) { + + if let comp = theme?.errorMessageText?.toLabelComponent( + ofSize: 13, + weight: .semibold + ) { cht.errorMessage = comp } - + if let color = theme?.savePhoneNumberIconColor?.toUIColors() { if let cc = color.color { //cht.savePhoneNumberIconColor = cc } } - + if let color = theme?.selectorIconColor?.toUIColors() { if let cc = color.color { //cht.selectorIconColor = cc } } - + if let color = theme?.paymentItemBackgroundColor?.toUIColors() { if let cc = color.color { cht.paymentItemBackgroundColor = cc } } - + if let switchColor = theme?.switchControl?.toCheckoutSwitch() { cht.switchOnTintColor = switchColor } - + if let button = theme?.button?.toCheckoutButton() { cht.button = button } - + if let selectorButton = theme?.selectorButton?.toCheckoutButton() { cht.selectorButton = selectorButton } - + if let iMargins = theme?.margins?.toMargins() { cht.margins = iMargins } - + if let iconColor = theme?.selectorIconColor?.toUIColors() { if let cc = iconColor.color { cht.iconColor = cc } } - - if let uiMode = theme?.uiMode{ - cht.uiMode = switch(uiMode){ - case "light": .LIGHT - case "dark": .DARK - default: .SYSTEM - } + + if let uiMode = theme?.uiMode { + cht.uiMode = + switch uiMode { + case "light": .LIGHT + case "dark": .DARK + default: .SYSTEM + } } - + return cht } - + private func verifyPaymentCallback(_ payload: String?) async -> CardVerificationResult { @@ -376,7 +442,7 @@ public class CheckoutPlatformView: NSObject, FlutterPlatformView { ) } else if let res = result { if let nsObjectValue = res as? NSObject { - if (nsObjectValue == FlutterMethodNotImplemented) + if nsObjectValue == FlutterMethodNotImplemented { continuation.resume( returning: @@ -417,7 +483,7 @@ public class CheckoutPlatformView: NSObject, FlutterPlatformView { extension CheckoutPlatformView: OttuDelegate { public func errorCallback(_ data: [String: Any]?) { - debugPrint("errorCallback\n") + Logger.sdk.debug("errorCallback\n") DispatchQueue.main .async { if let message = data?.description { @@ -436,15 +502,18 @@ extension CheckoutPlatformView: OttuDelegate { self._view.layoutIfNeeded() } } - + public func cancelCallback(_ data: [String: Any]?) { - debugPrint("cancelCallback\n") + Logger.sdk.debug("cancelCallback\n") DispatchQueue.main .async { if let message = data?.description { - self.channel.invokeMethod(_methodPaymentCancelResult, arguments: message) + self.channel.invokeMethod( + _methodPaymentCancelResult, + arguments: message + ) } - + self.paymentViewController?.view.setNeedsLayout() self.paymentViewController?.view.layoutIfNeeded() self._view.heightHandlerView.setNeedsLayout() @@ -453,14 +522,17 @@ extension CheckoutPlatformView: OttuDelegate { self._view.layoutIfNeeded() } } - + public func successCallback(_ data: [String: Any]?) { - debugPrint("successCallback\n") + Logger.sdk.debug("successCallback") DispatchQueue.main.async { if let message = data?.description { - self.channel.invokeMethod(_methodPaymentSuccessResult, arguments: message) + self.channel.invokeMethod( + _methodPaymentSuccessResult, + arguments: message + ) } - + //self.paymentViewController?.view.isHidden = true self.paymentViewController?.view.setNeedsLayout() self.paymentViewController?.view.layoutIfNeeded() @@ -474,92 +546,103 @@ extension CheckoutPlatformView: OttuDelegate { private class CheckoutContainerView: UIView { let heightHandlerView: CheckoutHeightHandlerView - + override init(frame: CGRect) { - + heightHandlerView = CheckoutHeightHandlerView() super.init(frame: frame) } - + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } - + public var onHeightChanged: ((Int) -> Void)? - + func addCheckoutView(_ checkoutView: UIView) { self.heightHandlerView.onHeightChanged = self.onHeightChanged self.heightHandlerView.translatesAutoresizingMaskIntoConstraints = false checkoutView.translatesAutoresizingMaskIntoConstraints = false checkoutView.accessibilityIdentifier = "CheckoutView" - + addSubview(heightHandlerView) addSubview(checkoutView) heightHandlerView.addSubview(checkoutView) - + NSLayoutConstraint.activate([ heightHandlerView.leftAnchor.constraint( - equalTo: self.leftAnchor), + equalTo: self.leftAnchor + ), heightHandlerView.rightAnchor.constraint( - equalTo: self.rightAnchor), + equalTo: self.rightAnchor + ), heightHandlerView.topAnchor.constraint(equalTo: self.topAnchor), //checkoutHeightHandler.bottomAnchor.constraint(equalTo: self.bottomAnchor), - + checkoutView.leftAnchor.constraint( - equalTo: heightHandlerView.leftAnchor), + equalTo: heightHandlerView.leftAnchor + ), checkoutView.rightAnchor.constraint( - equalTo: heightHandlerView.rightAnchor), + equalTo: heightHandlerView.rightAnchor + ), checkoutView.topAnchor.constraint( - equalTo: heightHandlerView.topAnchor), + equalTo: heightHandlerView.topAnchor + ), checkoutView.bottomAnchor.constraint( - equalTo: heightHandlerView.bottomAnchor), + equalTo: heightHandlerView.bottomAnchor + ), ]) } } private class CheckoutHeightHandlerView: UIView { - + override init(frame: CGRect) { super.init(frame: frame) } - + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } - + public var onHeightChanged: ((Int) -> Void)? - + override open func layoutSubviews() { super.layoutSubviews() self.setNeedsDisplay() - debugPrint("--------------------------\n") - debugPrint("layoutSubviews, parent") + Logger.sdk.debug("--------------------------\n") + Logger.sdk.debug("layoutSubviews, parent") self.subviews.forEach { let subview = $0 - debugPrint("layoutSubviews, child") - if(subview.accessibilityIdentifier == "CheckoutView" ) { - if(subview.isHidden == false){ + Logger.sdk.debug("layoutSubviews, child") + if subview.accessibilityIdentifier == "CheckoutView" { + if subview.isHidden == false { NSLayoutConstraint.activate([ subview.leftAnchor.constraint( - equalTo: self.leftAnchor), + equalTo: self.leftAnchor + ), subview.rightAnchor.constraint( - equalTo: self.rightAnchor), + equalTo: self.rightAnchor + ), subview.topAnchor.constraint( - equalTo: self.topAnchor), + equalTo: self.topAnchor + ), subview.bottomAnchor.constraint( - equalTo: self.bottomAnchor), + equalTo: self.bottomAnchor + ), ]) } else { subview.constraints.forEach { const in const.isActive = false - debugPrint("layoutSubviews, child constraint: \(const.description)\n\(const.isActive)") + Logger.sdk.debug( + "layoutSubviews, child constraint: \(const.description)\n\(const.isActive)" + ) } } } - debugPrint("\n") } - - debugPrint("layoutSubviews, bounds") + + Logger.sdk.debug("layoutSubviews, bounds") onHeightChanged?( Int(self.bounds.height) ) diff --git a/ios/ottu_flutter_checkout/Sources/ottu_flutter_checkout/Logger.swift b/ios/ottu_flutter_checkout/Sources/ottu_flutter_checkout/Logger.swift new file mode 100644 index 0000000..846c114 --- /dev/null +++ b/ios/ottu_flutter_checkout/Sources/ottu_flutter_checkout/Logger.swift @@ -0,0 +1,13 @@ +// +// Logger.swift +// ottu_checkout_sdk +// +// Created by Ottu on 04.02.2026. +// + +import OSLog + +internal extension Logger { + private static var subsystem = Bundle.main.bundleIdentifier! + public static let sdk = Logger(subsystem: subsystem, category: "flutter-sdk") +} diff --git a/ios/ottu_flutter_checkout/Sources/ottu_flutter_checkout/PayButtonText.swift b/ios/ottu_flutter_checkout/Sources/ottu_flutter_checkout/PayButtonText.swift new file mode 100644 index 0000000..b6cc373 --- /dev/null +++ b/ios/ottu_flutter_checkout/Sources/ottu_flutter_checkout/PayButtonText.swift @@ -0,0 +1,10 @@ +// +// PayButtonText.swift +// ottu_flutter_checkout +// +// Created by Ottu on 10.02.2026. +// +internal struct PayButtonText: Decodable { + let en: String + let ar: String +} diff --git a/ios/ottu_flutter_checkout/Sources/ottu_flutter_checkout/PaymentOptionsDisplaySettings.swift b/ios/ottu_flutter_checkout/Sources/ottu_flutter_checkout/PaymentOptionsDisplaySettings.swift index bde8ce6..25ca08c 100644 --- a/ios/ottu_flutter_checkout/Sources/ottu_flutter_checkout/PaymentOptionsDisplaySettings.swift +++ b/ios/ottu_flutter_checkout/Sources/ottu_flutter_checkout/PaymentOptionsDisplaySettings.swift @@ -5,7 +5,7 @@ // Created by Vi on 03.11.2025. // -struct PaymentOptionsDisplaySettings:Decodable { +struct PaymentOptionsDisplaySettings: Decodable { let mode: String let defaultSelectedPgCode: String? let visibleItemsCount: Int diff --git a/ios/ottu_flutter_checkout/Sources/ottu_flutter_checkout/ext.swift b/ios/ottu_flutter_checkout/Sources/ottu_flutter_checkout/ext.swift index 6ed9c45..308739b 100644 --- a/ios/ottu_flutter_checkout/Sources/ottu_flutter_checkout/ext.swift +++ b/ios/ottu_flutter_checkout/Sources/ottu_flutter_checkout/ext.swift @@ -9,18 +9,20 @@ import SwiftUI import UIKit import ottu_checkout_sdk +internal typealias SDKPayButtonText = ottu_checkout_sdk.PayButtonText + extension ColorState { public func toUIColors() -> (color: UIColor?, disabledColor: UIColor?) { let ucNormal: UIColor? = - if self.color != nil { - UIColor(hexString: self.color!) - } else { nil } - + if self.color != nil { + UIColor(hexString: self.color!) + } else { nil } + let ucDisabled: UIColor? = - if self.colorDisabled != nil { - UIColor(hexString: self.colorDisabled!) - } else { nil } - + if self.colorDisabled != nil { + UIColor(hexString: self.colorDisabled!) + } else { nil } + return (ucNormal, ucDisabled) } } @@ -28,30 +30,33 @@ extension ColorState { extension String { public func toUIColor() -> UIColor? { let ucNormal: UIColor? = - if self != nil { - UIColor(hexString: self) - } else { nil } + if self != nil { + UIColor(hexString: self) + } else { nil } return ucNormal } } extension TextStyle { - public func toLabelComponent(ofSize fontSize: CGFloat, weight: UIFont.Weight? = nil) -> LabelComponent? { + public func toLabelComponent( + ofSize fontSize: CGFloat, + weight: UIFont.Weight? = nil + ) -> LabelComponent? { let labelComponent = LabelComponent() if let color = self.textColor?.toUIColors() { if let cc = color.color { labelComponent.color = cc } } - - var size:CGFloat = fontSize + + var size: CGFloat = fontSize if let fs = self.fontSize { size = CGFloat(integerLiteral: fs) } - + if let ff = self.fontFamily { - if let customFont = UIFont(name: ff, size: size) { - labelComponent.font = customFont + if let customFont = UIFont(name: ff, size: size) { + labelComponent.font = customFont } else { if let fw = weight { labelComponent.font = .systemFont(ofSize: size, weight: fw) @@ -66,7 +71,7 @@ extension TextStyle { labelComponent.font = .systemFont(ofSize: size) } } - + return labelComponent } } @@ -74,7 +79,8 @@ extension TextStyle { extension UIColor { convenience init(hexString: String) { let hex = hexString.trimmingCharacters( - in: CharacterSet.alphanumerics.inverted) + in: CharacterSet.alphanumerics.inverted + ) var int = UInt64() Scanner(string: hex).scanHexInt64(&int) let a: UInt64 @@ -96,8 +102,11 @@ extension UIColor { (a, r, g, b) = (255, 0, 0, 0) } self.init( - red: CGFloat(r) / 255, green: CGFloat(g) / 255, - blue: CGFloat(b) / 255, alpha: CGFloat(a) / 255) + red: CGFloat(r) / 255, + green: CGFloat(g) / 255, + blue: CGFloat(b) / 255, + alpha: CGFloat(a) / 255 + ) } } @@ -141,24 +150,24 @@ extension TextFieldStyle { let tfc = TextFieldComponent() let label = LabelComponent() let text = LabelComponent() - + let textStyleColor = self.text?.textColor - + if let textColors = textStyleColor?.toUIColors() { if let color = textColors.color { label.color = color text.color = color } } - + //primaryColor = this.primaryColor?.toCheckoutColor(), //focusedColor = this.focusedColor?.toCheckoutColor(), //text = this.text?.toCheckoutText(), //error = this.error?.toCheckoutText() - + tfc.label = label tfc.text = text - + return tfc } } @@ -177,38 +186,38 @@ extension Margins { extension ButtonCmt { public func toCheckoutButton() -> ButtonComponent { let bc = ButtonComponent() - + let textColors = self.textColor?.toUIColors() let buttonColor = self.rippleColor?.color?.toUIColor() let buttonDisabledColor = self.rippleColor?.colorDisabled?.toUIColor() let fontType = self.fontType - + if let btnC = buttonColor { bc.enabledBackgroundColor = btnC } - + if let btnDC = buttonDisabledColor { bc.disabledBackgroundColor = btnDC } - + if let textColorNormal = textColors?.color { bc.enabledTitleColor = textColorNormal } if let textColorDisabled = textColors?.disabledColor { bc.disabledTitleColor = textColorDisabled } - + return bc } } extension SwitchComponent { public func toCheckoutSwitch() -> UIColor? { - + return if self.checkedThumbTintColor != nil { UIColor(hexString: self.checkedThumbTintColor!) } else { nil } - + // CheckoutTheme.Switch( // checkedThumbTintColor = this.checkedThumbTintColor?.toColor(), // uncheckedThumbTintColor = this.uncheckedThumbTintColor?.toColor(), @@ -221,3 +230,11 @@ extension SwitchComponent { // } } + +extension PayButtonText { + internal func toPayButtonText() -> SDKPayButtonText? { + return if self != nil { + SDKPayButtonText(en: self.en, ar: self.ar) + } else { nil } + } +} diff --git a/lib/ottu_flutter_checkout.dart b/lib/ottu_flutter_checkout.dart index 9f9b05e..1f27467 100644 --- a/lib/ottu_flutter_checkout.dart +++ b/lib/ottu_flutter_checkout.dart @@ -4,4 +4,5 @@ export 'src/checkout_theme.dart'; export 'src/payment_options_display_mode.dart'; export 'src/payment_options_display_settings.dart'; export 'src/forms_of_payment.dart'; -export 'src/card_verification_result.dart'; \ No newline at end of file +export 'src/card_verification_result.dart'; +export 'src/pay_button_text.dart'; \ No newline at end of file diff --git a/lib/src/checkout_arguments.dart b/lib/src/checkout_arguments.dart index 7dc84f9..5ef0ad8 100644 --- a/lib/src/checkout_arguments.dart +++ b/lib/src/checkout_arguments.dart @@ -1,7 +1,5 @@ import 'package:json_annotation/json_annotation.dart'; import 'package:ottu_flutter_checkout/ottu_flutter_checkout.dart'; -import 'package:ottu_flutter_checkout/src/forms_of_payment.dart'; -import 'package:ottu_flutter_checkout/src/payment_options_display_settings.dart'; part 'checkout_arguments.g.dart'; @@ -17,17 +15,20 @@ final class CheckoutArguments { String? setupPreload; final List? formsOfPayment; final CheckoutTheme? theme; + final PayButtonText? payButtonText; - CheckoutArguments( - {required this.merchantId, - required this.apiKey, - required this.sessionId, - required this.amount, - required this.showPaymentDetails, - required this.paymentOptionsDisplaySettings, - this.setupPreload, - this.formsOfPayment, - this.theme}); + CheckoutArguments({ + required this.merchantId, + required this.apiKey, + required this.sessionId, + required this.amount, + required this.showPaymentDetails, + required this.paymentOptionsDisplaySettings, + this.setupPreload, + this.formsOfPayment, + this.theme, + this.payButtonText, + }); factory CheckoutArguments.fromJson(Map json) => _$CheckoutArgumentsFromJson(json); diff --git a/lib/src/checkout_arguments.g.dart b/lib/src/checkout_arguments.g.dart index 001e24e..35ebe20 100644 --- a/lib/src/checkout_arguments.g.dart +++ b/lib/src/checkout_arguments.g.dart @@ -22,6 +22,10 @@ CheckoutArguments _$CheckoutArgumentsFromJson(Map json) => theme: json['theme'] == null ? null : CheckoutTheme.fromJson(json['theme'] as Map), + payButtonText: json['payButtonText'] == null + ? null + : PayButtonText.fromJson( + json['payButtonText'] as Map), ); Map _$CheckoutArgumentsToJson(CheckoutArguments instance) => @@ -37,6 +41,7 @@ Map _$CheckoutArgumentsToJson(CheckoutArguments instance) => ?.map((e) => _$FormsOfPaymentEnumMap[e]!) .toList(), 'theme': instance.theme, + 'payButtonText': instance.payButtonText, }; const _$FormsOfPaymentEnumMap = { diff --git a/lib/src/pay_button_text.dart b/lib/src/pay_button_text.dart new file mode 100644 index 0000000..ad028d1 --- /dev/null +++ b/lib/src/pay_button_text.dart @@ -0,0 +1,18 @@ +import 'package:json_annotation/json_annotation.dart'; + +part 'pay_button_text.g.dart'; + +/** + * Data class holds localization for the PayButton + */ +@JsonSerializable() +final class PayButtonText { + final String en; + final String ar; + + PayButtonText({required this.en, required this.ar}); + + factory PayButtonText.fromJson(Map json) => _$PayButtonTextFromJson(json); + + Map toJson() => _$PayButtonTextToJson(this); +} diff --git a/lib/src/pay_button_text.g.dart b/lib/src/pay_button_text.g.dart new file mode 100644 index 0000000..bc0a5ae --- /dev/null +++ b/lib/src/pay_button_text.g.dart @@ -0,0 +1,19 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'pay_button_text.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +PayButtonText _$PayButtonTextFromJson(Map json) => + PayButtonText( + en: json['en'] as String, + ar: json['ar'] as String, + ); + +Map _$PayButtonTextToJson(PayButtonText instance) => + { + 'en': instance.en, + 'ar': instance.ar, + };