diff --git a/CHANGELOG.md b/CHANGELOG.md index 7f1437e4..26845db8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ### New +- Added bin lookup callbacks for Drop-In. - Set minimum SDK version to Flutter 3.16/Dart 3.2 - Android Components/Drop-in version: [5.9.0](https://docs.adyen.com/online-payments/release-notes/?title%5B0%5D=Android+Components%2FDrop-in#releaseNote=2025-01-17-android-componentsdrop-in-5.9.0). diff --git a/android/src/main/kotlin/com/adyen/checkout/flutter/dropIn/DropInPlatformApi.kt b/android/src/main/kotlin/com/adyen/checkout/flutter/dropIn/DropInPlatformApi.kt index 6325af8c..fe94a8ac 100644 --- a/android/src/main/kotlin/com/adyen/checkout/flutter/dropIn/DropInPlatformApi.kt +++ b/android/src/main/kotlin/com/adyen/checkout/flutter/dropIn/DropInPlatformApi.kt @@ -45,17 +45,24 @@ import com.adyen.checkout.flutter.utils.ConfigurationMapper.mapToOrderResponseMo import com.adyen.checkout.sessions.core.CheckoutSession import com.adyen.checkout.sessions.core.SessionSetupResponse import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import org.json.JSONObject -class DropInPlatformApi( +internal class DropInPlatformApi( private val checkoutFlutter: CheckoutFlutterInterface, private val activity: FragmentActivity, private val sessionHolder: SessionHolder, ) : DropInPlatformInterface { lateinit var dropInSessionLauncher: ActivityResultLauncher lateinit var dropInAdvancedFlowLauncher: ActivityResultLauncher + private var dropInPlatformMessengerJob: Job? = null + + companion object { + val dropInMessageFlow = MutableSharedFlow() + } override fun showDropInSession(dropInConfigurationDTO: DropInConfigurationDTO) { setStoredPaymentMethodDeletionObserver() @@ -66,6 +73,11 @@ class DropInPlatformApi( dropInConfigurationDTO.environment.mapToEnvironment(), dropInConfigurationDTO.clientKey ) + + dropInPlatformMessengerJob?.cancel() + dropInPlatformMessengerJob = + activity.lifecycleScope.launch { dropInMessageFlow.collect { event -> checkoutFlutter.send(event) {} } } + DropIn.startPayment( activity.applicationContext, dropInSessionLauncher, @@ -84,6 +96,11 @@ class DropInPlatformApi( setBalanceCheckPlatformMessengerObserver() setOrderRequestPlatformMessengerObserver() setOrderCancelPlatformMessengerObserver() + + dropInPlatformMessengerJob?.cancel() + dropInPlatformMessengerJob = + activity.lifecycleScope.launch { dropInMessageFlow.collect { event -> checkoutFlutter.send(event) {} } } + activity.lifecycleScope.launch(Dispatchers.IO) { val paymentMethodsApiResponse = PaymentMethodsApiResponse.SERIALIZER.deserialize( @@ -138,6 +155,8 @@ class DropInPlatformApi( } override fun cleanUpDropIn() { + dropInPlatformMessengerJob?.cancel() + DropInServiceResultMessenger.instance().removeObservers(activity) DropInAdditionalDetailsPlatformMessenger.instance().removeObservers(activity) DropInPaymentMethodDeletionPlatformMessenger.instance().removeObservers(activity) diff --git a/android/src/main/kotlin/com/adyen/checkout/flutter/dropIn/advanced/AdvancedDropInService.kt b/android/src/main/kotlin/com/adyen/checkout/flutter/dropIn/advanced/AdvancedDropInService.kt index ac44190e..d572fe85 100644 --- a/android/src/main/kotlin/com/adyen/checkout/flutter/dropIn/advanced/AdvancedDropInService.kt +++ b/android/src/main/kotlin/com/adyen/checkout/flutter/dropIn/advanced/AdvancedDropInService.kt @@ -5,6 +5,8 @@ import android.os.IBinder import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.ServiceLifecycleDispatcher +import androidx.lifecycle.lifecycleScope +import com.adyen.checkout.card.BinLookupData import com.adyen.checkout.components.core.ActionComponentData import com.adyen.checkout.components.core.BalanceResult import com.adyen.checkout.components.core.Order @@ -20,15 +22,20 @@ import com.adyen.checkout.dropin.DropInServiceResult import com.adyen.checkout.dropin.ErrorDialog import com.adyen.checkout.dropin.OrderDropInServiceResult import com.adyen.checkout.dropin.RecurringDropInServiceResult +import com.adyen.checkout.flutter.dropIn.DropInPlatformApi import com.adyen.checkout.flutter.dropIn.model.DropInStoredPaymentMethodDeletionModel import com.adyen.checkout.flutter.dropIn.model.DropInType +import com.adyen.checkout.flutter.generated.BinLookupDataDTO import com.adyen.checkout.flutter.generated.DeletedStoredPaymentMethodResultDTO import com.adyen.checkout.flutter.generated.ErrorDTO import com.adyen.checkout.flutter.generated.OrderCancelResultDTO import com.adyen.checkout.flutter.generated.PaymentEventDTO import com.adyen.checkout.flutter.generated.PaymentEventType +import com.adyen.checkout.flutter.generated.CheckoutEvent +import com.adyen.checkout.flutter.generated.CheckoutEventType import com.adyen.checkout.flutter.utils.Constants import com.adyen.checkout.googlepay.GooglePayComponentState +import kotlinx.coroutines.launch import org.json.JSONObject class AdvancedDropInService : DropInService(), LifecycleOwner { @@ -98,6 +105,29 @@ class AdvancedDropInService : DropInService(), LifecycleOwner { } } + override fun onBinLookup(data: List) { + lifecycleScope.launch { + val binLookupDataDtoList = data.map { BinLookupDataDTO(it.brand) } + val checkoutEvent = + CheckoutEvent( + CheckoutEventType.BIN_LOOKUP, + binLookupDataDtoList + ) + DropInPlatformApi.dropInMessageFlow.emit(checkoutEvent) + } + } + + override fun onBinValue(binValue: String) { + lifecycleScope.launch { + val checkoutEvent = + CheckoutEvent( + CheckoutEventType.BIN_VALUE, + binValue + ) + DropInPlatformApi.dropInMessageFlow.emit(checkoutEvent) + } + } + private fun onPaymentComponentState(state: PaymentComponentState<*>) { try { setAdvancedFlowDropInServiceObserver() diff --git a/android/src/main/kotlin/com/adyen/checkout/flutter/dropIn/session/SessionDropInService.kt b/android/src/main/kotlin/com/adyen/checkout/flutter/dropIn/session/SessionDropInService.kt index d6d2aabd..6b4e6134 100644 --- a/android/src/main/kotlin/com/adyen/checkout/flutter/dropIn/session/SessionDropInService.kt +++ b/android/src/main/kotlin/com/adyen/checkout/flutter/dropIn/session/SessionDropInService.kt @@ -5,15 +5,22 @@ import android.os.IBinder import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.ServiceLifecycleDispatcher +import androidx.lifecycle.lifecycleScope +import com.adyen.checkout.card.BinLookupData import com.adyen.checkout.components.core.StoredPaymentMethod import com.adyen.checkout.dropin.ErrorDialog import com.adyen.checkout.dropin.RecurringDropInServiceResult import com.adyen.checkout.dropin.SessionDropInService +import com.adyen.checkout.flutter.dropIn.DropInPlatformApi import com.adyen.checkout.flutter.dropIn.advanced.DropInPaymentMethodDeletionPlatformMessenger import com.adyen.checkout.flutter.dropIn.advanced.DropInPaymentMethodDeletionResultMessenger import com.adyen.checkout.flutter.dropIn.model.DropInStoredPaymentMethodDeletionModel import com.adyen.checkout.flutter.dropIn.model.DropInType +import com.adyen.checkout.flutter.generated.BinLookupDataDTO import com.adyen.checkout.flutter.generated.DeletedStoredPaymentMethodResultDTO +import com.adyen.checkout.flutter.generated.CheckoutEvent +import com.adyen.checkout.flutter.generated.CheckoutEventType +import kotlinx.coroutines.launch class SessionDropInService : SessionDropInService(), LifecycleOwner { private val dispatcher = ServiceLifecycleDispatcher(this) @@ -32,6 +39,25 @@ class SessionDropInService : SessionDropInService(), LifecycleOwner { } } + override fun onBinLookup(data: List) { + lifecycleScope.launch { + val binLookupDataDtoList = data.map { BinLookupDataDTO(it.brand) } + val checkoutEvent = + CheckoutEvent( + CheckoutEventType.BIN_LOOKUP, + binLookupDataDtoList + ) + DropInPlatformApi.dropInMessageFlow.emit(checkoutEvent) + } + } + + override fun onBinValue(binValue: String) { + lifecycleScope.launch { + val platformCommunicationModel = CheckoutEvent(CheckoutEventType.BIN_VALUE, binValue) + DropInPlatformApi.dropInMessageFlow.emit(platformCommunicationModel) + } + } + private fun setStoredPaymentMethodDeletionObserver() { DropInPaymentMethodDeletionResultMessenger.instance().removeObservers(this) DropInPaymentMethodDeletionResultMessenger.instance().observe(this) { message -> diff --git a/android/src/main/kotlin/com/adyen/checkout/flutter/generated/PlatformApi.kt b/android/src/main/kotlin/com/adyen/checkout/flutter/generated/PlatformApi.kt index 22621377..bd682a37 100644 --- a/android/src/main/kotlin/com/adyen/checkout/flutter/generated/PlatformApi.kt +++ b/android/src/main/kotlin/com/adyen/checkout/flutter/generated/PlatformApi.kt @@ -138,7 +138,9 @@ enum class CheckoutEventType(val raw: Int) { DELETE_STORED_PAYMENT_METHOD(3), BALANCE_CHECK(4), REQUEST_ORDER(5), - CANCEL_ORDER(6); + CANCEL_ORDER(6), + BIN_LOOKUP(7), + BIN_VALUE(8); companion object { fun ofRaw(raw: Int): CheckoutEventType? { @@ -1191,6 +1193,25 @@ data class OrderCancelResultDTO ( ) } } + +/** Generated class from Pigeon that represents data sent in messages. */ +data class BinLookupDataDTO ( + val brand: String + +) { + companion object { + @Suppress("LocalVariableName") + fun fromList(__pigeon_list: List): BinLookupDataDTO { + val brand = __pigeon_list[0] as String + return BinLookupDataDTO(brand) + } + } + fun toList(): List { + return listOf( + brand, + ) + } +} private object PlatformApiPigeonCodec : StandardMessageCodec() { override fun readValueOfType(type: Byte, buffer: ByteBuffer): Any? { return when (type) { @@ -1340,91 +1361,96 @@ private object PlatformApiPigeonCodec : StandardMessageCodec() { } } 158.toByte() -> { + return (readValue(buffer) as? List)?.let { + BinLookupDataDTO.fromList(it) + } + } + 159.toByte() -> { return (readValue(buffer) as Int?)?.let { Environment.ofRaw(it) } } - 159.toByte() -> { + 160.toByte() -> { return (readValue(buffer) as Int?)?.let { AddressMode.ofRaw(it) } } - 160.toByte() -> { + 161.toByte() -> { return (readValue(buffer) as Int?)?.let { CardAuthMethod.ofRaw(it) } } - 161.toByte() -> { + 162.toByte() -> { return (readValue(buffer) as Int?)?.let { TotalPriceStatus.ofRaw(it) } } - 162.toByte() -> { + 163.toByte() -> { return (readValue(buffer) as Int?)?.let { GooglePayEnvironment.ofRaw(it) } } - 163.toByte() -> { + 164.toByte() -> { return (readValue(buffer) as Int?)?.let { CashAppPayEnvironment.ofRaw(it) } } - 164.toByte() -> { + 165.toByte() -> { return (readValue(buffer) as Int?)?.let { PaymentResultEnum.ofRaw(it) } } - 165.toByte() -> { + 166.toByte() -> { return (readValue(buffer) as Int?)?.let { CheckoutEventType.ofRaw(it) } } - 166.toByte() -> { + 167.toByte() -> { return (readValue(buffer) as Int?)?.let { ComponentCommunicationType.ofRaw(it) } } - 167.toByte() -> { + 168.toByte() -> { return (readValue(buffer) as Int?)?.let { PaymentEventType.ofRaw(it) } } - 168.toByte() -> { + 169.toByte() -> { return (readValue(buffer) as Int?)?.let { FieldVisibility.ofRaw(it) } } - 169.toByte() -> { + 170.toByte() -> { return (readValue(buffer) as Int?)?.let { InstantPaymentType.ofRaw(it) } } - 170.toByte() -> { + 171.toByte() -> { return (readValue(buffer) as Int?)?.let { ApplePayShippingType.ofRaw(it) } } - 171.toByte() -> { + 172.toByte() -> { return (readValue(buffer) as Int?)?.let { ApplePayMerchantCapability.ofRaw(it) } } - 172.toByte() -> { + 173.toByte() -> { return (readValue(buffer) as Int?)?.let { ApplePaySummaryItemType.ofRaw(it) } } - 173.toByte() -> { + 174.toByte() -> { return (readValue(buffer) as Int?)?.let { CardNumberValidationResultDTO.ofRaw(it) } } - 174.toByte() -> { + 175.toByte() -> { return (readValue(buffer) as Int?)?.let { CardExpiryDateValidationResultDTO.ofRaw(it) } } - 175.toByte() -> { + 176.toByte() -> { return (readValue(buffer) as Int?)?.let { CardSecurityCodeValidationResultDTO.ofRaw(it) } @@ -1550,76 +1576,80 @@ private object PlatformApiPigeonCodec : StandardMessageCodec() { stream.write(157) writeValue(stream, value.toList()) } - is Environment -> { + is BinLookupDataDTO -> { stream.write(158) + writeValue(stream, value.toList()) + } + is Environment -> { + stream.write(159) writeValue(stream, value.raw) } is AddressMode -> { - stream.write(159) + stream.write(160) writeValue(stream, value.raw) } is CardAuthMethod -> { - stream.write(160) + stream.write(161) writeValue(stream, value.raw) } is TotalPriceStatus -> { - stream.write(161) + stream.write(162) writeValue(stream, value.raw) } is GooglePayEnvironment -> { - stream.write(162) + stream.write(163) writeValue(stream, value.raw) } is CashAppPayEnvironment -> { - stream.write(163) + stream.write(164) writeValue(stream, value.raw) } is PaymentResultEnum -> { - stream.write(164) + stream.write(165) writeValue(stream, value.raw) } is CheckoutEventType -> { - stream.write(165) + stream.write(166) writeValue(stream, value.raw) } is ComponentCommunicationType -> { - stream.write(166) + stream.write(167) writeValue(stream, value.raw) } is PaymentEventType -> { - stream.write(167) + stream.write(168) writeValue(stream, value.raw) } is FieldVisibility -> { - stream.write(168) + stream.write(169) writeValue(stream, value.raw) } is InstantPaymentType -> { - stream.write(169) + stream.write(170) writeValue(stream, value.raw) } is ApplePayShippingType -> { - stream.write(170) + stream.write(171) writeValue(stream, value.raw) } is ApplePayMerchantCapability -> { - stream.write(171) + stream.write(172) writeValue(stream, value.raw) } is ApplePaySummaryItemType -> { - stream.write(172) + stream.write(173) writeValue(stream, value.raw) } is CardNumberValidationResultDTO -> { - stream.write(173) + stream.write(174) writeValue(stream, value.raw) } is CardExpiryDateValidationResultDTO -> { - stream.write(174) + stream.write(175) writeValue(stream, value.raw) } is CardSecurityCodeValidationResultDTO -> { - stream.write(175) + stream.write(176) writeValue(stream, value.raw) } else -> super.writeValue(stream, value) @@ -2199,12 +2229,12 @@ class ComponentFlutterInterface(private val binaryMessenger: BinaryMessenger, pr PlatformApiPigeonCodec } } - fun _generateCodecForDTOs(cardComponentConfigurationDTOArg: CardComponentConfigurationDTO, sessionDTOArg: SessionDTO, callback: (Result) -> Unit) + fun _generateCodecForDTOs(cardComponentConfigurationDTOArg: CardComponentConfigurationDTO, sessionDTOArg: SessionDTO, binLookupDataDTOArg: BinLookupDataDTO, callback: (Result) -> Unit) { val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else "" val channelName = "dev.flutter.pigeon.adyen_checkout.ComponentFlutterInterface._generateCodecForDTOs$separatedMessageChannelSuffix" val channel = BasicMessageChannel(binaryMessenger, channelName, codec) - channel.send(listOf(cardComponentConfigurationDTOArg, sessionDTOArg)) { + channel.send(listOf(cardComponentConfigurationDTOArg, sessionDTOArg, binLookupDataDTOArg)) { if (it is List<*>) { if (it.size > 1) { callback(Result.failure(AdyenPigeonError(it[0] as String, it[1] as String, it[2] as String?))) diff --git a/android/src/main/kotlin/com/adyen/checkout/flutter/utils/Constants.kt b/android/src/main/kotlin/com/adyen/checkout/flutter/utils/Constants.kt index f890e44b..0b03b80a 100644 --- a/android/src/main/kotlin/com/adyen/checkout/flutter/utils/Constants.kt +++ b/android/src/main/kotlin/com/adyen/checkout/flutter/utils/Constants.kt @@ -5,7 +5,7 @@ class Constants { const val WRONG_FLUTTER_ACTIVITY_USAGE_ERROR_MESSAGE = "FlutterFragmentActivity not used. Your activity needs to inherit from FlutterFragmentActivity." const val UNKNOWN_PAYMENT_METHOD_TYPE_ERROR_MESSAGE = "Unknown payment method type." - + const val ADYEN_LOG_TAG = "ADYEN_CHECKOUT" const val GOOGLE_PAY_SESSION_COMPONENT_KEY = "GOOGLE_PAY_SESSION_COMPONENT" const val GOOGLE_PAY_ADVANCED_COMPONENT_KEY = "GOOGLE_PAY_ADVANCED_COMPONENT" const val INSTANT_SESSION_COMPONENT_KEY = "INSTANT_SESSION_COMPONENT" diff --git a/example/lib/screens/drop_in/drop_in_screen.dart b/example/lib/screens/drop_in/drop_in_screen.dart index f2408a32..60a18d46 100644 --- a/example/lib/screens/drop_in/drop_in_screen.dart +++ b/example/lib/screens/drop_in/drop_in_screen.dart @@ -96,7 +96,12 @@ class DropInScreen extends StatelessWidget { } Future _createDropInConfiguration() async { - const CardConfiguration cardsConfiguration = CardConfiguration(); + CardConfiguration cardsConfiguration = CardConfiguration( + cardCallbacks: CardCallbacks( + onBinLookup: _onBinLookup, + onBinValue: _onBinValue, + ), + ); ApplePayConfiguration applePayConfiguration = ApplePayConfiguration( merchantId: Config.merchantId, @@ -143,4 +148,14 @@ class DropInScreen extends StatelessWidget { return dropInConfiguration; } + + void _onBinLookup(List binLookupData) { + for (var element in binLookupData) { + debugPrint("Bin lookup data: brand:${element.brand}"); + } + } + + void _onBinValue(String binValue) { + debugPrint("Bin value: $binValue"); + } } diff --git a/ios/Classes/dropIn/DropInPlatformApi.swift b/ios/Classes/dropIn/DropInPlatformApi.swift index b2e540ad..2c1b0516 100644 --- a/ios/Classes/dropIn/DropInPlatformApi.swift +++ b/ios/Classes/dropIn/DropInPlatformApi.swift @@ -57,6 +57,7 @@ class DropInPlatformApi: DropInPlatformInterface { ) dropInComponent.delegate = sessionHolder.session dropInComponent.partialPaymentDelegate = sessionHolder.session + dropInComponent.cardComponentDelegate = self if dropInConfigurationDTO.isRemoveStoredPaymentMethodEnabled { dropInComponent.storedPaymentMethodsDelegate = dropInSessionStoredPaymentMethodsDelegate } @@ -97,6 +98,7 @@ class DropInPlatformApi: DropInPlatformInterface { dropInAdvancedFlowDelegate = DropInAdvancedFlowDelegate(checkoutFlutter: checkoutFlutter) dropInAdvancedFlowDelegate?.dropInInteractorDelegate = self dropInComponent.delegate = dropInAdvancedFlowDelegate + dropInComponent.cardComponentDelegate = self if dropInConfigurationDTO.isPartialPaymentSupported { dropInComponent.partialPaymentDelegate = self } @@ -373,3 +375,25 @@ extension DropInPlatformApi: PartialPaymentDelegate { } } + +extension DropInPlatformApi: CardComponentDelegate { + func didSubmit(lastFour: String, finalBIN: String, component: Adyen.CardComponent) {} + + func didChangeBIN(_ value: String, component: Adyen.CardComponent) { + let checkoutEvent = CheckoutEvent(type: CheckoutEventType.binValue, data: value) + checkoutFlutter.send(event: checkoutEvent, completion: { _ in }) + } + + func didChangeCardBrand(_ value: [Adyen.CardBrand]?, component: Adyen.CardComponent) { + guard let binLookupData = value else { + return + } + + let binLookupDataDtoList: [BinLookupDataDTO] = binLookupData.map { cardBrand in + BinLookupDataDTO(brand: cardBrand.type.rawValue) + } + + let checkoutEvent = CheckoutEvent(type: CheckoutEventType.binLookup, data: binLookupDataDtoList) + checkoutFlutter.send(event: checkoutEvent, completion: { _ in }) + } +} diff --git a/ios/Classes/generated/PlatformApi.swift b/ios/Classes/generated/PlatformApi.swift index 3f97d88e..50e7c38b 100644 --- a/ios/Classes/generated/PlatformApi.swift +++ b/ios/Classes/generated/PlatformApi.swift @@ -117,6 +117,8 @@ enum CheckoutEventType: Int { case balanceCheck = 4 case requestOrder = 5 case cancelOrder = 6 + case binLookup = 7 + case binValue = 8 } enum ComponentCommunicationType: Int { @@ -1256,6 +1258,26 @@ struct OrderCancelResultDTO { } } +/// Generated class from Pigeon that represents data sent in messages. +struct BinLookupDataDTO { + var brand: String + + // swift-format-ignore: AlwaysUseLowerCamelCase + static func fromList(_ __pigeon_list: [Any?]) -> BinLookupDataDTO? { + let brand = __pigeon_list[0] as! String + + return BinLookupDataDTO( + brand: brand + ) + } + + func toList() -> [Any?] { + [ + brand + ] + } +} + private class PlatformApiPigeonCodecReader: FlutterStandardReader { override func readValue(ofType type: UInt8) -> Any? { switch type { @@ -1318,125 +1340,127 @@ private class PlatformApiPigeonCodecReader: FlutterStandardReader { case 157: return OrderCancelResultDTO.fromList(self.readValue() as! [Any?]) case 158: + return BinLookupDataDTO.fromList(self.readValue() as! [Any?]) + case 159: var enumResult: Environment? = nil let enumResultAsInt: Int? = nilOrValue(self.readValue() as? Int) if let enumResultAsInt { enumResult = Environment(rawValue: enumResultAsInt) } return enumResult - case 159: + case 160: var enumResult: AddressMode? = nil let enumResultAsInt: Int? = nilOrValue(self.readValue() as? Int) if let enumResultAsInt { enumResult = AddressMode(rawValue: enumResultAsInt) } return enumResult - case 160: + case 161: var enumResult: CardAuthMethod? = nil let enumResultAsInt: Int? = nilOrValue(self.readValue() as? Int) if let enumResultAsInt { enumResult = CardAuthMethod(rawValue: enumResultAsInt) } return enumResult - case 161: + case 162: var enumResult: TotalPriceStatus? = nil let enumResultAsInt: Int? = nilOrValue(self.readValue() as? Int) if let enumResultAsInt { enumResult = TotalPriceStatus(rawValue: enumResultAsInt) } return enumResult - case 162: + case 163: var enumResult: GooglePayEnvironment? = nil let enumResultAsInt: Int? = nilOrValue(self.readValue() as? Int) if let enumResultAsInt { enumResult = GooglePayEnvironment(rawValue: enumResultAsInt) } return enumResult - case 163: + case 164: var enumResult: CashAppPayEnvironment? = nil let enumResultAsInt: Int? = nilOrValue(self.readValue() as? Int) if let enumResultAsInt { enumResult = CashAppPayEnvironment(rawValue: enumResultAsInt) } return enumResult - case 164: + case 165: var enumResult: PaymentResultEnum? = nil let enumResultAsInt: Int? = nilOrValue(self.readValue() as? Int) if let enumResultAsInt { enumResult = PaymentResultEnum(rawValue: enumResultAsInt) } return enumResult - case 165: + case 166: var enumResult: CheckoutEventType? = nil let enumResultAsInt: Int? = nilOrValue(self.readValue() as? Int) if let enumResultAsInt { enumResult = CheckoutEventType(rawValue: enumResultAsInt) } return enumResult - case 166: + case 167: var enumResult: ComponentCommunicationType? = nil let enumResultAsInt: Int? = nilOrValue(self.readValue() as? Int) if let enumResultAsInt { enumResult = ComponentCommunicationType(rawValue: enumResultAsInt) } return enumResult - case 167: + case 168: var enumResult: PaymentEventType? = nil let enumResultAsInt: Int? = nilOrValue(self.readValue() as? Int) if let enumResultAsInt { enumResult = PaymentEventType(rawValue: enumResultAsInt) } return enumResult - case 168: + case 169: var enumResult: FieldVisibility? = nil let enumResultAsInt: Int? = nilOrValue(self.readValue() as? Int) if let enumResultAsInt { enumResult = FieldVisibility(rawValue: enumResultAsInt) } return enumResult - case 169: + case 170: var enumResult: InstantPaymentType? = nil let enumResultAsInt: Int? = nilOrValue(self.readValue() as? Int) if let enumResultAsInt { enumResult = InstantPaymentType(rawValue: enumResultAsInt) } return enumResult - case 170: + case 171: var enumResult: ApplePayShippingType? = nil let enumResultAsInt: Int? = nilOrValue(self.readValue() as? Int) if let enumResultAsInt { enumResult = ApplePayShippingType(rawValue: enumResultAsInt) } return enumResult - case 171: + case 172: var enumResult: ApplePayMerchantCapability? = nil let enumResultAsInt: Int? = nilOrValue(self.readValue() as? Int) if let enumResultAsInt { enumResult = ApplePayMerchantCapability(rawValue: enumResultAsInt) } return enumResult - case 172: + case 173: var enumResult: ApplePaySummaryItemType? = nil let enumResultAsInt: Int? = nilOrValue(self.readValue() as? Int) if let enumResultAsInt { enumResult = ApplePaySummaryItemType(rawValue: enumResultAsInt) } return enumResult - case 173: + case 174: var enumResult: CardNumberValidationResultDTO? = nil let enumResultAsInt: Int? = nilOrValue(self.readValue() as? Int) if let enumResultAsInt { enumResult = CardNumberValidationResultDTO(rawValue: enumResultAsInt) } return enumResult - case 174: + case 175: var enumResult: CardExpiryDateValidationResultDTO? = nil let enumResultAsInt: Int? = nilOrValue(self.readValue() as? Int) if let enumResultAsInt { enumResult = CardExpiryDateValidationResultDTO(rawValue: enumResultAsInt) } return enumResult - case 175: + case 176: var enumResult: CardSecurityCodeValidationResultDTO? = nil let enumResultAsInt: Int? = nilOrValue(self.readValue() as? Int) if let enumResultAsInt { @@ -1538,59 +1562,62 @@ private class PlatformApiPigeonCodecWriter: FlutterStandardWriter { } else if let value = value as? OrderCancelResultDTO { super.writeByte(157) super.writeValue(value.toList()) - } else if let value = value as? Environment { + } else if let value = value as? BinLookupDataDTO { super.writeByte(158) + super.writeValue(value.toList()) + } else if let value = value as? Environment { + super.writeByte(159) super.writeValue(value.rawValue) } else if let value = value as? AddressMode { - super.writeByte(159) + super.writeByte(160) super.writeValue(value.rawValue) } else if let value = value as? CardAuthMethod { - super.writeByte(160) + super.writeByte(161) super.writeValue(value.rawValue) } else if let value = value as? TotalPriceStatus { - super.writeByte(161) + super.writeByte(162) super.writeValue(value.rawValue) } else if let value = value as? GooglePayEnvironment { - super.writeByte(162) + super.writeByte(163) super.writeValue(value.rawValue) } else if let value = value as? CashAppPayEnvironment { - super.writeByte(163) + super.writeByte(164) super.writeValue(value.rawValue) } else if let value = value as? PaymentResultEnum { - super.writeByte(164) + super.writeByte(165) super.writeValue(value.rawValue) } else if let value = value as? CheckoutEventType { - super.writeByte(165) + super.writeByte(166) super.writeValue(value.rawValue) } else if let value = value as? ComponentCommunicationType { - super.writeByte(166) + super.writeByte(167) super.writeValue(value.rawValue) } else if let value = value as? PaymentEventType { - super.writeByte(167) + super.writeByte(168) super.writeValue(value.rawValue) } else if let value = value as? FieldVisibility { - super.writeByte(168) + super.writeByte(169) super.writeValue(value.rawValue) } else if let value = value as? InstantPaymentType { - super.writeByte(169) + super.writeByte(170) super.writeValue(value.rawValue) } else if let value = value as? ApplePayShippingType { - super.writeByte(170) + super.writeByte(171) super.writeValue(value.rawValue) } else if let value = value as? ApplePayMerchantCapability { - super.writeByte(171) + super.writeByte(172) super.writeValue(value.rawValue) } else if let value = value as? ApplePaySummaryItemType { - super.writeByte(172) + super.writeByte(173) super.writeValue(value.rawValue) } else if let value = value as? CardNumberValidationResultDTO { - super.writeByte(173) + super.writeByte(174) super.writeValue(value.rawValue) } else if let value = value as? CardExpiryDateValidationResultDTO { - super.writeByte(174) + super.writeByte(175) super.writeValue(value.rawValue) } else if let value = value as? CardSecurityCodeValidationResultDTO { - super.writeByte(175) + super.writeByte(176) super.writeValue(value.rawValue) } else { super.writeValue(value) @@ -2110,7 +2137,7 @@ class ComponentPlatformInterfaceSetup { /// Generated protocol from Pigeon that represents Flutter messages that can be called from Swift. protocol ComponentFlutterInterfaceProtocol { - func _generateCodecForDTOs(cardComponentConfigurationDTO cardComponentConfigurationDTOArg: CardComponentConfigurationDTO, sessionDTO sessionDTOArg: SessionDTO, completion: @escaping (Result) -> Void) + func _generateCodecForDTOs(cardComponentConfigurationDTO cardComponentConfigurationDTOArg: CardComponentConfigurationDTO, sessionDTO sessionDTOArg: SessionDTO, binLookupDataDTO binLookupDataDTOArg: BinLookupDataDTO, completion: @escaping (Result) -> Void) func onComponentCommunication(componentCommunicationModel componentCommunicationModelArg: ComponentCommunicationModel, completion: @escaping (Result) -> Void) } @@ -2126,10 +2153,10 @@ class ComponentFlutterInterface: ComponentFlutterInterfaceProtocol { PlatformApiPigeonCodec.shared } - func _generateCodecForDTOs(cardComponentConfigurationDTO cardComponentConfigurationDTOArg: CardComponentConfigurationDTO, sessionDTO sessionDTOArg: SessionDTO, completion: @escaping (Result) -> Void) { + func _generateCodecForDTOs(cardComponentConfigurationDTO cardComponentConfigurationDTOArg: CardComponentConfigurationDTO, sessionDTO sessionDTOArg: SessionDTO, binLookupDataDTO binLookupDataDTOArg: BinLookupDataDTO, completion: @escaping (Result) -> Void) { let channelName = "dev.flutter.pigeon.adyen_checkout.ComponentFlutterInterface._generateCodecForDTOs\(messageChannelSuffix)" let channel = FlutterBasicMessageChannel(name: channelName, binaryMessenger: binaryMessenger, codec: codec) - channel.sendMessage([cardComponentConfigurationDTOArg, sessionDTOArg] as [Any?]) { response in + channel.sendMessage([cardComponentConfigurationDTOArg, sessionDTOArg, binLookupDataDTOArg] as [Any?]) { response in guard let listResponse = response as? [Any?] else { completion(.failure(createConnectionError(withChannelName: channelName))) return diff --git a/ios/Classes/utils/Constants.swift b/ios/Classes/utils/Constants.swift index 3511af02..fb30e627 100644 --- a/ios/Classes/utils/Constants.swift +++ b/ios/Classes/utils/Constants.swift @@ -3,4 +3,5 @@ enum Constants { static let orderKey = "order" static let updatedPaymentMethodsKey = "updatedPaymentMethods" static let shouldUpdatePaymentMethodsKey = "shouldUpdatePaymentMethods" + static let brandKey = "brand" } diff --git a/lib/adyen_checkout.dart b/lib/adyen_checkout.dart index dde76013..7b27826c 100644 --- a/lib/adyen_checkout.dart +++ b/lib/adyen_checkout.dart @@ -4,6 +4,8 @@ export 'src/common/model/analytics_options.dart'; export 'src/common/model/api_only/card_expiry_date_validation_result.dart'; export 'src/common/model/api_only/card_number_validation_result.dart'; export 'src/common/model/api_only/card_security_code_validation_result.dart'; +export 'src/common/model/card_callbacks/bin_lookup_data.dart'; +export 'src/common/model/card_callbacks/card_callbacks.dart'; export 'src/common/model/checkout.dart'; export 'src/common/model/cse/encrypted_card.dart'; export 'src/common/model/cse/unencrypted_card.dart'; diff --git a/lib/src/common/model/card_callbacks/bin_lookup_data.dart b/lib/src/common/model/card_callbacks/bin_lookup_data.dart new file mode 100644 index 00000000..810ed775 --- /dev/null +++ b/lib/src/common/model/card_callbacks/bin_lookup_data.dart @@ -0,0 +1,5 @@ +class BinLookupData { + final String brand; + + BinLookupData({required this.brand}); +} diff --git a/lib/src/common/model/card_callbacks/card_callbacks.dart b/lib/src/common/model/card_callbacks/card_callbacks.dart new file mode 100644 index 00000000..155487e5 --- /dev/null +++ b/lib/src/common/model/card_callbacks/card_callbacks.dart @@ -0,0 +1,19 @@ +import 'package:adyen_checkout/src/common/model/card_callbacks/bin_lookup_data.dart'; + +abstract class CardCallbacksInterface { + void Function(List)? onBinLookup; + void Function(String)? onBinValue; +} + +class CardCallbacks implements CardCallbacksInterface { + CardCallbacks({ + this.onBinLookup, + this.onBinValue, + }); + + @override + void Function(List)? onBinLookup; + + @override + void Function(String)? onBinValue; +} diff --git a/lib/src/common/model/payment_method_configurations/card_configuration.dart b/lib/src/common/model/payment_method_configurations/card_configuration.dart index f7f9e6df..9ab85595 100644 --- a/lib/src/common/model/payment_method_configurations/card_configuration.dart +++ b/lib/src/common/model/payment_method_configurations/card_configuration.dart @@ -1,4 +1,4 @@ -import 'package:adyen_checkout/src/generated/platform_api.g.dart'; +import 'package:adyen_checkout/adyen_checkout.dart'; class CardConfiguration { final bool holderNameRequired; @@ -9,6 +9,7 @@ class CardConfiguration { final FieldVisibility kcpFieldVisibility; final FieldVisibility socialSecurityNumberFieldVisibility; final List supportedCardTypes; + final CardCallbacksInterface? cardCallbacks; const CardConfiguration({ this.holderNameRequired = false, @@ -19,5 +20,6 @@ class CardConfiguration { this.kcpFieldVisibility = FieldVisibility.hide, this.socialSecurityNumberFieldVisibility = FieldVisibility.hide, this.supportedCardTypes = const [], + this.cardCallbacks, }); } diff --git a/lib/src/drop_in/drop_in.dart b/lib/src/drop_in/drop_in.dart index b03e1909..bd89810f 100644 --- a/lib/src/drop_in/drop_in.dart +++ b/lib/src/drop_in/drop_in.dart @@ -28,7 +28,7 @@ class DropIn { Future startDropInSessionsPayment( DropInConfiguration dropInConfiguration, - SessionCheckout dropInSession, + SessionCheckout sessionCheckout, ) async { adyenLogger.print("Start Drop-in session"); final dropInSessionCompleter = Completer(); @@ -50,6 +50,16 @@ class DropIn { event, dropInConfiguration.storedPaymentMethodConfiguration, ); + case CheckoutEventType.binLookup: + _handleOnBinLookup( + event, + dropInConfiguration.cardConfiguration?.cardCallbacks?.onBinLookup, + ); + case CheckoutEventType.binValue: + _handleOnBinValue( + event, + dropInConfiguration.cardConfiguration?.cardCallbacks?.onBinValue, + ); default: } }); @@ -123,6 +133,16 @@ class DropIn { _handleOrderRequest(event, advancedCheckout.partialPayment); case CheckoutEventType.cancelOrder: _handleOrderCancel(event, advancedCheckout.partialPayment); + case CheckoutEventType.binLookup: + _handleOnBinLookup( + event, + dropInConfiguration.cardConfiguration?.cardCallbacks?.onBinLookup, + ); + case CheckoutEventType.binValue: + _handleOnBinValue( + event, + dropInConfiguration.cardConfiguration?.cardCallbacks?.onBinValue, + ); } }); @@ -336,4 +356,34 @@ class DropIn { OrderCancelResultDTO(orderCancelResponseBody: {})); } } + + void _handleOnBinLookup( + CheckoutEvent event, + Function? binLookupCallback, + ) { + if (binLookupCallback == null) { + return; + } + + if (event.data case List binLookupDataDTOList) { + final List binLookupDataList = binLookupDataDTOList + .whereType() + .map((entry) => BinLookupData(brand: entry.brand)) + .toList(); + binLookupCallback.call(binLookupDataList); + } + } + + void _handleOnBinValue( + CheckoutEvent event, + Function? binValueCallback, + ) { + if (binValueCallback == null) { + return; + } + + if (event.data case String binValue) { + binValueCallback.call(binValue); + } + } } diff --git a/lib/src/generated/platform_api.g.dart b/lib/src/generated/platform_api.g.dart index d8cbbc51..f0e18b43 100644 --- a/lib/src/generated/platform_api.g.dart +++ b/lib/src/generated/platform_api.g.dart @@ -76,6 +76,8 @@ enum CheckoutEventType { balanceCheck, requestOrder, cancelOrder, + binLookup, + binValue, } enum ComponentCommunicationType { @@ -1374,6 +1376,27 @@ class OrderCancelResultDTO { } } +class BinLookupDataDTO { + BinLookupDataDTO({ + required this.brand, + }); + + String brand; + + Object encode() { + return [ + brand, + ]; + } + + static BinLookupDataDTO decode(Object result) { + result as List; + return BinLookupDataDTO( + brand: result[0]! as String, + ); + } +} + class _PigeonCodec extends StandardMessageCodec { const _PigeonCodec(); @override @@ -1465,59 +1488,62 @@ class _PigeonCodec extends StandardMessageCodec { } else if (value is OrderCancelResultDTO) { buffer.putUint8(157); writeValue(buffer, value.encode()); - } else if (value is Environment) { + } else if (value is BinLookupDataDTO) { buffer.putUint8(158); + writeValue(buffer, value.encode()); + } else if (value is Environment) { + buffer.putUint8(159); writeValue(buffer, value.index); } else if (value is AddressMode) { - buffer.putUint8(159); + buffer.putUint8(160); writeValue(buffer, value.index); } else if (value is CardAuthMethod) { - buffer.putUint8(160); + buffer.putUint8(161); writeValue(buffer, value.index); } else if (value is TotalPriceStatus) { - buffer.putUint8(161); + buffer.putUint8(162); writeValue(buffer, value.index); } else if (value is GooglePayEnvironment) { - buffer.putUint8(162); + buffer.putUint8(163); writeValue(buffer, value.index); } else if (value is CashAppPayEnvironment) { - buffer.putUint8(163); + buffer.putUint8(164); writeValue(buffer, value.index); } else if (value is PaymentResultEnum) { - buffer.putUint8(164); + buffer.putUint8(165); writeValue(buffer, value.index); } else if (value is CheckoutEventType) { - buffer.putUint8(165); + buffer.putUint8(166); writeValue(buffer, value.index); } else if (value is ComponentCommunicationType) { - buffer.putUint8(166); + buffer.putUint8(167); writeValue(buffer, value.index); } else if (value is PaymentEventType) { - buffer.putUint8(167); + buffer.putUint8(168); writeValue(buffer, value.index); } else if (value is FieldVisibility) { - buffer.putUint8(168); + buffer.putUint8(169); writeValue(buffer, value.index); } else if (value is InstantPaymentType) { - buffer.putUint8(169); + buffer.putUint8(170); writeValue(buffer, value.index); } else if (value is ApplePayShippingType) { - buffer.putUint8(170); + buffer.putUint8(171); writeValue(buffer, value.index); } else if (value is ApplePayMerchantCapability) { - buffer.putUint8(171); + buffer.putUint8(172); writeValue(buffer, value.index); } else if (value is ApplePaySummaryItemType) { - buffer.putUint8(172); + buffer.putUint8(173); writeValue(buffer, value.index); } else if (value is CardNumberValidationResultDTO) { - buffer.putUint8(173); + buffer.putUint8(174); writeValue(buffer, value.index); } else if (value is CardExpiryDateValidationResultDTO) { - buffer.putUint8(174); + buffer.putUint8(175); writeValue(buffer, value.index); } else if (value is CardSecurityCodeValidationResultDTO) { - buffer.putUint8(175); + buffer.putUint8(176); writeValue(buffer, value.index); } else { super.writeValue(buffer, value); @@ -1586,61 +1612,63 @@ class _PigeonCodec extends StandardMessageCodec { case 157: return OrderCancelResultDTO.decode(readValue(buffer)!); case 158: + return BinLookupDataDTO.decode(readValue(buffer)!); + case 159: final int? value = readValue(buffer) as int?; return value == null ? null : Environment.values[value]; - case 159: + case 160: final int? value = readValue(buffer) as int?; return value == null ? null : AddressMode.values[value]; - case 160: + case 161: final int? value = readValue(buffer) as int?; return value == null ? null : CardAuthMethod.values[value]; - case 161: + case 162: final int? value = readValue(buffer) as int?; return value == null ? null : TotalPriceStatus.values[value]; - case 162: + case 163: final int? value = readValue(buffer) as int?; return value == null ? null : GooglePayEnvironment.values[value]; - case 163: + case 164: final int? value = readValue(buffer) as int?; return value == null ? null : CashAppPayEnvironment.values[value]; - case 164: + case 165: final int? value = readValue(buffer) as int?; return value == null ? null : PaymentResultEnum.values[value]; - case 165: + case 166: final int? value = readValue(buffer) as int?; return value == null ? null : CheckoutEventType.values[value]; - case 166: + case 167: final int? value = readValue(buffer) as int?; return value == null ? null : ComponentCommunicationType.values[value]; - case 167: + case 168: final int? value = readValue(buffer) as int?; return value == null ? null : PaymentEventType.values[value]; - case 168: + case 169: final int? value = readValue(buffer) as int?; return value == null ? null : FieldVisibility.values[value]; - case 169: + case 170: final int? value = readValue(buffer) as int?; return value == null ? null : InstantPaymentType.values[value]; - case 170: + case 171: final int? value = readValue(buffer) as int?; return value == null ? null : ApplePayShippingType.values[value]; - case 171: + case 172: final int? value = readValue(buffer) as int?; return value == null ? null : ApplePayMerchantCapability.values[value]; - case 172: + case 173: final int? value = readValue(buffer) as int?; return value == null ? null : ApplePaySummaryItemType.values[value]; - case 173: + case 174: final int? value = readValue(buffer) as int?; return value == null ? null : CardNumberValidationResultDTO.values[value]; - case 174: + case 175: final int? value = readValue(buffer) as int?; return value == null ? null : CardExpiryDateValidationResultDTO.values[value]; - case 175: + case 176: final int? value = readValue(buffer) as int?; return value == null ? null @@ -2423,7 +2451,8 @@ abstract class ComponentFlutterInterface { void _generateCodecForDTOs( CardComponentConfigurationDTO cardComponentConfigurationDTO, - SessionDTO sessionDTO); + SessionDTO sessionDTO, + BinLookupDataDTO binLookupDataDTO); void onComponentCommunication( ComponentCommunicationModel componentCommunicationModel); @@ -2456,9 +2485,13 @@ abstract class ComponentFlutterInterface { final SessionDTO? arg_sessionDTO = (args[1] as SessionDTO?); assert(arg_sessionDTO != null, 'Argument for dev.flutter.pigeon.adyen_checkout.ComponentFlutterInterface._generateCodecForDTOs was null, expected non-null SessionDTO.'); + final BinLookupDataDTO? arg_binLookupDataDTO = + (args[2] as BinLookupDataDTO?); + assert(arg_binLookupDataDTO != null, + 'Argument for dev.flutter.pigeon.adyen_checkout.ComponentFlutterInterface._generateCodecForDTOs was null, expected non-null BinLookupDataDTO.'); try { - api._generateCodecForDTOs( - arg_cardComponentConfigurationDTO!, arg_sessionDTO!); + api._generateCodecForDTOs(arg_cardComponentConfigurationDTO!, + arg_sessionDTO!, arg_binLookupDataDTO!); return wrapResponse(empty: true); } on PlatformException catch (e) { return wrapResponse(error: e); diff --git a/pigeons/platform_api.dart b/pigeons/platform_api.dart index b0e1e576..a7226cfd 100644 --- a/pigeons/platform_api.dart +++ b/pigeons/platform_api.dart @@ -64,6 +64,8 @@ enum CheckoutEventType { balanceCheck, requestOrder, cancelOrder, + binLookup, + binValue } enum ComponentCommunicationType { @@ -606,6 +608,12 @@ class OrderCancelResultDTO { ); } +class BinLookupDataDTO { + final String brand; + + BinLookupDataDTO({required this.brand}); +} + @HostApi() abstract class CheckoutPlatformInterface { @async @@ -722,6 +730,7 @@ abstract class ComponentFlutterInterface { void _generateCodecForDTOs( CardComponentConfigurationDTO cardComponentConfigurationDTO, SessionDTO sessionDTO, + BinLookupDataDTO binLookupDataDTO, ); void onComponentCommunication(