Skip to content

Commit

Permalink
Merge pull request #323 from Adyen/feature/addIdealComponent
Browse files Browse the repository at this point in the history
Added support of iDEAL to the Instant Component.
  • Loading branch information
Robert-SD authored Dec 19, 2024
2 parents a0a66b8 + 90b81b2 commit bb89a0a
Show file tree
Hide file tree
Showing 8 changed files with 237 additions and 28 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
## 1.2.0

* Improved dynamic viewport of card component.
* Added support of iDEAL to the Instant Component.
* Added the missing loading bottom sheet for the advanced flow google pay component.
* Updated iOS SDK to v5.14.0.
* Updated Android SDK to v5.8.0. Gradle v8 is now mandatory.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,19 @@ import com.adyen.checkout.action.core.internal.ActionHandlingComponent
import com.adyen.checkout.components.core.CheckoutConfiguration
import com.adyen.checkout.components.core.Order
import com.adyen.checkout.components.core.PaymentMethod
import com.adyen.checkout.components.core.PaymentMethodTypes
import com.adyen.checkout.components.core.action.Action
import com.adyen.checkout.core.exception.CheckoutException
import com.adyen.checkout.flutter.components.instant.advanced.IdealComponentAdvancedCallback
import com.adyen.checkout.flutter.components.instant.advanced.InstantComponentAdvancedCallback
import com.adyen.checkout.flutter.components.instant.session.IdealComponentSessionCallback
import com.adyen.checkout.flutter.components.instant.session.InstantComponentSessionCallback
import com.adyen.checkout.flutter.components.view.ComponentLoadingBottomSheet
import com.adyen.checkout.flutter.session.SessionHolder
import com.adyen.checkout.flutter.utils.ConfigurationMapper.mapToCheckoutConfiguration
import com.adyen.checkout.flutter.utils.Constants
import com.adyen.checkout.flutter.utils.Constants.Companion.UNKNOWN_PAYMENT_METHOD_TYPE_ERROR_MESSAGE
import com.adyen.checkout.ideal.IdealComponent
import com.adyen.checkout.instant.InstantPaymentComponent
import com.adyen.checkout.sessions.core.CheckoutSession
import com.adyen.checkout.sessions.core.SessionSetupResponse
Expand All @@ -29,6 +35,7 @@ class InstantComponentManager(
private val assignCurrentComponent: (ActionHandlingComponent?) -> Unit,
) {
private var instantPaymentComponent: InstantPaymentComponent? = null
private var idealPaymentComponent: IdealComponent? = null
private var componentId: String? = null

fun start(
Expand All @@ -37,31 +44,17 @@ class InstantComponentManager(
componentId: String
) {
try {
if (ComponentLoadingBottomSheet.isVisible(activity.supportFragmentManager)) {
hideLoadingBottomSheet()
}

val paymentMethod = PaymentMethod.SERIALIZER.deserialize(JSONObject(encodedPaymentMethod))
val configuration = instantPaymentConfigurationDTO.mapToCheckoutConfiguration()
val instantPaymentComponent =
when (componentId) {
Constants.INSTANT_ADVANCED_COMPONENT_KEY ->
createInstantAdvancedComponent(
configuration,
paymentMethod,
componentId
)

Constants.INSTANT_SESSION_COMPONENT_KEY ->
createInstantSessionComponent(
configuration,
paymentMethod,
componentId
)

else -> throw IllegalStateException("Instant component not available for payment flow.")
}

this.instantPaymentComponent = instantPaymentComponent
this.componentId = componentId
assignCurrentComponent(instantPaymentComponent)
ComponentLoadingBottomSheet.show(activity.supportFragmentManager, instantPaymentComponent)
when (paymentMethod.type) {
null, PaymentMethodTypes.UNKNOWN -> throw CheckoutException(UNKNOWN_PAYMENT_METHOD_TYPE_ERROR_MESSAGE)
PaymentMethodTypes.IDEAL -> startIdealPaymentComponent(componentId, configuration, paymentMethod)
else -> startInstantPaymentComponent(componentId, configuration, paymentMethod)
}
} catch (exception: Exception) {
val model =
ComponentCommunicationModel(
Expand All @@ -77,9 +70,72 @@ class InstantComponentManager(
}
}

private fun startInstantPaymentComponent(
componentId: String,
configuration: CheckoutConfiguration,
paymentMethod: PaymentMethod
) {
val instantPaymentComponent =
when (componentId) {
Constants.INSTANT_ADVANCED_COMPONENT_KEY ->
createInstantAdvancedComponent(
configuration,
paymentMethod,
componentId
)

Constants.INSTANT_SESSION_COMPONENT_KEY ->
createInstantSessionComponent(
configuration,
paymentMethod,
componentId
)

else -> throw IllegalStateException("Instant component not available for payment flow.")
}

this.instantPaymentComponent = instantPaymentComponent
this.componentId = componentId
this.idealPaymentComponent = null
assignCurrentComponent(instantPaymentComponent)
ComponentLoadingBottomSheet.show(activity.supportFragmentManager, instantPaymentComponent)
}

private fun startIdealPaymentComponent(
componentId: String,
configuration: CheckoutConfiguration,
paymentMethod: PaymentMethod
) {
val idealPaymentComponent =
when (componentId) {
Constants.INSTANT_ADVANCED_COMPONENT_KEY ->
createIdealAdvancedComponent(
configuration,
paymentMethod,
componentId
)

Constants.INSTANT_SESSION_COMPONENT_KEY ->
createIdealSessionComponent(
configuration,
paymentMethod,
componentId
)

else -> throw IllegalStateException("Ideal component not available for payment flow.")
}

this.idealPaymentComponent = idealPaymentComponent
this.componentId = componentId
this.instantPaymentComponent = null
assignCurrentComponent(idealPaymentComponent)
ComponentLoadingBottomSheet.show(activity.supportFragmentManager, idealPaymentComponent)
}

fun onDispose(componentId: String) {
if (componentId == this.componentId) {
instantPaymentComponent = null
idealPaymentComponent = null
}
}

Expand Down Expand Up @@ -132,7 +188,58 @@ class InstantComponentManager(
)
}

private fun handleAction(action: Action) = instantPaymentComponent?.handleAction(action, activity)
// We delete the ideal component integration when the instant component supports ideal.
private fun createIdealAdvancedComponent(
configuration: CheckoutConfiguration,
paymentMethod: PaymentMethod,
componentId: String,
): IdealComponent {
return IdealComponent.PROVIDER.get(
activity = activity,
paymentMethod = paymentMethod,
checkoutConfiguration = configuration,
callback =
IdealComponentAdvancedCallback(
componentFlutterInterface,
componentId,
::hideLoadingBottomSheet
),
key = UUID.randomUUID().toString()
)
}

private fun createIdealSessionComponent(
configuration: CheckoutConfiguration,
paymentMethod: PaymentMethod,
componentId: String,
): IdealComponent {
val sessionSetupResponse = SessionSetupResponse.SERIALIZER.deserialize(sessionHolder.sessionSetupResponse)
val order = sessionHolder.orderResponse?.let { Order.SERIALIZER.deserialize(it) }
val checkoutSession =
CheckoutSession(
sessionSetupResponse = sessionSetupResponse,
order = order,
environment = configuration.environment,
clientKey = configuration.clientKey
)
return IdealComponent.PROVIDER.get(
activity = activity,
checkoutSession = checkoutSession,
paymentMethod = paymentMethod,
checkoutConfiguration = configuration,
componentCallback =
IdealComponentSessionCallback(
componentFlutterInterface,
componentId,
::handleAction,
::hideLoadingBottomSheet
),
key = UUID.randomUUID().toString()
)
}

private fun handleAction(action: Action) =
instantPaymentComponent?.handleAction(action, activity) ?: idealPaymentComponent?.handleAction(action, activity)

private fun hideLoadingBottomSheet() = ComponentLoadingBottomSheet.hide(activity.supportFragmentManager)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package com.adyen.checkout.flutter.components.instant.advanced

import ComponentCommunicationModel
import ComponentFlutterInterface
import com.adyen.checkout.components.core.ComponentError
import com.adyen.checkout.components.core.PaymentComponentData
import com.adyen.checkout.flutter.components.base.ComponentAdvancedCallback
import com.adyen.checkout.flutter.utils.Constants
import com.adyen.checkout.ideal.IdealComponentState
import org.json.JSONObject

class IdealComponentAdvancedCallback(
private val componentFlutterApi: ComponentFlutterInterface,
private val componentId: String,
private val hideLoadingBottomSheet: () -> Unit,
) : ComponentAdvancedCallback<IdealComponentState>(componentFlutterApi, componentId) {
override fun onSubmit(state: IdealComponentState) {
val data = PaymentComponentData.SERIALIZER.serialize(state.data)
val submitData =
JSONObject().apply {
put(Constants.ADVANCED_PAYMENT_DATA_KEY, data)
put(Constants.ADVANCED_EXTRA_DATA_KEY, null)
}
val model =
ComponentCommunicationModel(
ComponentCommunicationType.ONSUBMIT,
componentId = componentId,
data = submitData.toString(),
)
componentFlutterApi.onComponentCommunication(model) {}
}

override fun onError(componentError: ComponentError) {
hideLoadingBottomSheet()
super.onError(componentError)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.adyen.checkout.flutter.components.instant.session

import ComponentFlutterInterface
import com.adyen.checkout.components.core.ComponentError
import com.adyen.checkout.components.core.action.Action
import com.adyen.checkout.flutter.components.base.ComponentSessionCallback
import com.adyen.checkout.ideal.IdealComponentState
import com.adyen.checkout.sessions.core.SessionPaymentResult

class IdealComponentSessionCallback(
private val componentFlutterApi: ComponentFlutterInterface,
private val componentId: String,
private val onActionCallback: (Action) -> Unit,
private val hideLoadingBottomSheet: () -> Unit,
) : ComponentSessionCallback<IdealComponentState>(componentFlutterApi, componentId, onActionCallback) {
override fun onError(componentError: ComponentError) {
hideLoadingBottomSheet()
super.onError(componentError)
}

override fun onFinished(result: SessionPaymentResult) {
hideLoadingBottomSheet()
super.onFinished(result)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -55,5 +55,7 @@ internal class ComponentLoadingBottomSheet<T> : BottomSheetDialogFragment() wher
(it as? BottomSheetDialogFragment)?.dismiss()
}
}

fun isVisible(fragmentManager: FragmentManager): Boolean = fragmentManager.findFragmentByTag(TAG) != null
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ class Constants {
companion object {
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 GOOGLE_PAY_SESSION_COMPONENT_KEY = "GOOGLE_PAY_SESSION_COMPONENT"
const val GOOGLE_PAY_ADVANCED_COMPONENT_KEY = "GOOGLE_PAY_ADVANCED_COMPONENT"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ class InstantAdvancedComponentScreen extends StatelessWidget {
_extractPaymentMethod(snapshot.data!, "paypal");
final klarnaPaymentMethodResponse =
_extractPaymentMethod(snapshot.data!, "klarna");
final idealPaymentMethodResponse =
_extractPaymentMethod(snapshot.data!, "ideal");

return Column(
mainAxisAlignment: MainAxisAlignment.center,
Expand Down Expand Up @@ -93,7 +95,23 @@ class InstantAdvancedComponentScreen extends StatelessWidget {
}
});
},
child: const Text("Klarna"))
child: const Text("Klarna")),
TextButton(
onPressed: () {
AdyenCheckout.advanced
.startInstantComponent(
configuration: instantComponentConfiguration,
paymentMethod: idealPaymentMethodResponse,
checkout: advancedCheckout,
)
.then((paymentResult) {
if (context.mounted) {
DialogBuilder.showPaymentResultDialog(
paymentResult, context);
}
});
},
child: const Text("iDEAL"))
],
);
} else {
Expand All @@ -107,7 +125,7 @@ class InstantAdvancedComponentScreen extends StatelessWidget {
Map<String, dynamic> paymentMethods, String key) {
return paymentMethods["paymentMethods"].firstWhere(
(paymentMethod) => paymentMethod["type"] == key,
orElse: () => throw Exception("$key payment method not provided"),
orElse: () => <String,dynamic>{},
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ class InstantSessionComponentScreen extends StatelessWidget {
_extractPaymentMethod(sessionCheckout.paymentMethods, "paypal");
final klarnaPaymentMethodResponse =
_extractPaymentMethod(sessionCheckout.paymentMethods, "klarna");
final idealPaymentMethodResponse =
_extractPaymentMethod(sessionCheckout.paymentMethods, "ideal");

return Column(
mainAxisAlignment: MainAxisAlignment.center,
Expand Down Expand Up @@ -88,7 +90,23 @@ class InstantSessionComponentScreen extends StatelessWidget {
}
});
},
child: const Text("Klarna"))
child: const Text("Klarna")),
TextButton(
onPressed: () {
AdyenCheckout.session
.startInstantComponent(
configuration: instantComponentConfiguration,
paymentMethod: idealPaymentMethodResponse,
checkout: sessionCheckout,
)
.then((paymentResult) {
if (context.mounted) {
DialogBuilder.showPaymentResultDialog(
paymentResult, context);
}
});
},
child: const Text("iDEAL"))
],
);
} else {
Expand All @@ -102,7 +120,7 @@ class InstantSessionComponentScreen extends StatelessWidget {
Map<String, dynamic> paymentMethods, String key) {
return paymentMethods["paymentMethods"].firstWhere(
(paymentMethod) => paymentMethod["type"] == key,
orElse: () => throw Exception("$key payment method not provided"),
orElse: () => <String,dynamic>{},
);
}
}

0 comments on commit bb89a0a

Please sign in to comment.