Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add bin lookup for card component #373

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

### New

- Added bin lookup callbacks for Drop-In.
- Added bin lookup callbacks for Card Component and 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).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@ import android.view.View
import androidx.activity.ComponentActivity
import com.adyen.checkout.card.CardComponent
import com.adyen.checkout.flutter.components.view.DynamicComponentView
import com.adyen.checkout.flutter.generated.BinLookupDataDTO
import com.adyen.checkout.flutter.generated.CardComponentConfigurationDTO
import com.adyen.checkout.flutter.generated.ComponentCommunicationModel
import com.adyen.checkout.flutter.generated.ComponentCommunicationType
import com.adyen.checkout.flutter.generated.ComponentFlutterInterface
import com.adyen.checkout.flutter.utils.ConfigurationMapper.mapToAmount
import com.adyen.checkout.flutter.utils.ConfigurationMapper.mapToAnalyticsConfiguration
Expand Down Expand Up @@ -49,11 +52,38 @@ abstract class BaseCardComponent(
}

fun addComponent(cardComponent: CardComponent) {
setOnBinLookupListener(cardComponent)
setOnBinValueListener(cardComponent)
dynamicComponentView.addComponent(cardComponent, activity)
}

fun setCurrentCardComponent() = setCurrentCardComponent(this)

private fun setOnBinLookupListener(cardComponent: CardComponent) {
cardComponent.setOnBinLookupListener { binLookupData ->
val binLookupDataDtoList = binLookupData.map { BinLookupDataDTO(it.brand) }
val componentCommunicationModel =
ComponentCommunicationModel(
ComponentCommunicationType.BIN_LOOKUP,
componentId,
binLookupDataDtoList
)
componentFlutterApi.onComponentCommunication(componentCommunicationModel) {}
}
}

private fun setOnBinValueListener(cardComponent: CardComponent) {
cardComponent.setOnBinValueListener { binValue ->
val componentCommunicationModel =
ComponentCommunicationModel(
ComponentCommunicationType.BIN_VALUE,
componentId,
binValue
)
componentFlutterApi.onComponentCommunication(componentCommunicationModel) {}
}
}

companion object {
const val CARD_COMPONENT_CONFIGURATION_KEY = "cardComponentConfiguration"
const val PAYMENT_METHOD_KEY = "paymentMethod"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,9 @@ enum class ComponentCommunicationType(val raw: Int) {
ADDITIONAL_DETAILS(1),
LOADING(2),
RESULT(3),
RESIZE(4);
RESIZE(4),
BIN_LOOKUP(5),
BIN_VALUE(6);

companion object {
fun ofRaw(raw: Int): ComponentCommunicationType? {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,26 +8,31 @@ import 'package:collection/collection.dart';
import 'package:flutter/material.dart';

class CardAdvancedComponentScreen extends StatelessWidget {
CardAdvancedComponentScreen({
const CardAdvancedComponentScreen({
required this.repository,
super.key,
});

final AdyenCardComponentRepository repository;
final CardComponentConfiguration cardComponentConfiguration =
CardComponentConfiguration(
environment: Config.environment,
clientKey: Config.clientKey,
countryCode: Config.countryCode,
shopperLocale: Config.shopperLocale,
cardConfiguration: const CardConfiguration(
holderNameRequired: true,
addressMode: AddressMode.full,
),
);

@override
Widget build(BuildContext context) {
final CardComponentConfiguration cardComponentConfiguration =
CardComponentConfiguration(
environment: Config.environment,
clientKey: Config.clientKey,
countryCode: Config.countryCode,
shopperLocale: Config.shopperLocale,
cardConfiguration: CardConfiguration(
holderNameRequired: true,
addressMode: AddressMode.full,
cardCallbacks: CardCallbacks(
onBinLookup: _onBinLookup,
onBinValue: _onBinValue,
),
),
);

return Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(title: const Text('Adyen card component')),
Expand All @@ -44,6 +49,7 @@ class CardAdvancedComponentScreen extends StatelessWidget {
child: Column(
children: [
_buildCardWidget(
cardComponentConfiguration,
snapshot.data!,
context,
),
Expand All @@ -58,6 +64,7 @@ class CardAdvancedComponentScreen extends StatelessWidget {
}

Widget _buildCardWidget(
CardComponentConfiguration cardComponentConfiguration,
Map<String, dynamic> paymentMethods,
BuildContext context,
) {
Expand Down Expand Up @@ -94,4 +101,14 @@ class CardAdvancedComponentScreen extends StatelessWidget {

return paymentMethod;
}

void _onBinLookup(List<BinLookupData> binLookupData) {
for (var element in binLookupData) {
debugPrint("Bin lookup data: brand:${element.brand}");
}
}

void _onBinValue(String binValue) {
debugPrint("Bin value: $binValue");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,29 +8,35 @@ import 'package:collection/collection.dart';
import 'package:flutter/material.dart';

class CardSessionComponentScreen extends StatelessWidget {
CardSessionComponentScreen({
const CardSessionComponentScreen({
required this.repository,
super.key,
});

final AdyenCardComponentRepository repository;
final CardComponentConfiguration cardComponentConfiguration =
CardComponentConfiguration(
environment: Config.environment,
clientKey: Config.clientKey,
countryCode: Config.countryCode,
shopperLocale: Config.shopperLocale,
cardConfiguration: const CardConfiguration(),
);

@override
Widget build(BuildContext context) {
final CardComponentConfiguration cardComponentConfiguration =
CardComponentConfiguration(
environment: Config.environment,
clientKey: Config.clientKey,
countryCode: Config.countryCode,
shopperLocale: Config.shopperLocale,
cardConfiguration: CardConfiguration(
cardCallbacks: CardCallbacks(
onBinLookup: _onBinLookup,
onBinValue: _onBinValue,
),
),
);

return Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(title: const Text('Adyen card component')),
body: SafeArea(
child: FutureBuilder(
future: _getSessionCheckout(),
future: _getSessionCheckout(cardComponentConfiguration),
builder:
(BuildContext context, AsyncSnapshot<SessionCheckout> snapshot) {
if (snapshot.hasData) {
Expand Down Expand Up @@ -63,8 +69,9 @@ class CardSessionComponentScreen extends StatelessWidget {
);
}

Future<SessionCheckout> _getSessionCheckout() async =>
await repository.createSessionCheckout(cardComponentConfiguration);
Future<SessionCheckout> _getSessionCheckout(
CardComponentConfiguration configuration) async =>
await repository.createSessionCheckout(configuration);

Map<String, dynamic> _extractPaymentMethod(
Map<String, dynamic> paymentMethods) {
Expand All @@ -86,4 +93,14 @@ class CardSessionComponentScreen extends StatelessWidget {

return paymentMethod;
}

void _onBinLookup(List<BinLookupData> binLookupData) {
for (var element in binLookupData) {
debugPrint("Bin lookup data: brand:${element.brand}");
}
}

void _onBinValue(String binValue) {
debugPrint("Bin value: $binValue");
}
}
61 changes: 61 additions & 0 deletions ios/Classes/components/card/BaseCardComponent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,31 @@ class BaseCardComponent: NSObject, FlutterPlatformView, UIScrollViewDelegate {

return rootViewController
}

func buildCardComponent(
paymentMethodString: String?,
isStoredPaymentMethod: Bool,
cardComponentConfiguration: CardComponentConfigurationDTO?,
componentDelegate: PaymentComponentDelegate?,
cardDelegate: CardComponentDelegate?
) throws -> CardComponent {
guard let paymentMethodString = paymentMethod else { throw PlatformError(errorDescription: "Payment method not found") }
guard let cardComponentConfiguration else { throw PlatformError(errorDescription: "Card configuration not found") }
let adyenContext = try cardComponentConfiguration.createAdyenContext()
let cardConfiguration = cardComponentConfiguration.cardConfiguration.mapToCardComponentConfiguration(
shopperLocale: cardComponentConfiguration.shopperLocale)
let paymentMethod: AnyCardPaymentMethod = isStoredPaymentMethod
? try JSONDecoder().decode(StoredCardPaymentMethod.self, from: Data(paymentMethodString.utf8))
: try JSONDecoder().decode(CardPaymentMethod.self, from: Data(paymentMethodString.utf8))
let cardComponent = CardComponent(
paymentMethod: paymentMethod,
context: adyenContext,
configuration: cardConfiguration
)
cardComponent.delegate = componentDelegate
cardComponent.cardComponentDelegate = cardDelegate
return cardComponent
}

func showCardComponent(cardComponent: CardComponent) {
self.cardComponent = cardComponent
Expand Down Expand Up @@ -149,3 +174,39 @@ class BaseCardComponent: NSObject, FlutterPlatformView, UIScrollViewDelegate {
}
}
}

extension BaseCardComponent: CardComponentDelegate {
func didSubmit(lastFour: String, finalBIN: String, component: Adyen.CardComponent) {}

func didChangeBIN(_ value: String, component: Adyen.CardComponent) {
let componentCommunicationModel = ComponentCommunicationModel(
type: ComponentCommunicationType.binValue,
componentId: componentId,
data: value
)
componentFlutterApi.onComponentCommunication(
componentCommunicationModel: componentCommunicationModel,
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 componentCommunicationModel = ComponentCommunicationModel(
type: ComponentCommunicationType.binLookup,
componentId: componentId,
data: binLookupDataDtoList
)
componentFlutterApi.onComponentCommunication(
componentCommunicationModel: componentCommunicationModel,
completion: { _ in }
)
}
}
37 changes: 12 additions & 25 deletions ios/Classes/components/card/advanced/CardAdvancedComponent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ class CardAdvancedComponent: BaseCardComponent {
private var actionComponentDelegate: ActionComponentDelegate?
private var actionComponent: AdyenActionComponent?
private var presentationDelegate: PresentationDelegate?
private var cardDelegate: PaymentComponentDelegate?
private var componentDelegate: PaymentComponentDelegate?

override init(
frame: CGRect,
Expand Down Expand Up @@ -38,6 +38,7 @@ class CardAdvancedComponent: BaseCardComponent {
private func setupCardComponentView() {
do {
let cardComponent = try setupCardComponent()
actionComponent = buildActionComponent(adyenContext: cardComponent.context)
showCardComponent(cardComponent: cardComponent)
componentPlatformApi.onActionCallback = { [weak self] jsonActionResponse in
self?.onAction(actionResponse: jsonActionResponse)
Expand All @@ -51,31 +52,17 @@ class CardAdvancedComponent: BaseCardComponent {
}

private func setupCardComponent() throws -> CardComponent {
guard let cardComponentConfiguration else { throw PlatformError(errorDescription: "Card configuration not found") }
guard let paymentMethodString = paymentMethod else { throw PlatformError(errorDescription: "Payment method not found") }
let cardComponent = try buildCardComponent(
paymentMethodString: paymentMethodString,
isStoredPaymentMethod: isStoredPaymentMethod,
cardComponentConfiguration: cardComponentConfiguration
componentDelegate = CardAdvancedFlowDelegate(
componentFlutterApi: componentFlutterApi,
componentId: componentId
)
cardDelegate = CardAdvancedFlowDelegate(componentFlutterApi: componentFlutterApi, componentId: componentId)
cardComponent.delegate = cardDelegate
return cardComponent
}

private func buildCardComponent(
paymentMethodString: String,
isStoredPaymentMethod: Bool,
cardComponentConfiguration: CardComponentConfigurationDTO
) throws -> CardComponent {
let adyenContext = try cardComponentConfiguration.createAdyenContext()
let cardConfiguration = cardComponentConfiguration.cardConfiguration.mapToCardComponentConfiguration(
shopperLocale: cardComponentConfiguration.shopperLocale)
let paymentMethod: AnyCardPaymentMethod = isStoredPaymentMethod
? try JSONDecoder().decode(StoredCardPaymentMethod.self, from: Data(paymentMethodString.utf8))
: try JSONDecoder().decode(CardPaymentMethod.self, from: Data(paymentMethodString.utf8))
actionComponent = buildActionComponent(adyenContext: adyenContext)
return CardComponent(paymentMethod: paymentMethod, context: adyenContext, configuration: cardConfiguration)
return try buildCardComponent(
paymentMethodString: paymentMethod,
isStoredPaymentMethod: isStoredPaymentMethod,
cardComponentConfiguration: cardComponentConfiguration,
componentDelegate: componentDelegate,
cardDelegate: self
)
}

private func buildActionComponent(adyenContext: AdyenContext) -> AdyenActionComponent {
Expand Down
30 changes: 6 additions & 24 deletions ios/Classes/components/card/session/CardSessionComponent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,31 +39,13 @@ class CardSessionComponent: BaseCardComponent {
}

private func setupCardComponent() throws -> CardComponent {
guard let cardComponentConfiguration else { throw PlatformError(errorDescription: "Card configuration not found") }
guard let paymentMethodString = paymentMethod else { throw PlatformError(errorDescription: "Payment method not found") }
guard let session = sessionHolder.session else { throw PlatformError(errorDescription: "Session not found") }
let cardComponent = try buildCardComponent(
paymentMethodString: paymentMethodString,
cardComponentConfiguration: cardComponentConfiguration
)
cardComponent.delegate = session
return cardComponent
}

private func buildCardComponent(
paymentMethodString: String,
cardComponentConfiguration: CardComponentConfigurationDTO
) throws -> CardComponent {
let cardPaymentMethod: AnyCardPaymentMethod = isStoredPaymentMethod
? try JSONDecoder().decode(StoredCardPaymentMethod.self, from: Data(paymentMethodString.utf8))
: try JSONDecoder().decode(CardPaymentMethod.self, from: Data(paymentMethodString.utf8))
let adyenContext = try cardComponentConfiguration.createAdyenContext()
let cardConfiguration = cardComponentConfiguration.cardConfiguration.mapToCardComponentConfiguration(
shopperLocale: cardComponentConfiguration.shopperLocale)
return CardComponent(
paymentMethod: cardPaymentMethod,
context: adyenContext,
configuration: cardConfiguration
return try buildCardComponent(
paymentMethodString: paymentMethod,
isStoredPaymentMethod: isStoredPaymentMethod,
cardComponentConfiguration: cardComponentConfiguration,
componentDelegate: session,
cardDelegate: self
)
}

Expand Down
2 changes: 2 additions & 0 deletions ios/Classes/generated/PlatformApi.swift
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,8 @@ enum ComponentCommunicationType: Int {
case loading = 2
case result = 3
case resize = 4
case binLookup = 5
case binValue = 6
}

enum PaymentEventType: Int {
Expand Down
Loading
Loading