diff --git a/android/src/main/java/com/adyenreactnativesdk/AdyenPaymentPackage.java b/android/src/main/java/com/adyenreactnativesdk/AdyenPaymentPackage.java index c118d8fae..3f7077a7b 100644 --- a/android/src/main/java/com/adyenreactnativesdk/AdyenPaymentPackage.java +++ b/android/src/main/java/com/adyenreactnativesdk/AdyenPaymentPackage.java @@ -7,7 +7,6 @@ package com.adyenreactnativesdk; import com.adyenreactnativesdk.component.applepay.AdyenApplePayMock; -import com.adyenreactnativesdk.component.card.AdyenCardComponent; import com.adyenreactnativesdk.component.dropin.AdyenDropInComponent; import com.adyenreactnativesdk.component.googlepay.AdyenGooglePayComponent; import com.adyenreactnativesdk.component.instant.AdyenInstantComponent; @@ -31,7 +30,6 @@ public List createViewManagers(ReactApplicationContext reactContext public List createNativeModules(ReactApplicationContext reactContext) { List modules = new ArrayList<>(); modules.add(new AdyenDropInComponent(reactContext)); - modules.add(new AdyenCardComponent(reactContext)); modules.add(new AdyenInstantComponent(reactContext)); modules.add(new AdyenGooglePayComponent(reactContext)); modules.add(new AdyenApplePayMock(reactContext)); diff --git a/android/src/main/java/com/adyenreactnativesdk/component/card/AdyenCardComponent.kt b/android/src/main/java/com/adyenreactnativesdk/component/card/AdyenCardComponent.kt deleted file mode 100644 index 654eb71a1..000000000 --- a/android/src/main/java/com/adyenreactnativesdk/component/card/AdyenCardComponent.kt +++ /dev/null @@ -1,175 +0,0 @@ -/* - * Copyright (c) 2021 Adyen N.V. - * - * This file is open source and available under the MIT license. See the LICENSE file for more info. - */ -package com.adyenreactnativesdk.component.card - -import com.facebook.react.bridge.ReactApplicationContext -import com.facebook.react.bridge.ReactMethod -import com.facebook.react.bridge.ReadableMap -import com.adyenreactnativesdk.configuration.RootConfigurationParser -import com.adyen.checkout.components.model.payments.Amount -import com.adyenreactnativesdk.configuration.CardConfigurationParser -import com.adyen.checkout.card.CardConfiguration -import org.json.JSONException -import android.util.Log -import androidx.appcompat.app.AppCompatActivity -import androidx.fragment.app.DialogFragment -import com.adyen.checkout.card.CardComponent -import com.adyen.checkout.card.CardView -import com.adyen.checkout.components.model.payments.request.PaymentComponentData -import com.adyen.checkout.components.ActionComponentData -import com.adyen.checkout.components.model.payments.response.Action -import com.adyen.checkout.core.api.Environment -import com.adyenreactnativesdk.action.ActionHandler -import com.adyenreactnativesdk.action.ActionHandlerConfiguration -import com.adyenreactnativesdk.action.ActionHandlingInterface -import com.adyenreactnativesdk.component.BaseModule -import com.adyenreactnativesdk.ui.ComponentViewModel -import com.adyenreactnativesdk.ui.PaymentComponentListener -import com.adyenreactnativesdk.ui.AdyenBottomSheetDialogFragment -import com.adyenreactnativesdk.util.ReactNativeError -import com.adyenreactnativesdk.util.ReactNativeJson -import com.facebook.react.bridge.WritableMap -import java.lang.Exception -import java.lang.ref.WeakReference -import java.util.* - -class AdyenCardComponent(context: ReactApplicationContext?) : BaseModule(context), - PaymentComponentListener, ActionHandlingInterface { - private var dialog: WeakReference = WeakReference(null) - - override fun getName(): String { - return COMPONENT_NAME - } - - @ReactMethod - fun open(paymentMethodsData: ReadableMap, configuration: ReadableMap) { - val paymentMethods = getPaymentMethodsApiResponse(paymentMethodsData) - if (paymentMethods == null) { - sendEvent( - DID_FAILED, - ReactNativeError.mapError("$TAG: can not deserialize paymentMethods") - ) - return - } - val paymentMethod = getPaymentMethod(paymentMethods, PAYMENT_METHOD_KEY) - if (paymentMethod == null) { - sendEvent( - DID_FAILED, - ReactNativeError.mapError("$TAG: can not parse payment methods") - ) - return - } - - val config = RootConfigurationParser(configuration) - val environment = config.environment - val clientKey = config.clientKey - val shopperLocale = config.locale ?: currentLocale(reactApplicationContext) - val amount = config.amount - - val actionHandlerConfiguration = - ActionHandlerConfiguration(shopperLocale, environment, clientKey!!) - actionHandler = ActionHandler(this, actionHandlerConfiguration) - - val parser = CardConfigurationParser(configuration) - val componentConfiguration: CardConfiguration - val builder = CardConfiguration.Builder(shopperLocale, environment, clientKey) - componentConfiguration = parser.getConfiguration(builder) - - val theActivity = appCompatActivity - val viewModel = ComponentViewModel( - paymentMethod, - shopperLocale, - amount - ) - viewModel.listener = this - theActivity.runOnUiThread { - showComponentView( - theActivity, - viewModel, - componentConfiguration - ) - } - } - - @ReactMethod - fun handle(actionMap: ReadableMap?) { - try { - val jsonObject = ReactNativeJson.convertMapToJson(actionMap) - val action = Action.SERIALIZER.deserialize(jsonObject) - actionHandler?.handleAction(appCompatActivity, action) - } catch (e: JSONException) { - sendEvent(DID_FAILED, ReactNativeError.mapError(e)) - } - } - - @ReactMethod - fun hide(success: Boolean?, message: ReadableMap?) { - val dialogFragment = dialog.get() ?: return - Log.d(TAG, "Closing component") - dialogFragment.dismiss() - actionHandler = null - } - - private fun showComponentView( - theActivity: AppCompatActivity, - viewModel: ComponentViewModel, - configuration: CardConfiguration - ) { - val componentView = CardView(theActivity) - val component: CardComponent = CardComponent.PROVIDER - .get(theActivity, viewModel.paymentMethod, configuration) - val fragmentManager = theActivity.supportFragmentManager - val componentDialog = - AdyenBottomSheetDialogFragment( - viewModel, - componentView, - component - ) - componentDialog.show(fragmentManager, "Component") - dialog = WeakReference(componentDialog) - } - - override fun onError(error: Exception) { - val errorMap = ReactNativeError.mapError(error) - sendEvent(DID_FAILED, errorMap) - } - - override fun onSubmit(data: PaymentComponentData<*>) { - val jsonObject = PaymentComponentData.SERIALIZER.serialize(data) - try { - val map: WritableMap = ReactNativeJson.convertJsonToMap(jsonObject) - map.putString("returnUrl", ActionHandler.getReturnUrl(reactApplicationContext)) - Log.d(TAG, "Paying") - sendEvent(DID_SUBMIT, map) - } catch (e: JSONException) { - sendEvent(DID_FAILED, ReactNativeError.mapError(e)) - } - } - - override fun provide(actionComponentData: ActionComponentData) { - val jsonObject = ActionComponentData.SERIALIZER.serialize(actionComponentData) - try { - val map = ReactNativeJson.convertJsonToMap(jsonObject) - sendEvent(DID_PROVIDE, map) - } catch (e: JSONException) { - sendEvent(DID_FAILED, ReactNativeError.mapError(e)) - } - } - - override fun onClose() { - sendEvent(DID_FAILED, ReactNativeError.mapError("Closed")) - } - - override fun onFinish() { - sendEvent(DID_COMPLETE, null) - } - - companion object { - private const val PAYMENT_METHOD_KEY = "scheme" - private const val TAG = "CardComponent" - private const val COMPONENT_NAME = "AdyenCardComponent" - } -} diff --git a/android/src/main/java/com/adyenreactnativesdk/ui/AdyenBottomSheetDialogFragment.java b/android/src/main/java/com/adyenreactnativesdk/ui/AdyenBottomSheetDialogFragment.java deleted file mode 100644 index 3ed83457f..000000000 --- a/android/src/main/java/com/adyenreactnativesdk/ui/AdyenBottomSheetDialogFragment.java +++ /dev/null @@ -1,168 +0,0 @@ -/* - * Copyright (c) 2021 Adyen N.V. - * - * This file is open source and available under the MIT license. See the LICENSE file for more info. - */ - -package com.adyenreactnativesdk.ui; - -import android.app.Dialog; -import android.content.Context; -import android.content.DialogInterface; -import android.os.Bundle; -import android.util.Log; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.FrameLayout; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.lifecycle.Observer; -import androidx.lifecycle.ViewModelProvider; - -import com.adyen.checkout.components.ComponentError; -import com.adyen.checkout.components.ComponentView; -import com.adyen.checkout.components.PaymentComponent; -import com.adyen.checkout.components.PaymentComponentState; -import com.adyen.checkout.components.ViewableComponent; -import com.adyen.checkout.components.model.payments.Amount; -import com.adyen.checkout.components.util.CurrencyUtils; -import com.adyen.checkout.dropin.databinding.FragmentGenericComponentBinding; -import com.adyen.checkout.dropin.ui.viewmodel.ComponentDialogViewModel; -import com.adyen.checkout.dropin.ui.viewmodel.ComponentFragmentState; -import com.adyenreactnativesdk.R; -import com.google.android.material.bottomsheet.BottomSheetBehavior; -import com.google.android.material.bottomsheet.BottomSheetDialog; -import com.google.android.material.bottomsheet.BottomSheetDialogFragment; - - -public class AdyenBottomSheetDialogFragment extends BottomSheetDialogFragment implements View.OnClickListener { - - private int dialogInitViewState = BottomSheetBehavior.STATE_COLLAPSED; - - private ComponentViewModel viewModel; - private ComponentView componentView; - private FragmentGenericComponentBinding binding; - private ComponentDialogViewModel componentViewModel; - private PaymentComponent component; - - public AdyenBottomSheetDialogFragment(@NonNull ComponentViewModel viewModel, @NonNull ComponentView view, @NonNull PaymentComponent component) { - this.viewModel = viewModel; - this.componentView = view; - this.component = component; - } - - @Override - public void onAttach(@NonNull Context context) { - super.onAttach(context); - componentViewModel = new ViewModelProvider(getActivity()).get(ComponentDialogViewModel.class); - } - - @NonNull - @Override - public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { - Dialog dialog = super.onCreateDialog(savedInstanceState); - - dialog.setOnShowListener(new DialogInterface.OnShowListener() { - @Override - public void onShow(DialogInterface dialog) { - FrameLayout bottomSheet = ((BottomSheetDialog)dialog).findViewById(com.google.android.material.R.id.design_bottom_sheet); - - if (bottomSheet == null) { return; } - - BottomSheetBehavior behavior = BottomSheetBehavior.from(bottomSheet); - if (dialogInitViewState == BottomSheetBehavior.STATE_EXPANDED) { - behavior.setSkipCollapsed(true); - } - behavior.setState(dialogInitViewState); - } - }); - - return dialog; - } - - @Nullable - @Override - public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { - binding = FragmentGenericComponentBinding.inflate(inflater, container, false); - - componentViewModel.getComponentFragmentState().observe(getViewLifecycleOwner(), new Observer() { - @Override - public void onChanged(ComponentFragmentState state) { - setPaymentPendingInitialization(state == ComponentFragmentState.AWAITING_COMPONENT_INITIALIZATION); - if (state == ComponentFragmentState.INVALID_UI) { - highlightValidationErrors(); - } - PaymentComponentState componentState = component.getState(); - if (state == ComponentFragmentState.PAYMENT_READY && componentState != null) { - Log.d("DialogFragment", "Paying: " + state.toString()); - viewModel.getListener().onSubmit(componentState.getData()); - componentViewModel.paymentStarted(); - } - } - }); - - return binding.getRoot(); - } - - @Override - public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { - - component.observe(getViewLifecycleOwner(), new Observer() { - @Override - public void onChanged(Object o) { - componentViewModel.componentStateChanged(component.getState(), componentView.isConfirmationRequired()); - } - }); - - component.observeErrors(getViewLifecycleOwner(), new Observer() { - @Override - public void onChanged(ComponentError componentError) { - viewModel.getListener().onError(componentError.getException()); - } - }); - - binding.header.setText(viewModel.getPaymentMethod().getName()); - binding.componentContainer.addView((View)componentView); - componentView.attach((ViewableComponent) component, getActivity()); - - Amount amount = viewModel.getAmount(); - if (amount != null && amount.isEmpty()) { - String amountString = CurrencyUtils.formatAmount(amount, viewModel.getShopperLocale()); - String formatted = getResources().getString(R.string.pay_button_with_value, amountString); - binding.payButton.setText(formatted); - } else { - binding.payButton.setText(getResources().getString(R.string.pay_button)); - } - - if (componentView.isConfirmationRequired()) { - dialogInitViewState = BottomSheetBehavior.STATE_EXPANDED; - binding.payButton.setOnClickListener(this); - ((View) componentView).requestFocus(); - } else { - binding.payButton.setVisibility(View.GONE); - } - } - - protected void highlightValidationErrors() { - componentView.highlightValidationErrors(); - } - - protected void setPaymentPendingInitialization(boolean pending) { - if (!componentView.isConfirmationRequired()) { - binding.payButton.setVisibility( pending ? View.INVISIBLE : View.VISIBLE); - } - - if (pending) { - binding.progressBar.show(); - } else { - binding.progressBar.hide(); - } - } - - @Override - public void onClick(View v) { - componentViewModel.payButtonClicked(); - } -} diff --git a/android/src/main/java/com/adyenreactnativesdk/ui/ComponentViewModel.java b/android/src/main/java/com/adyenreactnativesdk/ui/ComponentViewModel.java deleted file mode 100644 index cb42cd11e..000000000 --- a/android/src/main/java/com/adyenreactnativesdk/ui/ComponentViewModel.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (c) 2022 Adyen N.V. - * - * This file is open source and available under the MIT license. See the LICENSE file for more info. - */ - -package com.adyenreactnativesdk.ui; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import com.adyen.checkout.components.model.paymentmethods.PaymentMethod; -import com.adyen.checkout.components.model.payments.Amount; - -import java.util.Locale; - -public class ComponentViewModel { - private final PaymentMethod paymentMethod; - private final Amount amount; - private final Locale shopperLocale; - private PaymentComponentListener listener; - - public ComponentViewModel(@NonNull PaymentMethod paymentMethod, @NonNull Locale shopperLocale, @Nullable Amount amount) { - this.paymentMethod = paymentMethod; - this.amount = amount; - this.shopperLocale = shopperLocale; - } - - @Nullable - public Amount getAmount() { - return amount; - } - - @NonNull - public PaymentMethod getPaymentMethod() { - return paymentMethod; - } - - @NonNull - public Locale getShopperLocale() { - return shopperLocale; - } - - public PaymentComponentListener getListener() { - return listener; - } - - public void setListener(PaymentComponentListener listener) { - this.listener = listener; - } -} diff --git a/ios/AdyenModule.m b/ios/AdyenModule.m index 577a9e92f..8c1b0da61 100644 --- a/ios/AdyenModule.m +++ b/ios/AdyenModule.m @@ -20,18 +20,6 @@ @interface RCT_EXTERN_MODULE(AdyenDropIn, NSObject) @end -@interface RCT_EXTERN_MODULE(AdyenCardComponent, NSObject) - -RCT_EXTERN_METHOD(open:(NSDictionary *)paymentMethods - configuration:(NSDictionary *)configuration) - -RCT_EXTERN_METHOD(hide:(nonnull NSNumber *)success - event:(NSDictionary *)event) - -RCT_EXTERN_METHOD(handle:(NSDictionary *)action) - -@end - @interface RCT_EXTERN_MODULE(AdyenInstant, NSObject) RCT_EXTERN_METHOD(open:(NSDictionary *)paymentMethods @@ -61,4 +49,3 @@ @interface RCT_EXTERN_MODULE(AdyenGooglePay, NSObject) configuration:(NSDictionary *)configuration) @end - diff --git a/ios/Components/AdyenCardComponent.swift b/ios/Components/AdyenCardComponent.swift deleted file mode 100644 index 5f391fbd2..000000000 --- a/ios/Components/AdyenCardComponent.swift +++ /dev/null @@ -1,93 +0,0 @@ -// -// Copyright (c) 2022 Adyen N.V. -// -// This file is open source and available under the MIT license. See the LICENSE file for more info. -// - -import Adyen -import Foundation -import React -import UIKit - -@objc(AdyenCardComponent) -final internal class AdyenCardComponent: BaseModule { - - override func supportedEvents() -> [String]! { super.supportedEvents() } - @objc - func hide(_ success: NSNumber, event: NSDictionary) { - DispatchQueue.main.async { [weak self] in - guard let self = self else { return } - - self.currentComponent?.finalizeIfNeeded(with: true, completion: { - self.cleanUp() - }) - } - } - - @objc - func open(_ paymentMethodsDict: NSDictionary, configuration: NSDictionary) { - let paymentMethod: CardPaymentMethod - do { - paymentMethod = try parsePaymentMethod(from: paymentMethodsDict, for: CardPaymentMethod.self) - } catch { - return assertionFailure("AdyenCardComponent: \(error.localizedDescription)") - } - - let parser = RootConfigurationParser(configuration: configuration) - guard let clientKey = parser.clientKey else { - return assertionFailure("AdyenCardComponent: No clientKey in configuration") - } - - let apiContext = APIContext(environment: parser.environment, clientKey: clientKey) - - actionHandler = AdyenActionComponent(apiContext: apiContext) - actionHandler?.delegate = self - actionHandler?.presentationDelegate = self - - let config = CardConfigurationParser(configuration: configuration).configuration - let component = CardComponent(paymentMethod: paymentMethod, - apiContext: apiContext, - configuration: config) - component.payment = parser.payment - present(component: component) - } - - @objc - func handle(_ action: NSDictionary) { - guard let data = try? JSONSerialization.data(withJSONObject: action, options: []), - let action = try? JSONDecoder().decode(Action.self, from: data) - else { return } - - DispatchQueue.main.async { [weak self] in - self?.actionHandler?.handle(action) - } - } - -} - -extension AdyenCardComponent: PaymentComponentDelegate { - - internal func didSubmit(_ data: PaymentComponentData, from component: PaymentComponent) { - sendEvent(event: .didSubmit, body: data.jsonObject) - } - - internal func didFail(with error: Error, from component: PaymentComponent) { - sendEvent(event: .didFail, body: error.toDictionary) - } - -} - -extension AdyenCardComponent: ActionComponentDelegate { - - internal func didFail(with error: Error, from component: ActionComponent) { - sendEvent(event: .didFail, body: error.toDictionary) - } - - internal func didComplete(from component: ActionComponent) { - sendEvent(event: .didComplete, body: nil) - } - - internal func didProvide(_ data: ActionComponentData, from component: ActionComponent) { - sendEvent(event: .didProvide, body: data.jsonObject) - } -} diff --git a/src/AdyenNativeModules.js b/src/AdyenNativeModules.js index 43a5fb4ff..37f146e9f 100644 --- a/src/AdyenNativeModules.js +++ b/src/AdyenNativeModules.js @@ -25,13 +25,6 @@ export const AdyenInstant = NativeModules.AdyenInstant { get() { throw new Error(LINKING_ERROR); }, } ); -export const AdyenCardComponent = NativeModules.AdyenCardComponent - ? NativeModules.AdyenCardComponent - : new Proxy( - {}, - { get() { throw new Error(LINKING_ERROR); }, } - ); - export const AdyenApplePay = NativeModules.AdyenApplePay ? NativeModules.AdyenApplePay : new Proxy( @@ -53,14 +46,10 @@ export function getNativeComponent(name, paymentMethods) { case 'drop-in': case 'adyendropin': return { nativeComponent: AdyenDropIn }; - case 'adyencardcomponent': - return { nativeComponent: AdyenCardComponent }; case 'applepay': case 'apple-pay': return { nativeComponent: AdyenApplePay }; case 'paywithgoogle': - case 'googlepay': - case 'google-pay': return { nativeComponent: AdyenGooglePay }; default: break;