diff --git a/merchant-sdk/README.md b/merchant-sdk/README.md index 06b041b6ac..3321c2502b 100644 --- a/merchant-sdk/README.md +++ b/merchant-sdk/README.md @@ -7,9 +7,7 @@ The Gini Merchant SDK for Android provides all the UI and functionality needed t method in Android apps. The payment information can be reviewed and paid using any available payment provider app (e.g., banking app). -The Gini Merchant API provides a secure channel for sharing payment related information between clients. In addition, it -also provides an information extraction service for analyzing invoices. Specifically, it extracts information such as -the document sender or the payment relevant information (amount to pay, IBAN, etc.). +The Gini Merchant API provides a secure channel for sharing payment related information between clients. Documentation ------------- diff --git a/merchant-sdk/example-app/src/main/java/net/gini/android/merchant/sdk/exampleapp/orders/ui/OrdersActivity.kt b/merchant-sdk/example-app/src/main/java/net/gini/android/merchant/sdk/exampleapp/orders/ui/OrdersActivity.kt index 22de1114a9..d80a771d1a 100644 --- a/merchant-sdk/example-app/src/main/java/net/gini/android/merchant/sdk/exampleapp/orders/ui/OrdersActivity.kt +++ b/merchant-sdk/example-app/src/main/java/net/gini/android/merchant/sdk/exampleapp/orders/ui/OrdersActivity.kt @@ -65,7 +65,6 @@ class OrdersActivity : AppCompatActivity() { is GiniMerchant.MerchantSDKEvents.OnScreenDisplayed -> { when (event.displayedScreen) { DisplayedScreen.MoreInformationFragment -> setActivityTitle(net.gini.android.merchant.sdk.R.string.gms_more_information_fragment_title) - DisplayedScreen.ReviewFragment -> setActivityTitle(R.string.title_fragment_order_details) else -> { setActivityTitle(R.string.title_activity_orders) } } } diff --git a/merchant-sdk/sdk/src/doc/source/_static/logo_flat.png b/merchant-sdk/sdk/src/doc/source/_static/logo_flat.png index a4d563256b..28c495a388 100644 Binary files a/merchant-sdk/sdk/src/doc/source/_static/logo_flat.png and b/merchant-sdk/sdk/src/doc/source/_static/logo_flat.png differ diff --git a/merchant-sdk/sdk/src/doc/source/authentication.rst b/merchant-sdk/sdk/src/doc/source/authentication.rst index d17d94476c..e259f9d81c 100644 --- a/merchant-sdk/sdk/src/doc/source/authentication.rst +++ b/merchant-sdk/sdk/src/doc/source/authentication.rst @@ -1,16 +1,12 @@ Authentication ============== -The entry point for the Gini Merchant SDK is ``GiniMerchant`` class which depends -on ``GiniHealthAPI`` from the Gini Health API Library to interact with the Gini Health API. +The entry point for the Gini Merchant SDK is the ``GiniMerchant`` class. -The ``GiniHealthAPI`` class can be built either with client credentials (clientId and clientSecret) -or with a ``SessionManager`` if you have a token. For these two cases there are helper methods: +The ``GiniMerchant`` class can be built either with client credentials (clientId and clientSecret) +or with a ``SessionManager`` if you have a token: -- ``getGiniApi(context: Context, clientId: String, clientSecret: String, emailDomain: String)`` -- ``getGiniApi(context: Context, sessionManager: SessionManager)`` +- ``GiniMerchant(context: Context, clientId: String, clientSecret: String, emailDomain: String)`` +- ``GiniMerchant(context: Context, sessionManager: SessionManager)`` -``SessionManager`` is an interface which you need to implement to send the token. - -For more details about ``GiniHealthAPI`` see the `Gini Health API Library -`_. \ No newline at end of file +``SessionManager`` is an interface which you need to implement to send the token. \ No newline at end of file diff --git a/merchant-sdk/sdk/src/doc/source/customization.rst b/merchant-sdk/sdk/src/doc/source/customization.rst index 97f265cfe8..2e986576a8 100644 --- a/merchant-sdk/sdk/src/doc/source/customization.rst +++ b/merchant-sdk/sdk/src/doc/source/customization.rst @@ -77,12 +77,12 @@ the following snippet to one of your resources XML file (e.g, ``colors.xml``): If you overridde the ``GiniMerchantTheme``, the theme colors you set there override the color palette customization. Find the names of the color resources in the color palette (you can also view it in Figma `here -`_): +`_): .. raw:: html | @@ -128,12 +128,12 @@ application, use the root style as the parent, for example: We use ``android:lineSpacingMultiplier`` in combination with ``android:textSize`` to support resizing text for accessibility and avoid overlapping text. Preview our typography and find the names of the style resources (you can also view it in Figma `here -`_): +`_): .. raw:: html | @@ -157,7 +157,7 @@ Payment Component ~~~~~~~~~~~~~~~~~ You can also view the UI customisation guide in Figma `here -`_. +`_. .. note:: @@ -166,7 +166,7 @@ You can also view the UI customisation guide in Figma `here .. raw:: html | @@ -175,7 +175,7 @@ Bank Selection Bottom Sheet ~~~~~~~~~~~~~~~~~~~~~~~~~~~ You can also view the UI customisation guide in Figma `here -`_. +`_. .. note:: @@ -184,7 +184,7 @@ You can also view the UI customisation guide in Figma `here .. raw:: html | @@ -193,7 +193,7 @@ Payment Feature Info Screen ~~~~~~~~~~~~~~~~~~~~~~~~~~~ You can also view the UI customisation guide in Figma `here -`_. +`_. .. warning:: @@ -207,19 +207,16 @@ You can also view the UI customisation guide in Figma `here .. raw:: html +| + Payment Review Screen ~~~~~~~~~~~~~~~~~~~~~ You can also view the UI customisation guide in Figma `here -`_. - -.. note:: - - The close button in the top right corner is disabled by default. You can enable it by setting the - ``showCloseButton`` to ``true`` when creating a ``ReviewConfiguration``. +`_. .. note:: @@ -228,6 +225,6 @@ You can also view the UI customisation guide in Figma `here .. raw:: html diff --git a/merchant-sdk/sdk/src/doc/source/event-tracking.rst b/merchant-sdk/sdk/src/doc/source/event-tracking.rst index 23cffb2a82..0f62b7470b 100644 --- a/merchant-sdk/sdk/src/doc/source/event-tracking.rst +++ b/merchant-sdk/sdk/src/doc/source/event-tracking.rst @@ -4,63 +4,16 @@ Event Tracking GiniMerchant ---------- -The ``GiniMerchant`` class exposes kotlin flows which you can collect to track events. The following flows are available: +The ``GiniMerchant`` class exposes a kotlin flow which you can collect to track events: -* ``documentFlow`` is a ``StateFlow`` of ``ResultWrapper`` which emits the Gini Health API's document used by - the ``ReviewFragment``. It emits the following states: - * ``ResultWrapper.Loading()`` when the document is being loaded. - * ``ResultWrapper.Success(document)`` when the document is available. - * ``ResultWrapper.Error(throwable)`` when there was an error loading the document. -* ``paymentFlow`` is a ``StateFlow`` of ``ResultWrapper`` which emits the payment information shown in - the ``ReviewFragment``. - * ``ResultWrapper.Loading()`` when the payment details are being loaded. - * ``ResultWrapper.Success(paymentDetails)`` when the payment details are available. - * ``ResultWrapper.Error(throwable)`` when there was an error loading the payment details. -* ``openBankState`` is a ``StateFlow`` of ``PaymentState`` which emits the state of opening the banking app. It emits - the following states: - * ``PaymentState.NoAction()`` is the idle state. - * ``PaymentState.Loading()`` when the user requested to open the banking app and the Merchant SDK started creating a - payment request. - * ``PaymentState.Success(paymentRequest)`` when the payment request is ready and the banking app will be opened. - * ``PaymentState.Error(throwable)`` when there was an error creating the payment request or opening the banking app. +* ``eventsFlow`` is a ``SharedFlow`` of ``MerchantSDKEvents``. It emits the following events: + * ``NoAction`` is the idle state + * ``OnLoading`` when communicating with the Gini Merchant API (e.g., creating a payment request). + * ``OnScreenDisplayed(displayedScreen)`` when there is a change in the screens displayed within the payment flow. + * ``OnFinishedWithPaymentRequestCreated(paymentRequestId, paymentProviderName)`` when the payment request is ready and the user is redirected to the bank. + * ``OnFinishedWithCancellation`` when the payment flow was cancelled. Can be either from an internal error, or from exiting the payment flow without finalising it. + * ``OnErrorOccurred(throwable)`` when there was an error within the payment flow. -PaymentComponent ----------------- +.. note:: -The ``PaymentComponent`` class also exposes kotlin flows which you can collect to track events. The following flows are available: - -* ``paymentProviderAppsFlow`` is a ``StateFlow`` of ``PaymentProviderAppsState`` which emits the available payment provider apps used by - the ``PaymentComponentView`` and related screens. It emits the following states: - * ``PaymentProviderAppsState.Loading()`` when the payment provider apps are being loaded. - * ``PaymentProviderAppsState.Success(paymentProviderApps)`` when the list of payment provider apps is available. - * ``PaymentProviderAppsState.Error(throwable)`` when there was an error loading the payment provider apps. -* ``selectedPaymentProviderAppFlow`` is a ``StateFlow`` of ``SelectedPaymentProviderAppState`` which emits selected payment provider app shown in - the ``PaymentComponentView`` and related screens. It emits the following states: - * ``SelectedPaymentProviderAppState.NothingSelected()`` when there is no selection. - * ``SelectedPaymentProviderAppState.AppSelected(paymentProviderApp)`` when a payment provider app has been selected. - -ReviewFragment --------------- - -To get informed of ``ReviewFragment`` events (like the user clicking the "close" or "next" button) you can implement -the ``ReviewFragmentListener`` and set it on the fragment. - -.. code-block:: kotlin - - val reviewConfiguration = ReviewConfiguration(...) - - val paymentReviewFragment = paymentComponent.getPaymentReviewFragment( - documentId, reviewConfiguration - ) - - paymentReviewFragment.listener = object : ReviewFragmentListener { - override fun onCloseReview() { - // Called only when the ``ReviewConfiguration.showCloseButton`` was set to ``true``. - // Dismiss the ReviewFragment. - } - - override fun onToTheBankButtonClicked(paymentProviderName: String) { - // Log or track the used payment provider name. - // No action required, the payment process is handled by the Gini Merchant SDK. - } - } + ``OnFinishedWithPaymentRequestCreated`` and ``OnFinishedWithCancellation`` can be checked for to remove the ``PaymentFragment`` from the hierarchy. diff --git a/merchant-sdk/sdk/src/doc/source/flow.rst b/merchant-sdk/sdk/src/doc/source/flow.rst index edc70aadec..1c129c55d6 100644 --- a/merchant-sdk/sdk/src/doc/source/flow.rst +++ b/merchant-sdk/sdk/src/doc/source/flow.rst @@ -1,9 +1,8 @@ Flow ==== -``GiniMerchant`` and ``PaymentComponent`` are the main classes for interacting with the Gini Merchant SDK. ``GiniMerchant`` -manages interaction with the Gini Health API and ``PaymentComponent`` manages the data and state used by every -``PaymentComponentView`` and related screens. +``GiniMerchant`` and ``PaymentFragment`` are the main classes for interacting with the Gini Merchant SDK. ``GiniMerchant`` +manages interaction with the Gini Merchant API and ``PaymentFragment`` handles the data and the logic for displaying related screens. .. contents:: The recommended flow is: :local: @@ -11,243 +10,49 @@ manages interaction with the Gini Health API and ``PaymentComponent`` manages th Create the GiniMerchant instance ------------------------------ -Before creating an instance of ``GiniMerchant`` you need to create an instance of the ``GiniHealthAPI``. The Gini Merchant -SDK provides the following two helper methods for creating the ``GiniHealthAPI`` instance: +The ``GiniMerchant`` class can be built either with client credentials (clientId and clientSecret) +or with a ``SessionManager`` if you have a token: -* ``getGiniApi(context: Context, clientId: String, clientSecret: String, emailDomain: String)`` -* ``getGiniApi(context: Context, sessionManager: SessionManager)`` +- ``GiniMerchant(context: Context, clientId: String, clientSecret: String, emailDomain: String)`` +- ``GiniMerchant(context: Context, sessionManager: SessionManager)`` -After that you can create an instance of ``GiniMerchant``: - -.. code-block:: kotlin - - val giniMerchant = GiniMerchant(giniHealthApi) - -Upload documents ----------------- - -Uploading documents is achieved via the ``GiniHealthAPI`` instance's ``documentManager``. You can access it by using the -``giniMerchant.giniHealthAPI.documentManager`` property. - -For each document page a *partial document* needs to be created. The following example shows how to create a new partial -document from a byte array containing a JPEG image: - -.. code-block:: kotlin - - // Assuming `imageBytes` is an instance of a byte array containing a JPEG image, - // e.g. from a picture taken by the camera - - coroutineScope.launch { - // Create a partial document by uploading the document data - val partialDocumentResource = - giniMerchant.giniHealthApi.documentManager.createPartialDocument(imageBytes, "image/jpeg", "document_page_1.jpg") - - when (partialDocumentResource) { - is Resource.Success -> { - // Use the partial document - val partialDocument = partialDocumentResource.data - } - is Resource.Error -> // Handle error - is Resource.Cancelled -> // Handle cancellation - } - } - -After all partial documents have been created you can create a *composite document* from the partials to bundle them -into one final document: - -.. code-block:: kotlin - - // Assuming `partialDocuments` is a list of `Documents` which were - // returned by `createPartialDocument(...)` calls - - coroutineScope.launch { - // Create a composite document by uploading the document data - val compositeDocumentResource = - giniMerchant.giniHealthApi.documentManager.createCompositeDocument(partialDocuments) - - when (compositeDocumentResource) { - is Resource.Success -> { - // Use the composite document - val compositeDocument = compositeDocumentResource.data - } - is Resource.Error -> // Handle error - is Resource.Cancelled -> // Handle cancellation - } - } - -Check which documents/invoices are payable ------------------------------------------- - -Call ``giniMerchant.checkIfDocumentIsPayable()`` with the composite document id for each invoice to check whether it is -payable. We recommend performing this check only once right after the invoice has been uploaded and processed by Gini's -Health API. You can then store the ``isPayable`` state in your own data model. - -.. code-block:: kotlin - - // Assuming `compositeDocument` is `Document` returned by `createCompositeDocument(...)` - - coroutineScope.launch { - try { - // Check whether the composite document is payable - val isPayable = giniMerchant.checkIfDocumentIsPayable(compositeDocument.id) - } catch (e: Exception) { - // Handle error - } - } - -Create the PaymentComponent instance ------------------------------------- - -For creating an instance of the ``PaymentComponent`` you need to pass in the Android context (either the application or -an activity context) and the ``GiniMerchant`` instance: - -.. code-block:: kotlin - - val paymentComponent = PaymentComponent(context, giniMerchant) - -Add a listener to the PaymentComponent --------------------------------------- - -Set a listener on the ``PaymentComponent`` to get informed of events from every ``PaymentComponentView``: - -.. code-block:: kotlin - - paymentComponent.listener = object: PaymentComponent.Listener { - override fun onMoreInformationClicked() { - // Show the MoreInformationFragment. - } - - override fun onBankPickerClicked() { - // Show the BankSelectionBottomSheet. - } - - override fun onPayInvoiceClicked(documentId: String) { - // Show the ReviewFragment. - } - } +``SessionManager`` is an interface which you need to implement to send the token. Load payment providers ---------------------- -Call ``paymentComponent.loadPaymentProviderApps()`` to load the available payment providers from the Gini Health API and -to check which ones are installed on the user's device. - -.. note:: - - It should be sufficient to call ``paymentComponent.loadPaymentProviderApps()`` only once when your app starts. - -Show the PaymentComponentViews ------------------------------- +You can call ``giniMerchant.loadPaymentProviderApps()`` to load the available payment providers and to check which ones are installed on the user's device. -The ``PaymentComponentView`` is a custom view widget and the main entry point for users. It allows them to pick a bank -and initiate the payment process. In addition, it also allows users to view more information about the payment feature. +This step is optional, as the method is called when starting the payment flow. Calling it beforehand will lead to a faster load of the payment flow. -The ``PaymentComponentView`` is hidden by default and should be added to the layout of each invoice item: - -.. code-block:: xml - - - -When creating the view holder for the invoice item, pass the ``PaymentComponent`` instance to the view holder: - -.. code-block:: kotlin - - val paymentComponentView = view.findViewById(R.id.payment_component) - paymentComponentView.paymentComponent = paymentComponent - -When binding the view holder of the invoice item, prepare it for reuse, set the payable state and the document id: - -.. code-block:: kotlin - - viewHolder.paymentComponentView.prepareForReuse() - viewHolder.paymentComponentView.isPayable = invoiceItem.isPayable - viewHolder.paymentComponentView.documentId = invoiceItem.documentId - -.. note:: - - The ``PaymentComponentView`` will only be visible if its ``isPayable`` property is ``true``. - -Show the MoreInformationFragment --------------------------------- +Create the PaymentFragment instance +------------------------------------ -The ``MoreInformationFragment`` shows the Payment Feature Info Screen. It displays information and an FAQ section about the payment feature. It requires a -``PaymentComponent`` instance to show the icons of the available banks. +Create an instance of ``PaymentFragment`` and load it into the hierarchy. -To instantiate it use ``MoreInformationFragment.newInstance()`` and pass in your ``PaymentComponent`` instance: +For creating an instance of the ``PaymentFragment`` you need to use the ``createFragment`` method provided by ``GiniMerchant``, passing in the payment details (IBAN, Amount, Purpose and Recipient). +The ``GiniMerchantSDK`` expects the payment details to not be empty, and will throw an ``IllegalStateException`` if any of the fields are empty. .. code-block:: kotlin - MoreInformationFragment.newInstance(paymentComponent) + val paymentFragment = giniMerchant.createFragment(iban, recipient, amount, purpose) .. note:: - The ``MoreInformationFragment`` doesn't handle navigation related events and doesn't show a navigation bar. You are - free to design navigation to and from the fragment as you see fit. - - For the navigation bar title you should use the ``gms_more_information_fragment_title`` string resource. - -.. warning:: + Optionally, you can pass in an instance of ``PaymentFlowConfiguration``. - You need to override the ``gms_privacy_policy_link_url`` string resource to provide a link to your company's privacy - policy page. This link will be shown to users in the answer to the "Who or what is Gini?" question. + The ``PaymentFlowConfiguration`` class contains the following options: -Show the BankSelectionBottomSheet ---------------------------------- - -The ``BankSelectionBottomSheet`` displays a list of available banks for the user to choose from. If a banking app is not -installed it will also display its Play Store link. - -To instantiate it use ``BankSelectionBottomSheet.newInstance()`` and pass in your ``PaymentComponent`` instance: - -.. code-block:: kotlin - - BankSelectionBottomSheet.newInstance(paymentComponent) - - -Show the ReviewFragment ------------------------ - -The ``ReviewFragment`` displays an invoice's pages and extractions. It also lets users pay the invoice with the bank -they selected in the ``BankSelectionBottomSheet``. - -To instantiate it use ``paymentComponent.getPaymentReviewFragment()`` and pass in the Gini Health API's document id of -the invoice and the configuration for the screen. Also set a listener to get informed of events from the fragment: - -.. code-block:: kotlin - - val reviewConfiguration = ReviewConfiguration(...) - - val paymentReviewFragment = paymentComponent.getPaymentReviewFragment( - documentId, reviewConfiguration - ) - - paymentReviewFragment.listener = object : ReviewFragmentListener { - override fun onCloseReview() { - // Called only when the ``ReviewConfiguration.showCloseButton`` was set to ``true``. - // Dismiss the ReviewFragment. - } - - override fun onToTheBankButtonClicked(paymentProviderName: String) { - // Log or track the used payment provider name. - // No action required, the payment process is handled by the Gini Merchant SDK. - } - } + - ``shouldHandleErrorsInternally``: If set to ``true``, ``GiniMerchant`` will handle errors internally and show + snackbars for errors. If set to ``false``, errors will be ignored by the ``GiniMerchant``. In this case the flows + exposed by ``GiniMerchant`` should be observed for errors. Default value is ``true``. + - ``showReviewFragment``: If set to ``true``, the user will be able to review the payment details before continuing with the payment. Default value is ``false``. .. note:: - ``paymentComponent.getPaymentReviewFragment()`` will load the document extractions asynchronously. It's a suspend - function and must be called from a coroutine. - - The ``ReviewFragment`` doesn't handle navigation related events and doesn't show a navigation bar. You are - free to design navigation to and from the fragment as you see fit. + Users will have the chance to edit their payment information in their banking app, even if ``showReviewFragment`` is set to false. -The ``ReviewConfiguration`` class contains the following options: +Event Tracking +-------------- -- ``handleErrorsInternally``: If set to ``true``, the ``ReviewFragment`` will handle errors internally and show - snackbars for errors. If set to ``false``, errors will be ignored by the ``ReviewFragment``. In this case the flows - exposed by ``GiniMerchant`` should be observed for errors. Default value is ``true``. -- ``showCloseButton``: If set to ``true``, a floating close button will be shown in the top right corner of the screen. Default value is ``false``. +``GiniMerchant`` exposes ``eventFlow``, which can be collected by client apps to be aware of state / events / errors happening within the SDK. \ No newline at end of file diff --git a/merchant-sdk/sdk/src/doc/source/images/testing/BankSelectionBottomSheet.png b/merchant-sdk/sdk/src/doc/source/images/testing/BankSelectionBottomSheet.png index 9210c57c0d..0d5e2d45c5 100644 Binary files a/merchant-sdk/sdk/src/doc/source/images/testing/BankSelectionBottomSheet.png and b/merchant-sdk/sdk/src/doc/source/images/testing/BankSelectionBottomSheet.png differ diff --git a/merchant-sdk/sdk/src/doc/source/images/testing/PaymentComponentScreen.png b/merchant-sdk/sdk/src/doc/source/images/testing/PaymentComponentScreen.png new file mode 100644 index 0000000000..7896b7cf7b Binary files /dev/null and b/merchant-sdk/sdk/src/doc/source/images/testing/PaymentComponentScreen.png differ diff --git a/merchant-sdk/sdk/src/doc/source/images/testing/PaymentComponentViews.png b/merchant-sdk/sdk/src/doc/source/images/testing/PaymentComponentViews.png deleted file mode 100644 index 1609722bed..0000000000 Binary files a/merchant-sdk/sdk/src/doc/source/images/testing/PaymentComponentViews.png and /dev/null differ diff --git a/merchant-sdk/sdk/src/doc/source/images/testing/PaymentReviewScreen.png b/merchant-sdk/sdk/src/doc/source/images/testing/PaymentReviewScreen.png index 0b02183c61..46bf27dc54 100644 Binary files a/merchant-sdk/sdk/src/doc/source/images/testing/PaymentReviewScreen.png and b/merchant-sdk/sdk/src/doc/source/images/testing/PaymentReviewScreen.png differ diff --git a/merchant-sdk/sdk/src/doc/source/index.rst b/merchant-sdk/sdk/src/doc/source/index.rst index ee3f4cf693..6d95c27415 100644 --- a/merchant-sdk/sdk/src/doc/source/index.rst +++ b/merchant-sdk/sdk/src/doc/source/index.rst @@ -1,17 +1,11 @@ Gini Merchant SDK for Android =========================== -The Gini Merchant SDK for Android provides all the UI and functionality needed to use the Gini Health API in your app to -extract payment and health information from invoices. The payment information can be reviewed and then the invoice can -be paid using any available payment provider app (e.g., banking app). - -The Gini Health API provides an information extraction service for analyzing health invoices. Specifically, it extracts -information such as the document sender or the payment relevant information (amount to pay, IBAN, etc.). In addition it -also provides a secure channel for sharing payment related information between clients. - -.. note:: - The documentation for the 2.x.x version can be found `here `_. +The Gini Merchant SDK for Android provides all the UI and functionality needed to use the Gini Pay Connect payment +method in Android apps. The payment information can be reviewed and paid using any available payment provider app (e.g., +banking app). +The Gini Merchant API provides a secure channel for sharing payment related information between clients. Table of contents ----------------- diff --git a/merchant-sdk/sdk/src/doc/source/setup.rst b/merchant-sdk/sdk/src/doc/source/setup.rst index 8dfe14e5ed..423f17071b 100644 --- a/merchant-sdk/sdk/src/doc/source/setup.rst +++ b/merchant-sdk/sdk/src/doc/source/setup.rst @@ -18,7 +18,7 @@ Gini Pay Deep Link For Your App ------------------------------- In order for banking apps to be able to return the user to your app after the payment has been resolved you can -register one of your activities to respond to a deep link scheme known by the Gini Health API. +register one of your activities to respond to a deep link scheme known by the Gini Merchant API. You should already have a scheme and host from us. Please contact us in case you don't have them. @@ -38,9 +38,9 @@ The following is an example for the deep link ``ginipay-business://payment-reque -Gini Health API Client Credentials +Gini Merchant API Client Credentials ------------------------------- -You should have received Gini Health API client credentials from us. Please get in touch with us in case you don't have them. +You should have received Gini Merchant API client credentials from us. Please get in touch with us in case you don't have them. Continue to `Authentication `_ to see how to use the client credentials to initialize the Gini Merchant SDK. \ No newline at end of file diff --git a/merchant-sdk/sdk/src/doc/source/testing.rst b/merchant-sdk/sdk/src/doc/source/testing.rst index 245dbf2c79..807b0ac5c5 100644 --- a/merchant-sdk/sdk/src/doc/source/testing.rst +++ b/merchant-sdk/sdk/src/doc/source/testing.rst @@ -10,10 +10,10 @@ Example banking app An example banking app is available in the `Gini Bank SDK `_ repository called ``example-app``. -You can use the same Gini Health API client credentials in the example banking app as in your app, if not otherwise +You can use the same Gini Merchant API client credentials in the example banking app as in your app, if not otherwise specified. -Development Gini Health API client credentials +Development Gini Merchant API client credentials ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ In order to test using the Gini Bank SDK's example banking app, you will need to use development client credentials in @@ -34,15 +34,14 @@ the ``Gini-Test-Payment-Provider``. You can also install the Gini Bank SDK example app's ``devPaymentProvider2Debug`` and ``devPaymentProvider3Debug`` build variants to test with two additional example banking apps named ``GiniBank`` and ``Bank``. -Payment component +Payment fragment ~~~~~~~~~~~~~~~~~ -By following the `flow guide `_ you should be showing the ``PaymentComponentView`` for each invoice in your -app's list of invoices. The following screenshot shows a sample list of invoices where the ``PaymentComponentView`` is -shown for each invoice. +By following the `flow guide `_ you should be showing the ``PaymentFragment`` when wanting to pay an order. The following screenshot shows a sample invoice for which +the ``PaymentFragment`` is shown. -.. image:: images/testing/PaymentComponentViews.png - :alt: Screenshot of invoices list with PaymentComponentViews showing the Gini-Test-Payment-Provider. +.. image:: images/testing/PaymentComponentScreen.png + :alt: Screenshot of an order with PayymentFragment showing the Gini-Test-Payment-Provider. :width: 200px :align: center @@ -51,7 +50,7 @@ shown for each invoice. Bank picker ~~~~~~~~~~~ -You should see the ``Gini-Test-Payment-Provider`` preselected in every ``PaymentComponentView``. By clicking the picker +You should see the ``Gini-Test-Payment-Provider`` preselected in ``PaymentFragment`` everytime when starting the flow. By clicking the picker you should see the ``BankSelectionBottomSheet`` with the list of available banking apps (including ``Gini-Test-Payment-Provider`` and other testing and production apps). @@ -65,7 +64,7 @@ you should see the ``BankSelectionBottomSheet`` with the list of available banki More information and FAQ ~~~~~~~~~~~~~~~~~~~~~~~~ -By clicking either the ``more information`` or the info icon on the ``PaymentComponentView`` you should see the +By clicking the ``More information`` button in ``BankSelectionBottomSheet`` you should see the ``MoreInformationFragment`` with information about the payment feature and an FAQ section. .. image:: images/testing/MoreInformationScreen.png @@ -78,10 +77,11 @@ By clicking either the ``more information`` or the info icon on the ``PaymentCom Payment review ~~~~~~~~~~~~~~~ -By clicking the "Pay the invoice" button on a ``PaymentComponentView`` you should see the ``ReviewFragment``, which -shows the invoice's pages and the payment information. It also allows editing the payment information. The "To the +If the ``PaymentFragment`` was created with an instance of ``PaymentFlowConfiguration`` with the ``showReviewFragment`` option set to true, +by clicking the "Pay the invoice" button on the ``PaymentFragment`` you should see the ``ReviewFragment``. It +shows the payment information. It also allows editing the amount. The "To the banking app" button should have the icon and colors of the banking app, which was selected in the -``PaymentComponentView``. +``BankSelectionBottomSheet``. .. image:: images/testing/PaymentReviewScreen.png :alt: Screenshot of the Payment Review screen showing the Gini-Test-Payment-Provider. @@ -96,7 +96,7 @@ Execute payment When clicking the "To the banking app" button on the ``ReviewFragment`` you should be redirected to the example banking app where the payment information will be fetched from Gini (including any changes you made on the ``ReviewFragment``). Press the "Pay" button to execute a test payment which will mark the payment as paid in the -Gini Health API. +Gini Merchant API. .. image:: images/testing/BankSDKExampleAppPaymentDetails.png :alt: Screenshot of the example banking app showing the same payment information as the Payment Review screen previously. @@ -111,7 +111,7 @@ Return to your app After the test payment has been executed, the example banking app should show a "Return to Business" button which should take you back to your app. -With these steps completed you have verified that your app, the Gini Health API, the Gini Merchant SDK and the Gini Bank +With these steps completed you have verified that your app, the Gini Merchant API, the Gini Merchant SDK and the Gini Bank SDK work together correctly. .. image:: images/testing/BankSDKExampleAppReturnToBusinessApp.png @@ -129,4 +129,4 @@ to use production credentials in your app. This will make sure the Gini Merchant which open real banking apps. You will also need to install a banking app which uses the Gini Bank SDK. You should be able to install these from the -list shown by clicking the bank picker button in a ``PaymentComponentView``. +list shown by clicking the bank picker button in the ``PaymentFragment``. diff --git a/merchant-sdk/sdk/src/main/java/net/gini/android/merchant/sdk/GiniMerchant.kt b/merchant-sdk/sdk/src/main/java/net/gini/android/merchant/sdk/GiniMerchant.kt index 72aa05bdd5..d2f7c9b636 100644 --- a/merchant-sdk/sdk/src/main/java/net/gini/android/merchant/sdk/GiniMerchant.kt +++ b/merchant-sdk/sdk/src/main/java/net/gini/android/merchant/sdk/GiniMerchant.kt @@ -23,11 +23,13 @@ import net.gini.android.merchant.sdk.util.DisplayedScreen import org.slf4j.LoggerFactory /** - * [GiniMerchant] is one of the main classes for interacting with the Gini Merchant SDK. + * [GiniMerchant] is one of the main classes for interacting with the Gini Merchant SDK. It provides a way to create the [PaymentFragment], + * which is the entrypoint to the Merchant SDK. * - * [documentFlow], [paymentFlow], [openBankState] are used by the [ReviewFragment] to observe their state, but they are public - * so that they can be observed anywhere, the main purpose for this is to observe errors. + * [eventsFlow] is used by the [PaymentFragment] to observe its state, but it is public + * so that it can be observed anywhere, the main purpose for this is to observe screen changes and finish events. */ + class GiniMerchant( private val context: Context, private val clientId: String = "", @@ -95,7 +97,16 @@ class GiniMerchant( internal fun replaceHealthApiInstance(giniHealthAPI: GiniHealthAPI) { _giniHealthAPI = giniHealthAPI } - + + /** + * Creates and returns the [PaymentFragment]. Checks if [iban], [recipient], [amount] and [purpose] are empty and throws [IllegalStateException] if any of them are. + * + * @param iban - the iban of the recipient + * @param recipient + * @param amount - the amount to be paid + * @param purpose - the purpose of the payment + * @param flowConfiguration - optional parameter with the [PaymentFlowConfiguration] + */ fun createFragment(iban: String, recipient: String, amount: String, purpose: String, flowConfiguration: PaymentFlowConfiguration? = null): PaymentFragment { if (iban.isEmpty() || recipient.isEmpty() || amount.isEmpty() || purpose.isEmpty()) throw IllegalStateException("Payment details are incomplete.") @@ -151,19 +162,69 @@ class GiniMerchant( class DocumentId(val id: String, val paymentDetails: PaymentDetails? = null) : CapturedArguments() } + /** + * State of the payment request + */ sealed class PaymentState { + /** + * Not performing any operation. + */ object NoAction : PaymentState() + + /** + * Request is in progress. + */ object Loading : PaymentState() + + /** + * Payment request was completed successfully + * + * @param paymentRequest - the payment request + * @param paymentProviderName - the name of the payment provider with which the request was created + */ class Success(val paymentRequest: PaymentRequest, val paymentProviderName: String) : PaymentState() + + /** + * Payment request returned an error. + */ class Error(val throwable: Throwable) : PaymentState() } + /** + * Different events that can be emitted by the MerchantSDK. + */ sealed class MerchantSDKEvents { object NoAction: MerchantSDKEvents() + + /** + * Signal loading started. + */ object OnLoading: MerchantSDKEvents() + + /** + * A change of screens within the [PaymentFragment]. + * + * @param [displayedScreen] - the newly displayed screen. Can be observed to update the activity title. + */ class OnScreenDisplayed(val displayedScreen: DisplayedScreen): MerchantSDKEvents() - class OnFinishedWithPaymentRequestCreated(val paymentRequestId: String, val paymentProviderName: String): MerchantSDKEvents() + + /** + * Payment request finished with success. + * + * @param [paymentRequestId] - the id of the payment request + * @param [paymentProviderName] - the selected payment provider name + */ + class OnFinishedWithPaymentRequestCreated(val paymentRequestId: String, + val paymentProviderName: String): MerchantSDKEvents() + + /** + * Payment request was cancelled. Can be either from the server, or by cancelling the payment flow. + */ class OnFinishedWithCancellation(): MerchantSDKEvents() + + /** + * An error occurred during the payment request. + */ class OnErrorOccurred(val throwable: Throwable): MerchantSDKEvents() } diff --git a/merchant-sdk/sdk/src/main/java/net/gini/android/merchant/sdk/api/authorization/model/Session.kt b/merchant-sdk/sdk/src/main/java/net/gini/android/merchant/sdk/api/authorization/model/Session.kt index e3a85b61a6..b9969c4979 100644 --- a/merchant-sdk/sdk/src/main/java/net/gini/android/merchant/sdk/api/authorization/model/Session.kt +++ b/merchant-sdk/sdk/src/main/java/net/gini/android/merchant/sdk/api/authorization/model/Session.kt @@ -1,8 +1,10 @@ package net.gini.android.merchant.sdk.api.authorization.model -import net.gini.android.core.api.authorization.apimodels.SessionToken import java.util.Date +/** + * The session is the value object for the session of a user. + */ class Session( /** The session's access token. */ val accessToken: String, @@ -11,23 +13,4 @@ class Session( ) { /** The expiration date of the access token. */ val expirationDate: Date = Date(expirationDate.time) - - /** - * Uses the current locale's time to check whether or not this session has already expired. - * - * @return Whether or not the session has already expired. - */ - fun hasExpired(): Boolean { - val now = Date() - return now.after(expirationDate) - } - - companion object { - fun fromAPIResponse(apiResponse: SessionToken): Session { - val accessToken = apiResponse.accessToken - val now = Date() - val expirationTime = now.time + apiResponse.expiresIn * 1000 - return Session(accessToken, Date(expirationTime)) - } - } } \ No newline at end of file diff --git a/merchant-sdk/sdk/src/main/java/net/gini/android/merchant/sdk/api/payment/model/Payment.kt b/merchant-sdk/sdk/src/main/java/net/gini/android/merchant/sdk/api/payment/model/Payment.kt index f1a573fbb4..0042b47219 100644 --- a/merchant-sdk/sdk/src/main/java/net/gini/android/merchant/sdk/api/payment/model/Payment.kt +++ b/merchant-sdk/sdk/src/main/java/net/gini/android/merchant/sdk/api/payment/model/Payment.kt @@ -3,15 +3,11 @@ package net.gini.android.merchant.sdk.api.payment.model /** * Holds information about a payment. */ -data class Payment( +internal data class Payment( val paidAt: String, val recipient: String, val iban: String, val amount: String, val purpose: String, val bic: String? = null, -) - -internal fun net.gini.android.core.api.models.Payment.toPayment() = Payment( - paidAt, recipient, iban, amount, purpose, bic ) \ No newline at end of file diff --git a/merchant-sdk/sdk/src/main/java/net/gini/android/merchant/sdk/api/payment/model/PaymentDetails.kt b/merchant-sdk/sdk/src/main/java/net/gini/android/merchant/sdk/api/payment/model/PaymentDetails.kt index 8f41b3e818..ef4eb1544a 100644 --- a/merchant-sdk/sdk/src/main/java/net/gini/android/merchant/sdk/api/payment/model/PaymentDetails.kt +++ b/merchant-sdk/sdk/src/main/java/net/gini/android/merchant/sdk/api/payment/model/PaymentDetails.kt @@ -2,14 +2,9 @@ package net.gini.android.merchant.sdk.api.payment.model import android.os.Parcelable import kotlinx.parcelize.Parcelize -import net.gini.android.core.api.models.CompoundExtraction -import net.gini.android.core.api.models.ExtractionsContainer -import net.gini.android.core.api.models.SpecificExtraction -import net.gini.android.merchant.sdk.review.error.NoPaymentDataExtracted -import net.gini.android.merchant.sdk.util.toBackendFormat /** - * Represents the payment details of an invoice as extracted from a document. + * Represents the payment details of an order. */ @Parcelize data class PaymentDetails( @@ -17,96 +12,11 @@ data class PaymentDetails( val iban: String, val amount: String, val purpose: String, - internal val extractions: ExtractionsContainer? = null ): Parcelable -internal fun ExtractionsContainer.toPaymentDetails(): PaymentDetails { - if (!compoundExtractions.containsKey("payment")) { - throw NoPaymentDataExtracted() - } - return PaymentDetails( - recipient = compoundExtractions.getPaymentExtraction("payment_recipient")?.value - ?: "", - iban = compoundExtractions.getPaymentExtraction("iban")?.value ?: "", - amount = compoundExtractions.getPaymentExtraction("amount_to_pay")?.value?.toAmount() - ?: "", - purpose = compoundExtractions.getPaymentExtraction("payment_purpose")?.value ?: "", - extractions = this - ) -} - -internal fun String.toAmount(): String { - val delimiterIndex = this.indexOf(":") - return if (delimiterIndex != -1) { - this.substring(0, delimiterIndex) - } else { - this - } -} - -/** - * Checks if the document is payable which looks for iban extraction. - */ -val PaymentDetails.isPayable get() = iban.isNotEmpty() // It appears this is not used anymore - we could remove it at a later stage (would remove it from the documentation as well) - -internal fun MutableMap.withFeedback(paymentDetails: PaymentDetails): Map { - this["payment"] = this["payment"].let { payment -> - CompoundExtraction( - payment?.name ?: "payment", - payment?.specificExtractionMaps?.mapIndexed { index, specificExtractions -> - if (index > 0) return@mapIndexed specificExtractions - - mutableMapOf().also { extractions -> - extractions.putAll(specificExtractions) - extractions["payment_recipient"] = extractions["payment_recipient"].let { extraction -> - SpecificExtraction( - extraction?.name ?: "payment_recipient", - paymentDetails.recipient, - extraction?.entity ?: "", - extraction?.box, - extraction?.candidate ?: emptyList() - ) - } - extractions["iban"] = extractions["iban"].let { extraction -> - SpecificExtraction( - extraction?.name ?: "iban", - paymentDetails.iban, - extraction?.entity ?: "", - extraction?.box, - extraction?.candidate ?: emptyList() - ) - } - extractions["amount_to_pay"] = extractions["amount_to_pay"].let { extraction -> - SpecificExtraction( - extraction?.name ?: "amount_to_pay", - "${paymentDetails.amount.toBackendFormat()}:EUR", - extraction?.entity ?: "", - extraction?.box, - extraction?.candidate ?: emptyList() - ) - } - extractions["payment_purpose"] = extractions["payment_purpose"].let { extraction -> - SpecificExtraction( - extraction?.name ?: "payment_purpose", - paymentDetails.purpose, - extraction?.entity ?: "", - extraction?.box, - extraction?.candidate ?: emptyList() - ) - } - } - } ?: listOf() - ) - } - return this -} - -internal fun MutableMap.getPaymentExtraction(name: String) = this["payment"]?.specificExtractionMaps?.get(0)?.get(name) - internal fun PaymentDetails.overwriteEmptyFields(value: PaymentDetails): PaymentDetails = this.copy( recipient = if (recipient.trim().isEmpty()) value.recipient else recipient, iban = if (iban.trim().isEmpty()) value.iban else iban, amount = if (amount.trim().isEmpty()) value.amount else amount, purpose = if (purpose.trim().isEmpty()) value.purpose else purpose, - extractions = extractions ?: value.extractions, ) \ No newline at end of file diff --git a/merchant-sdk/sdk/src/main/java/net/gini/android/merchant/sdk/integratedFlow/PaymentFlowViewModel.kt b/merchant-sdk/sdk/src/main/java/net/gini/android/merchant/sdk/integratedFlow/PaymentFlowViewModel.kt index 870533410b..64554367a6 100644 --- a/merchant-sdk/sdk/src/main/java/net/gini/android/merchant/sdk/integratedFlow/PaymentFlowViewModel.kt +++ b/merchant-sdk/sdk/src/main/java/net/gini/android/merchant/sdk/integratedFlow/PaymentFlowViewModel.kt @@ -10,7 +10,6 @@ import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.launch import net.gini.android.core.api.Resource import net.gini.android.merchant.sdk.GiniMerchant -import net.gini.android.merchant.sdk.api.payment.model.Payment import net.gini.android.merchant.sdk.api.payment.model.PaymentDetails import net.gini.android.merchant.sdk.api.payment.model.PaymentRequest import net.gini.android.merchant.sdk.paymentcomponent.BankPickerRows @@ -114,12 +113,6 @@ internal class PaymentFlowViewModel( giniPaymentManager.onPayment(initialSelectedPaymentProvider, paymentDetails) } - fun onBankOpened() { - // Schedule on the main dispatcher to allow all collectors to receive the current state before - // the state is overridden - giniMerchant.emitSDKEvent(GiniMerchant.PaymentState.NoAction) - } - fun getPaymentProviderApp() = initialSelectedPaymentProvider private fun observeOpenWithCount(paymentProviderAppId: String) { diff --git a/merchant-sdk/sdk/src/main/java/net/gini/android/merchant/sdk/integratedFlow/PaymentFragment.kt b/merchant-sdk/sdk/src/main/java/net/gini/android/merchant/sdk/integratedFlow/PaymentFragment.kt index c3f41bcf38..be3c864627 100644 --- a/merchant-sdk/sdk/src/main/java/net/gini/android/merchant/sdk/integratedFlow/PaymentFragment.kt +++ b/merchant-sdk/sdk/src/main/java/net/gini/android/merchant/sdk/integratedFlow/PaymentFragment.kt @@ -50,12 +50,12 @@ import net.gini.android.merchant.sdk.util.wrappedWithGiniMerchantTheme import org.jetbrains.annotations.VisibleForTesting /** - * Configuration for the integrated payment flow + * Configuration for the payment flow. */ @Parcelize data class PaymentFlowConfiguration( /** - * If set to `true`, the [ReviewFragment] will be shown before initiating payment. + * If set to `true`, the [ReviewBottomSheet] will be shown before initiating payment. * If set to `false`, the payment request process will be executed under the hood before redirecting to the selected payment provider * * Default value is `false` @@ -64,19 +64,19 @@ data class PaymentFlowConfiguration( /** * If set to `true`, the errors will be handled internally and snackbars will be shown for errors. - * If set to `false`, errors will be ignored by the [PaymentFragment] and the [ReviewFragment]. In this case the flows exposed by [GiniMerchant] should be observed for errors. + * If set to `false`, errors will be ignored by the [PaymentFragment] and the [ReviewBottomSheet]. In this case the flows exposed by [GiniMerchant] should be observed for errors. * * Default value is `true`. */ val shouldHandleErrorsInternally: Boolean = true, /** - * If set to `true`, the [Amount] field of shown as part of the [ReviewFragment] will be editable. + * If set to `true`, the [Amount] field of shown as part of the [ReviewBottomSheet] will be editable. * If set to `false`, the [Amount] field will be read-only. * * Default value is `false` */ - val isAmountFieldEditable: Boolean = true, + internal val isAmountFieldEditable: Boolean = true, /** * If set to `true` and the user is a returning one, the `Select your bank to pay` and `More Information` labels will be hidden @@ -334,7 +334,6 @@ class PaymentFragment private constructor( val intent = viewModel.getPaymentProviderApp()?.getIntent(sdkEvent.paymentRequestId) if (intent != null) { startActivity(intent) - viewModel.onBankOpened() } else { handleError(getString(R.string.gms_generic_error_message)) { viewModel.onPaymentButtonTapped(requireContext().externalCacheDir) } } diff --git a/merchant-sdk/sdk/src/main/java/net/gini/android/merchant/sdk/paymentComponentBottomSheet/PaymentComponentBottomSheet.kt b/merchant-sdk/sdk/src/main/java/net/gini/android/merchant/sdk/paymentComponentBottomSheet/PaymentComponentBottomSheet.kt index e25caa132a..6270bd714e 100644 --- a/merchant-sdk/sdk/src/main/java/net/gini/android/merchant/sdk/paymentComponentBottomSheet/PaymentComponentBottomSheet.kt +++ b/merchant-sdk/sdk/src/main/java/net/gini/android/merchant/sdk/paymentComponentBottomSheet/PaymentComponentBottomSheet.kt @@ -19,8 +19,7 @@ import net.gini.android.merchant.sdk.util.autoCleared import net.gini.android.merchant.sdk.util.extensions.setBackListener import org.jetbrains.annotations.VisibleForTesting - -class PaymentComponentBottomSheet private constructor( +internal class PaymentComponentBottomSheet private constructor( paymentComponent: PaymentComponent?, reviewFragmentShown: Boolean, backListener: BackListener? = null diff --git a/merchant-sdk/sdk/src/main/java/net/gini/android/merchant/sdk/paymentprovider/PaymentProviderApp.kt b/merchant-sdk/sdk/src/main/java/net/gini/android/merchant/sdk/paymentprovider/PaymentProviderApp.kt index 891fcdec8d..9c817c9aff 100644 --- a/merchant-sdk/sdk/src/main/java/net/gini/android/merchant/sdk/paymentprovider/PaymentProviderApp.kt +++ b/merchant-sdk/sdk/src/main/java/net/gini/android/merchant/sdk/paymentprovider/PaymentProviderApp.kt @@ -131,12 +131,12 @@ internal data class PaymentProviderApp( } } -data class PaymentProviderAppColors( +internal data class PaymentProviderAppColors( @ColorInt val backgroundColor: Int, @ColorInt val textColor: Int ) -data class InstalledPaymentProviderApp( +internal data class InstalledPaymentProviderApp( val packageName: String, val version: String, val launchIntent: Intent diff --git a/merchant-sdk/sdk/src/main/java/net/gini/android/merchant/sdk/review/ReviewConfiguration.kt b/merchant-sdk/sdk/src/main/java/net/gini/android/merchant/sdk/review/ReviewConfiguration.kt index 4e040cd582..5820e2cb01 100644 --- a/merchant-sdk/sdk/src/main/java/net/gini/android/merchant/sdk/review/ReviewConfiguration.kt +++ b/merchant-sdk/sdk/src/main/java/net/gini/android/merchant/sdk/review/ReviewConfiguration.kt @@ -4,7 +4,7 @@ package net.gini.android.merchant.sdk.review /** * Configuration for the [ReviewBottomSheet]. */ -data class ReviewConfiguration( +internal data class ReviewConfiguration( /** * If set to `true`, the [ReviewBottomSheet] will handle errors internally and show snackbars for errors. * If set to `false`, errors will be ignored by the [ReviewBottomSheet]. In this case the flows exposed by [GiniMerchant] should be observed for errors. diff --git a/merchant-sdk/sdk/src/main/java/net/gini/android/merchant/sdk/review/error/Errors.kt b/merchant-sdk/sdk/src/main/java/net/gini/android/merchant/sdk/review/error/Errors.kt deleted file mode 100644 index 9a3be838d9..0000000000 --- a/merchant-sdk/sdk/src/main/java/net/gini/android/merchant/sdk/review/error/Errors.kt +++ /dev/null @@ -1,6 +0,0 @@ -package net.gini.android.merchant.sdk.review.error - -/** - * Thrown when there was no payment data in the document extractions. - */ -class NoPaymentDataExtracted : Throwable("No payment data extracted.") \ No newline at end of file diff --git a/merchant-sdk/sdk/src/main/java/net/gini/android/merchant/sdk/review/installApp/InstallAppBottomSheet.kt b/merchant-sdk/sdk/src/main/java/net/gini/android/merchant/sdk/review/installApp/InstallAppBottomSheet.kt index b826ecfc88..cae2037775 100644 --- a/merchant-sdk/sdk/src/main/java/net/gini/android/merchant/sdk/review/installApp/InstallAppBottomSheet.kt +++ b/merchant-sdk/sdk/src/main/java/net/gini/android/merchant/sdk/review/installApp/InstallAppBottomSheet.kt @@ -28,7 +28,7 @@ import org.slf4j.LoggerFactory /** * Interface for forwarding the request to redirect to bank app. */ -interface InstallAppForwardListener { +internal interface InstallAppForwardListener { fun onForwardToBankSelected() } diff --git a/merchant-sdk/sdk/src/main/java/net/gini/android/merchant/sdk/review/pager/DocumentPageAdapter.kt b/merchant-sdk/sdk/src/main/java/net/gini/android/merchant/sdk/review/pager/DocumentPageAdapter.kt deleted file mode 100644 index d36b67e38c..0000000000 --- a/merchant-sdk/sdk/src/main/java/net/gini/android/merchant/sdk/review/pager/DocumentPageAdapter.kt +++ /dev/null @@ -1,129 +0,0 @@ -package net.gini.android.merchant.sdk.review.pager - -import android.graphics.BitmapFactory -import android.graphics.Matrix -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.widget.Button -import android.widget.FrameLayout -import android.widget.ImageView -import android.widget.ProgressBar -import androidx.core.view.ViewCompat -import androidx.core.view.isVisible -import androidx.recyclerview.widget.DiffUtil -import androidx.recyclerview.widget.ListAdapter -import androidx.recyclerview.widget.RecyclerView -import com.github.chrisbanes.photoview.PhotoView -import dev.chrisbanes.insetter.applyInsetter -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.cancelChildren -import kotlinx.coroutines.launch -import net.gini.android.merchant.sdk.GiniMerchant -import net.gini.android.merchant.sdk.databinding.GmsItemPageHorizontalBinding -import net.gini.android.merchant.sdk.api.ResultWrapper -import net.gini.android.merchant.sdk.api.wrapToResult -import net.gini.android.merchant.sdk.util.hideKeyboard - -internal class DocumentPageAdapter(private val giniMerchant: GiniMerchant) : - ListAdapter(DiffUtilCallback) { - - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PageViewHolder = - HorizontalViewHolder(giniMerchant, GmsItemPageHorizontalBinding.inflate(LayoutInflater.from(parent.context), parent, false)) - - override fun onBindViewHolder(holder: PageViewHolder, position: Int) { - holder.onBind(currentList[position]) - } - - abstract class PageViewHolder(private val giniMerchant: GiniMerchant, view: View) : RecyclerView.ViewHolder(view) { - private val imageLoadingScope = CoroutineScope(Dispatchers.Main) - - protected abstract val loadingView: ProgressBar - protected abstract val imageView: ImageView - protected abstract val errorView: FrameLayout - protected abstract val retry: Button - - fun onBind(page: Page) { - imageLoadingScope.launch { - loadingView.isVisible = true - when (val imageResult = wrapToResult { giniMerchant.giniHealthAPI.documentManager.getPageImage(page.documentId, page.number) }) { - is ResultWrapper.Error -> { - loadingView.isVisible = false - errorView.isVisible = true - retry.setOnClickListener { - errorView.isVisible = false - onBind(page) - } - } - is ResultWrapper.Success -> { - loadingView.isVisible = false - imageView.isVisible = true - imageView.setImageBitmap(BitmapFactory.decodeByteArray(imageResult.value, 0, imageResult.value.size)) - } - is ResultWrapper.Loading -> {} - } - } - } - - fun cancel() { - imageLoadingScope.coroutineContext.cancelChildren() - } - } - - class HorizontalViewHolder( - giniMerchant: GiniMerchant, - private val binding: GmsItemPageHorizontalBinding, - override val loadingView: ProgressBar = binding.loading, - override val imageView: PhotoView = binding.image, - override val errorView: FrameLayout = binding.error.root, - override val retry: Button = binding.error.pageErrorRetry, - ) : PageViewHolder(giniMerchant, binding.root) { - - private val photoViewMatrix = Matrix() - - init { - imageView.applyInsetter { - type(statusBars = true) { - padding(top = true) - } - type(ime = true) { - padding(bottom = true) - } - } - - imageView.setOnViewTapListener { view, _, _ -> - view.hideKeyboard() - } - - imageView.setOnScaleChangeListener { _, _, _ -> - binding.root.parent.requestDisallowInterceptTouchEvent(true) - } - ViewCompat.setOnApplyWindowInsetsListener(binding.root) { _, insets -> - with(imageView) { - post { - // Calling setSuppMatrix() translates the image to the edge of the visible area - // if the new visible area size results in a gap between the edges of the image and - // the visible area. Scale is always preserved. - getSuppMatrix(photoViewMatrix) - setSuppMatrix(photoViewMatrix) - } - } - insets - } - } - } - - override fun onViewRecycled(holder: PageViewHolder) { - holder.cancel() - super.onViewRecycled(holder) - } - - object DiffUtilCallback : DiffUtil.ItemCallback() { - override fun areItemsTheSame(oldItem: Page, newItem: Page) = oldItem.number == newItem.number - - override fun areContentsTheSame(oldItem: Page, newItem: Page) = oldItem.documentId == newItem.documentId - } - - data class Page(val documentId: String, val number: Int) -} \ No newline at end of file diff --git a/merchant-sdk/sdk/src/main/java/net/gini/android/merchant/sdk/util/BackListener.kt b/merchant-sdk/sdk/src/main/java/net/gini/android/merchant/sdk/util/BackListener.kt index 8590a50dda..b777ac9608 100644 --- a/merchant-sdk/sdk/src/main/java/net/gini/android/merchant/sdk/util/BackListener.kt +++ b/merchant-sdk/sdk/src/main/java/net/gini/android/merchant/sdk/util/BackListener.kt @@ -3,6 +3,6 @@ package net.gini.android.merchant.sdk.util /** * Listener for back events. */ -interface BackListener { +internal interface BackListener { fun backCalled() } \ No newline at end of file diff --git a/merchant-sdk/sdk/src/main/java/net/gini/android/merchant/sdk/util/DisplayedScreen.kt b/merchant-sdk/sdk/src/main/java/net/gini/android/merchant/sdk/util/DisplayedScreen.kt index 5c07a24464..941c8e8a8d 100644 --- a/merchant-sdk/sdk/src/main/java/net/gini/android/merchant/sdk/util/DisplayedScreen.kt +++ b/merchant-sdk/sdk/src/main/java/net/gini/android/merchant/sdk/util/DisplayedScreen.kt @@ -1,13 +1,47 @@ package net.gini.android.merchant.sdk.util +/** + * Represents the currently visible screen presented in [PaymentFragment] + */ sealed class DisplayedScreen { + /** + * Default state - nothing visible + */ object Nothing : DisplayedScreen() + + /** + * Entrypoint to the payment flow - shows which bank is selected + * (or prompts the user to choose a payment provider if there is no previous selection) for making the payment. + */ object PaymentComponentBottomSheet : DisplayedScreen() + + /** + * Bottom sheet for selecting a bank to pay with. + */ object BankSelectionBottomSheet : DisplayedScreen() + + /** + * More information screen. + */ object MoreInformationFragment : DisplayedScreen() - object ReviewFragment : DisplayedScreen() + + /** + * Prompt for the user to install the selected banking app. + */ object InstallAppBottomSheet: DisplayedScreen() + + /** + * Prompt for the user to select another application to share the payment request through. + */ object OpenWithBottomSheet: DisplayedScreen() + + /** + * OS native share sheet. + */ object ShareSheet: DisplayedScreen() + + /** + * Payment details review screen. + */ object ReviewBottomSheet: DisplayedScreen() } \ No newline at end of file diff --git a/merchant-sdk/sdk/src/main/java/net/gini/android/merchant/sdk/util/GiniPaymentManager.kt b/merchant-sdk/sdk/src/main/java/net/gini/android/merchant/sdk/util/GiniPaymentManager.kt index 3b71b7ec96..79e159563d 100644 --- a/merchant-sdk/sdk/src/main/java/net/gini/android/merchant/sdk/util/GiniPaymentManager.kt +++ b/merchant-sdk/sdk/src/main/java/net/gini/android/merchant/sdk/util/GiniPaymentManager.kt @@ -5,11 +5,9 @@ import kotlinx.coroutines.flow.StateFlow import net.gini.android.core.api.Resource import net.gini.android.health.api.models.PaymentRequestInput import net.gini.android.merchant.sdk.GiniMerchant -import net.gini.android.merchant.sdk.api.ResultWrapper import net.gini.android.merchant.sdk.api.payment.model.PaymentDetails import net.gini.android.merchant.sdk.api.payment.model.PaymentRequest import net.gini.android.merchant.sdk.api.payment.model.toPaymentRequest -import net.gini.android.merchant.sdk.api.payment.model.withFeedback import net.gini.android.merchant.sdk.paymentprovider.PaymentProviderApp import net.gini.android.merchant.sdk.review.ValidationMessage import net.gini.android.merchant.sdk.review.validate diff --git a/merchant-sdk/sdk/src/main/java/net/gini/android/merchant/sdk/util/GmsBottomSheetDialogFragment.kt b/merchant-sdk/sdk/src/main/java/net/gini/android/merchant/sdk/util/GmsBottomSheetDialogFragment.kt index b30828b78e..f0e4222409 100644 --- a/merchant-sdk/sdk/src/main/java/net/gini/android/merchant/sdk/util/GmsBottomSheetDialogFragment.kt +++ b/merchant-sdk/sdk/src/main/java/net/gini/android/merchant/sdk/util/GmsBottomSheetDialogFragment.kt @@ -11,7 +11,7 @@ import com.google.android.material.bottomsheet.BottomSheetDialogFragment import net.gini.android.merchant.sdk.R -open class GmsBottomSheetDialogFragment: BottomSheetDialogFragment() { +internal open class GmsBottomSheetDialogFragment: BottomSheetDialogFragment() { override fun onGetLayoutInflater(savedInstanceState: Bundle?): LayoutInflater { val inflater = super.onGetLayoutInflater(savedInstanceState) return this.getLayoutInflaterWithGiniMerchantTheme(inflater) diff --git a/merchant-sdk/sdk/src/main/java/net/gini/android/merchant/sdk/util/extensions/File.kt b/merchant-sdk/sdk/src/main/java/net/gini/android/merchant/sdk/util/extensions/File.kt index 1062b65826..1442c2d0a7 100644 --- a/merchant-sdk/sdk/src/main/java/net/gini/android/merchant/sdk/util/extensions/File.kt +++ b/merchant-sdk/sdk/src/main/java/net/gini/android/merchant/sdk/util/extensions/File.kt @@ -2,9 +2,8 @@ package net.gini.android.merchant.sdk.util.extensions import java.io.File import java.io.FileOutputStream -import java.io.IOException -fun File.createTempPdfFile(byteArray: ByteArray, fileName: String): File { +internal fun File.createTempPdfFile(byteArray: ByteArray, fileName: String): File { val file = File("${this.path}/", "${fileName}.pdf") FileOutputStream(file, false).use { outputStream -> diff --git a/merchant-sdk/sdk/src/main/java/net/gini/android/merchant/sdk/util/extensions/Fragment.kt b/merchant-sdk/sdk/src/main/java/net/gini/android/merchant/sdk/util/extensions/Fragment.kt index 5834a48f2d..09b82a10e6 100644 --- a/merchant-sdk/sdk/src/main/java/net/gini/android/merchant/sdk/util/extensions/Fragment.kt +++ b/merchant-sdk/sdk/src/main/java/net/gini/android/merchant/sdk/util/extensions/Fragment.kt @@ -6,7 +6,7 @@ import androidx.core.content.FileProvider import androidx.fragment.app.Fragment import java.io.File -fun Fragment.startSharePdfIntent(file: File, pendingIntent: PendingIntent? = null) { +internal fun Fragment.startSharePdfIntent(file: File, pendingIntent: PendingIntent? = null) { val uriForFile = FileProvider.getUriForFile( requireContext(), requireContext().packageName + ".merchant.sdk.fileprovider", diff --git a/merchant-sdk/sdk/src/main/java/net/gini/android/merchant/sdk/util/extensions/Resource.kt b/merchant-sdk/sdk/src/main/java/net/gini/android/merchant/sdk/util/extensions/Resource.kt deleted file mode 100644 index 440ed1e9f1..0000000000 --- a/merchant-sdk/sdk/src/main/java/net/gini/android/merchant/sdk/util/extensions/Resource.kt +++ /dev/null @@ -1,15 +0,0 @@ -package net.gini.android.merchant.sdk.util.extensions - -import net.gini.android.core.api.Resource.Error - -fun Error.toException(): Exception { - return if (exception != null && message != null) { - Exception(message, exception) - } else if (message != null) { - Exception(message) - } else if (exception != null) { - Exception(exception) - } else { - Exception("Unknown error") - } -} \ No newline at end of file diff --git a/merchant-sdk/sdk/src/main/java/net/gini/android/merchant/sdk/util/extensions/SupportFragmentManager.kt b/merchant-sdk/sdk/src/main/java/net/gini/android/merchant/sdk/util/extensions/SupportFragmentManager.kt index 3b823a144d..d7bc37da45 100644 --- a/merchant-sdk/sdk/src/main/java/net/gini/android/merchant/sdk/util/extensions/SupportFragmentManager.kt +++ b/merchant-sdk/sdk/src/main/java/net/gini/android/merchant/sdk/util/extensions/SupportFragmentManager.kt @@ -11,7 +11,7 @@ import net.gini.android.merchant.sdk.review.openWith.OpenWithBottomSheet import net.gini.android.merchant.sdk.review.openWith.OpenWithForwardListener import net.gini.android.merchant.sdk.util.BackListener -fun FragmentManager.add(@IdRes containerId: Int, fragment: Fragment, addToBackStack: Boolean) { +internal fun FragmentManager.add(@IdRes containerId: Int, fragment: Fragment, addToBackStack: Boolean) { beginTransaction() .add(containerId, fragment, fragment::class.java.name) .apply { if (addToBackStack) addToBackStack(fragment::class.java.name) } diff --git a/merchant-sdk/sdk/src/test/java/net/gini/android/merchant/sdk/integratedFlow/PaymentFlowFragmentTest.kt b/merchant-sdk/sdk/src/test/java/net/gini/android/merchant/sdk/integratedFlow/PaymentFlowFragmentTest.kt index 129dec018f..b9bd8367a0 100644 --- a/merchant-sdk/sdk/src/test/java/net/gini/android/merchant/sdk/integratedFlow/PaymentFlowFragmentTest.kt +++ b/merchant-sdk/sdk/src/test/java/net/gini/android/merchant/sdk/integratedFlow/PaymentFlowFragmentTest.kt @@ -84,7 +84,6 @@ class PaymentFlowFragmentTest { @Test fun `shows review fragment when payment button is tapped and config allows showing of review fragment`() = runTest { // Given - every { paymentFlowViewModel!!.addToBackStack(DisplayedScreen.ReviewFragment) } returns Unit every { paymentFlowViewModel!!.paymentFlowConfiguration!!.shouldHandleErrorsInternally } returns false every { paymentFlowViewModel!!.paymentFlowConfiguration!!.shouldShowReviewFragment } returns true every { paymentFlowViewModel!!.paymentFlowConfiguration!!.isAmountFieldEditable } returns false @@ -116,7 +115,6 @@ class PaymentFlowFragmentTest { @Test fun `updates payment details when payment button is tapped on the review fragment`() = runTest { // Given - every { paymentFlowViewModel!!.addToBackStack(DisplayedScreen.ReviewFragment) } returns Unit every { paymentFlowViewModel!!.paymentFlowConfiguration!!.shouldHandleErrorsInternally } returns false every { paymentFlowViewModel!!.paymentFlowConfiguration!!.shouldShowReviewFragment } returns true every { paymentFlowViewModel!!.paymentFlowConfiguration!!.isAmountFieldEditable } returns false diff --git a/merchant-sdk/sdk/src/test/java/net/gini/android/merchant/sdk/integratedFlow/PaymentFlowViewModelTest.kt b/merchant-sdk/sdk/src/test/java/net/gini/android/merchant/sdk/integratedFlow/PaymentFlowViewModelTest.kt index 877bdf3e4a..3e6355c009 100644 --- a/merchant-sdk/sdk/src/test/java/net/gini/android/merchant/sdk/integratedFlow/PaymentFlowViewModelTest.kt +++ b/merchant-sdk/sdk/src/test/java/net/gini/android/merchant/sdk/integratedFlow/PaymentFlowViewModelTest.kt @@ -210,20 +210,6 @@ class PaymentFlowViewModelTest { assertThat(viewModel.getPaymentProviderApp()?.paymentProvider?.id).isEqualTo(changedPaymentProviderApp.paymentProvider.id) } - @Test - fun `forwards on bank opened event to giniMerchant`() = runTest { - val viewModel = PaymentFlowViewModel( - paymentComponent = paymentComponent!!, - paymentDetails = PaymentDetails("", "", "", ""), - paymentFlowConfiguration = null, - giniMerchant = giniMerchant!!, - giniPaymentManager = giniPayment!! - ) - - viewModel.onBankOpened() - verify(exactly = 1) { giniMerchant!!.emitSDKEvent(any()) } - } - @Test fun `emits share with started event`() = runTest { val viewModel = PaymentFlowViewModel( diff --git a/merchant-sdk/sdk/src/test/java/net/gini/android/merchant/sdk/paymentprovider/PaymentProviderAppTest.kt b/merchant-sdk/sdk/src/test/java/net/gini/android/merchant/sdk/paymentprovider/PaymentProviderAppTest.kt index f47f04e549..ecf9877674 100644 --- a/merchant-sdk/sdk/src/test/java/net/gini/android/merchant/sdk/paymentprovider/PaymentProviderAppTest.kt +++ b/merchant-sdk/sdk/src/test/java/net/gini/android/merchant/sdk/paymentprovider/PaymentProviderAppTest.kt @@ -22,7 +22,7 @@ import org.junit.runner.RunWith * Copyright (c) 2021 Gini GmbH. */ -val installedPaymentProviderAppsFixture = listOf( +private val installedPaymentProviderAppsFixture = listOf( InstalledPaymentProviderApp( packageName = "net.gini.android.bank.exampleapp1", version = "1.2.3", diff --git a/merchant-sdk/sdk/src/test/java/net/gini/android/merchant/sdk/review/model/PaymentDetailsTest.kt b/merchant-sdk/sdk/src/test/java/net/gini/android/merchant/sdk/review/model/PaymentDetailsTest.kt index 10b8beda89..b8142bdaac 100644 --- a/merchant-sdk/sdk/src/test/java/net/gini/android/merchant/sdk/review/model/PaymentDetailsTest.kt +++ b/merchant-sdk/sdk/src/test/java/net/gini/android/merchant/sdk/review/model/PaymentDetailsTest.kt @@ -1,16 +1,11 @@ package net.gini.android.merchant.sdk.review.model -import net.gini.android.core.api.models.CompoundExtraction -import net.gini.android.core.api.models.ExtractionsContainer -import net.gini.android.core.api.models.SpecificExtraction +import com.google.common.truth.Truth.assertThat import net.gini.android.merchant.sdk.api.payment.model.PaymentDetails -import net.gini.android.merchant.sdk.api.payment.model.getPaymentExtraction -import net.gini.android.merchant.sdk.api.payment.model.toPaymentDetails -import net.gini.android.merchant.sdk.api.payment.model.withFeedback -import org.junit.Assert.assertEquals +import net.gini.android.merchant.sdk.api.payment.model.overwriteEmptyFields import org.junit.Before import org.junit.Test -import java.util.* +import java.util.Locale /** * Created by Alpár Szotyori on 01.02.22. @@ -25,141 +20,50 @@ class PaymentDetailsTest { } @Test - fun `Updates payment extractions with payment detail values for feedback`() { + fun `Overwrites empty fields`() { // Given - val compoundExtractions = mutableMapOf("payment" to CompoundExtraction( - "payment", - listOf(mapOf( - "payment_recipient" to SpecificExtraction( - "payment_recipient", - "John Doe", - "", null, emptyList() - ), - "iban" to SpecificExtraction( - "iban", - "DE123456789", - "", null, emptyList() - ), - "amount_to_pay" to SpecificExtraction( - "amount_to_pay", - "1.11:EUR", - "", null, emptyList() - ), - "payment_purpose" to SpecificExtraction( - "payment_purpose", - "Testing", - "", null, emptyList() - ) - )) - )) - val paymentDetails = PaymentDetails( - recipient = "Jack Vance", - iban = "DE987654321", - amount = "9.99:EUR", - purpose = "Still testing" + recipient = "", + iban = "iban", + amount = "30", + purpose = "payment" ) - // When - compoundExtractions.withFeedback(paymentDetails) - - // Then - assertEquals(paymentDetails.recipient, compoundExtractions.getPaymentExtraction("payment_recipient")?.value) - assertEquals(paymentDetails.iban, compoundExtractions.getPaymentExtraction("iban")?.value) - assertEquals(paymentDetails.amount, compoundExtractions.getPaymentExtraction("amount_to_pay")?.value) - assertEquals(paymentDetails.purpose, compoundExtractions.getPaymentExtraction("payment_purpose")?.value) - } - - @Test - fun `Adds missing payment extraction when updating payment detail values for feedback`() { - // Given - val compoundExtractions = mutableMapOf("payment" to CompoundExtraction( - "payment", - listOf(mapOf( - "iban" to SpecificExtraction( - "iban", - "DE123456789", - "", null, emptyList() - ), - "amount_to_pay" to SpecificExtraction( - "amount_to_pay", - "1.11:EUR", - "", null, emptyList() - ), - "payment_purpose" to SpecificExtraction( - "payment_purpose", - "Testing", - "", null, emptyList() - ) - )) - )) - - val paymentDetails = PaymentDetails( - recipient = "Jack Vance", - iban = "DE987654321", - amount = "9.99:EUR", - purpose = "Still testing" + val nonEmptyPaymentFields = PaymentDetails( + recipient = "recipient", + iban = "iban", + amount = "30", + purpose = "payment" ) // When - compoundExtractions.withFeedback(paymentDetails) + val newPaymentDetails = paymentDetails.overwriteEmptyFields(nonEmptyPaymentFields) // Then - assertEquals(paymentDetails.recipient, compoundExtractions.getPaymentExtraction("payment_recipient")?.value) - assertEquals(paymentDetails.iban, compoundExtractions.getPaymentExtraction("iban")?.value) - assertEquals(paymentDetails.amount, compoundExtractions.getPaymentExtraction("amount_to_pay")?.value) - assertEquals(paymentDetails.purpose, compoundExtractions.getPaymentExtraction("payment_purpose")?.value) + assertThat(newPaymentDetails.recipient).isEqualTo("recipient") } @Test - fun `Converts amount_to_pay to backend format when updating payment detail values for feedback`() { + fun `Keeps original value if field not empty`() { // Given - val compoundExtractions = mutableMapOf("payment" to CompoundExtraction( - "payment", - listOf(mapOf( - "amount_to_pay" to SpecificExtraction( - "amount_to_pay", - "1.11:EUR", - "", null, emptyList() - ) - )) - )) - val paymentDetails = PaymentDetails( - recipient = "Jack Vance", - iban = "DE987654321", - amount = "9.99", - purpose = "Still testing" + recipient = "", + iban = "iban", + amount = "30", + purpose = "payment" ) - // When - compoundExtractions.withFeedback(paymentDetails) - - // Then - assertEquals("9.99:EUR", compoundExtractions.getPaymentExtraction("amount_to_pay")?.value) - } - - @Test - fun `Converts amount_to_pay extraction value to number`() { - // Given - val extractionsContainer = ExtractionsContainer( - emptyMap(), - mutableMapOf("payment" to CompoundExtraction( - "payment", - listOf(mapOf( - "amount_to_pay" to SpecificExtraction( - "amount_to_pay", - "1.11:EUR", - "", null, emptyList() - ) - )) - )) + val nonEmptyPaymentFields = PaymentDetails( + recipient = "recipient", + iban = "iban2", + amount = "30", + purpose = "payment" ) // When - val paymentDetails = extractionsContainer.toPaymentDetails() + val newPaymentDetails = paymentDetails.overwriteEmptyFields(nonEmptyPaymentFields) // Then - assertEquals("1.11", paymentDetails.amount) + assertThat(newPaymentDetails.iban).isEqualTo("iban") } } \ No newline at end of file diff --git a/merchant-sdk/sdk/src/test/java/net/gini/android/merchant/sdk/util/GiniPaymentTest.kt b/merchant-sdk/sdk/src/test/java/net/gini/android/merchant/sdk/util/GiniPaymentTest.kt index a1d3eea2e6..dd769fd8db 100644 --- a/merchant-sdk/sdk/src/test/java/net/gini/android/merchant/sdk/util/GiniPaymentTest.kt +++ b/merchant-sdk/sdk/src/test/java/net/gini/android/merchant/sdk/util/GiniPaymentTest.kt @@ -61,7 +61,6 @@ class GiniPaymentTest { iban = "iban", amount = "20", purpose = "purpose", - extractions = null ) @Before