Skip to content

Commit 9f52b71

Browse files
feat(ad-hoc): Tokenize nAPM flow in example app (#304)
1 parent a6409ca commit 9f52b71

10 files changed

Lines changed: 214 additions & 87 deletions

File tree

example/src/main/kotlin/com/processout/example/ui/screen/nativeapm/NativeApmFragment.kt

Lines changed: 47 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import com.processout.example.ui.screen.nativeapm.NativeApmUiState.*
1616
import com.processout.sdk.core.onFailure
1717
import com.processout.sdk.core.onSuccess
1818
import com.processout.sdk.ui.napm.PONativeAlternativePaymentConfiguration
19-
import com.processout.sdk.ui.napm.PONativeAlternativePaymentConfiguration.Button
19+
import com.processout.sdk.ui.napm.PONativeAlternativePaymentConfiguration.*
2020
import com.processout.sdk.ui.napm.PONativeAlternativePaymentLauncher
2121
import com.processout.sdk.ui.napm.delegate.PONativeAlternativePaymentDelegate
2222
import com.processout.sdk.ui.nativeapm.PONativeAlternativePaymentMethodConfiguration
@@ -73,18 +73,26 @@ class NativeApmFragment : BaseFragment<FragmentNativeApmBinding>(
7373
}
7474

7575
private fun setOnClickListeners() {
76-
binding.launchNativeApmButton.setOnClickListener {
77-
onSubmitClick(launchCompose = false)
76+
binding.buttonAuthorizeLegacy.setOnClickListener {
77+
onSubmitClick(launchCompose = false, tokenize = false)
7878
}
79-
binding.launchNativeApmComposeButton.setOnClickListener {
80-
onSubmitClick(launchCompose = true)
79+
binding.buttonAuthorizeCompose.setOnClickListener {
80+
onSubmitClick(launchCompose = true, tokenize = false)
81+
}
82+
binding.buttonTokenizeCompose.setOnClickListener {
83+
onSubmitClick(launchCompose = true, tokenize = true)
8184
}
8285
}
8386

84-
private fun onSubmitClick(launchCompose: Boolean) {
87+
private fun onSubmitClick(launchCompose: Boolean, tokenize: Boolean) {
8588
val amount = binding.amountInput.text.toString()
8689
val currency = binding.currencyInput.text.toString()
87-
viewModel.createInvoice(amount, currency, launchCompose)
90+
viewModel.createInvoice(
91+
amount = amount,
92+
currency = currency,
93+
launchCompose = launchCompose,
94+
tokenize = tokenize
95+
)
8896
}
8997

9098
private fun handle(uiState: NativeApmUiState) {
@@ -98,13 +106,36 @@ class NativeApmFragment : BaseFragment<FragmentNativeApmBinding>(
98106

99107
private fun launch(uiModel: NativeApmUiModel) {
100108
if (uiModel.launchCompose) {
101-
launcherCompose.launch(
102-
PONativeAlternativePaymentConfiguration(
103-
invoiceId = uiModel.invoiceId,
104-
gatewayConfigurationId = uiModel.gatewayConfigurationId,
105-
submitButton = Button()
109+
if (uiModel.tokenize) {
110+
launcherCompose.launch(
111+
PONativeAlternativePaymentConfiguration(
112+
flow = Flow.Tokenization(
113+
customerId = uiModel.customerId,
114+
customerTokenId = uiModel.customerTokenId,
115+
gatewayConfigurationId = uiModel.gatewayConfigurationId
116+
),
117+
cancelButton = CancelButton(),
118+
paymentConfirmation = PaymentConfirmationConfiguration(
119+
confirmButton = Button(),
120+
cancelButton = CancelButton(disabledForSeconds = 3)
121+
)
122+
)
106123
)
107-
)
124+
} else {
125+
launcherCompose.launch(
126+
PONativeAlternativePaymentConfiguration(
127+
flow = Flow.Authorization(
128+
invoiceId = uiModel.invoiceId,
129+
gatewayConfigurationId = uiModel.gatewayConfigurationId
130+
),
131+
cancelButton = CancelButton(),
132+
paymentConfirmation = PaymentConfirmationConfiguration(
133+
confirmButton = Button(),
134+
cancelButton = CancelButton(disabledForSeconds = 3)
135+
)
136+
)
137+
)
138+
}
108139
} else {
109140
launcher.launch(
110141
PONativeAlternativePaymentMethodConfiguration(
@@ -128,8 +159,9 @@ class NativeApmFragment : BaseFragment<FragmentNativeApmBinding>(
128159
with(binding) {
129160
amountInput.isEnabled = isEnabled
130161
currencyInput.isEnabled = isEnabled
131-
launchNativeApmButton.isClickable = isEnabled
132-
launchNativeApmComposeButton.isClickable = isEnabled
162+
buttonAuthorizeLegacy.isClickable = isEnabled
163+
buttonAuthorizeCompose.isClickable = isEnabled
164+
buttonTokenizeCompose.isClickable = isEnabled
133165
amountInput.clearFocus()
134166
currencyInput.clearFocus()
135167
}

example/src/main/kotlin/com/processout/example/ui/screen/nativeapm/NativeApmUiState.kt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,8 @@ sealed class NativeApmUiState {
1313
data class NativeApmUiModel(
1414
val invoiceId: String,
1515
val gatewayConfigurationId: String,
16-
val launchCompose: Boolean
16+
val customerId: String,
17+
val customerTokenId: String,
18+
val launchCompose: Boolean,
19+
val tokenize: Boolean
1720
)

example/src/main/kotlin/com/processout/example/ui/screen/nativeapm/NativeApmViewModel.kt

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,11 @@ import androidx.lifecycle.viewModelScope
66
import com.processout.example.ui.screen.nativeapm.NativeApmUiState.*
77
import com.processout.sdk.api.ProcessOut
88
import com.processout.sdk.api.model.request.POCreateCustomerRequest
9+
import com.processout.sdk.api.model.request.POCreateCustomerTokenRequest
10+
import com.processout.sdk.api.model.request.POCreateCustomerTokenRequestBody
911
import com.processout.sdk.api.model.request.POCreateInvoiceRequest
1012
import com.processout.sdk.api.model.response.POCustomer
13+
import com.processout.sdk.api.model.response.POCustomerToken
1114
import com.processout.sdk.api.service.POCustomerTokensService
1215
import com.processout.sdk.api.service.POInvoicesService
1316
import com.processout.sdk.core.getOrNull
@@ -44,23 +47,29 @@ class NativeApmViewModel(
4447
fun createInvoice(
4548
amount: String,
4649
currency: String,
47-
launchCompose: Boolean
50+
launchCompose: Boolean,
51+
tokenize: Boolean
4852
) {
4953
_uiState.value = Submitting
5054
viewModelScope.launch {
55+
val customerId = createCustomer()?.id ?: String()
56+
val customerTokenId = createCustomerToken(customerId)?.id ?: String()
5157
val request = POCreateInvoiceRequest(
5258
name = UUID.randomUUID().toString(),
5359
amount = amount,
5460
currency = currency,
55-
customerId = createCustomer()?.id
61+
customerId = customerId
5662
)
5763
invoices.createInvoice(request)
5864
.onSuccess { invoice ->
5965
_uiState.value = Submitted(
6066
NativeApmUiModel(
6167
invoiceId = invoice.id,
6268
gatewayConfigurationId = gatewayConfigurationId,
63-
launchCompose = launchCompose
69+
customerId = customerId,
70+
customerTokenId = customerTokenId,
71+
launchCompose = launchCompose,
72+
tokenize = tokenize
6473
)
6574
)
6675
}.onFailure { _uiState.value = Failure(it) }
@@ -76,6 +85,14 @@ class NativeApmViewModel(
7685
)
7786
).getOrNull()
7887

88+
private suspend fun createCustomerToken(customerId: String): POCustomerToken? =
89+
customerTokens.createCustomerToken(
90+
POCreateCustomerTokenRequest(
91+
customerId = customerId,
92+
body = POCreateCustomerTokenRequestBody()
93+
)
94+
).getOrNull()
95+
7996
fun onLaunched() {
8097
_uiState.value = Launched
8198
}

example/src/main/res/layout/fragment_native_apm.xml

Lines changed: 40 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
android:defaultFocusHighlightEnabled="false"
1515
android:fillViewport="true"
1616
android:isScrollContainer="true"
17-
app:layout_constraintBottom_toTopOf="@id/footer_divider"
17+
app:layout_constraintBottom_toTopOf="@id/buttons"
1818
app:layout_constraintLeft_toLeftOf="parent"
1919
app:layout_constraintRight_toRightOf="parent"
2020
app:layout_constraintTop_toTopOf="parent"
@@ -76,38 +76,48 @@
7676
</LinearLayout>
7777
</androidx.core.widget.NestedScrollView>
7878

79-
<View
80-
android:id="@+id/footer_divider"
81-
android:layout_width="match_parent"
82-
android:layout_height="@dimen/po_borderWidth"
83-
android:layout_marginBottom="@dimen/button_marginVertical"
84-
android:background="@color/po_border_subtle"
85-
app:layout_constraintBottom_toTopOf="@id/launch_native_apm_button"
86-
app:layout_constraintLeft_toLeftOf="parent"
87-
app:layout_constraintRight_toRightOf="parent" />
88-
89-
<Button
90-
android:id="@+id/launch_native_apm_button"
79+
<LinearLayout
80+
android:id="@+id/buttons"
9181
android:layout_width="match_parent"
9282
android:layout_height="wrap_content"
93-
android:layout_marginStart="@dimen/content_padding"
94-
android:layout_marginEnd="@dimen/content_padding"
95-
android:layout_marginBottom="@dimen/button_space_vertical"
96-
android:text="@string/launch_native_apm"
97-
app:layout_constraintBottom_toTopOf="@id/launch_native_apm_compose_button"
98-
app:layout_constraintLeft_toLeftOf="parent"
99-
app:layout_constraintRight_toRightOf="parent" />
100-
101-
<Button
102-
android:id="@+id/launch_native_apm_compose_button"
103-
android:layout_width="match_parent"
104-
android:layout_height="wrap_content"
105-
android:layout_marginStart="@dimen/content_padding"
106-
android:layout_marginEnd="@dimen/content_padding"
107-
android:layout_marginBottom="@dimen/button_marginVertical"
108-
android:text="@string/launch_native_apm_compose"
83+
android:orientation="vertical"
10984
app:layout_constraintBottom_toBottomOf="parent"
11085
app:layout_constraintLeft_toLeftOf="parent"
111-
app:layout_constraintRight_toRightOf="parent" />
86+
app:layout_constraintRight_toRightOf="parent">
87+
88+
<View
89+
android:id="@+id/footer_divider"
90+
android:layout_width="match_parent"
91+
android:layout_height="@dimen/po_borderWidth"
92+
android:layout_marginBottom="@dimen/button_marginVertical"
93+
android:background="@color/po_border_subtle" />
94+
95+
<Button
96+
android:id="@+id/button_authorize_legacy"
97+
android:layout_width="match_parent"
98+
android:layout_height="wrap_content"
99+
android:layout_marginStart="@dimen/content_padding"
100+
android:layout_marginEnd="@dimen/content_padding"
101+
android:layout_marginBottom="@dimen/button_space_vertical"
102+
android:text="Authorize (Legacy)" />
103+
104+
<Button
105+
android:id="@+id/button_authorize_compose"
106+
android:layout_width="match_parent"
107+
android:layout_height="wrap_content"
108+
android:layout_marginStart="@dimen/content_padding"
109+
android:layout_marginEnd="@dimen/content_padding"
110+
android:layout_marginBottom="@dimen/button_marginVertical"
111+
android:text="Authorize (Compose)" />
112+
113+
<Button
114+
android:id="@+id/button_tokenize_compose"
115+
android:layout_width="match_parent"
116+
android:layout_height="wrap_content"
117+
android:layout_marginStart="@dimen/content_padding"
118+
android:layout_marginEnd="@dimen/content_padding"
119+
android:layout_marginBottom="@dimen/button_marginVertical"
120+
android:text="Tokenize (Compose)" />
121+
</LinearLayout>
112122

113123
</androidx.constraintlayout.widget.ConstraintLayout>

example/src/main/res/values/strings.xml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@
44

55
<string name="dynamic_checkout" translatable="false">Dynamic Checkout (Beta)</string>
66
<string name="native_apm" translatable="false">Native Alternative Payment</string>
7-
<string name="launch_native_apm" translatable="false">Launch Native APM</string>
8-
<string name="launch_native_apm_compose" translatable="false">Launch Native APM (Compose)</string>
97
<string name="authorize_invoice" translatable="false">Authorize Invoice</string>
108
<string name="invoice_details" translatable="false">Invoice Details</string>
119
<string name="amount" translatable="false">Amount</string>

ui/src/main/kotlin/com/processout/sdk/ui/checkout/DynamicCheckoutActivity.kt

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ import com.processout.sdk.ui.core.theme.ProcessOutTheme
4949
import com.processout.sdk.ui.googlepay.POGooglePayCardTokenizationLauncher
5050
import com.processout.sdk.ui.napm.NativeAlternativePaymentViewModel
5151
import com.processout.sdk.ui.napm.PONativeAlternativePaymentConfiguration
52+
import com.processout.sdk.ui.napm.PONativeAlternativePaymentConfiguration.Flow
5253
import com.processout.sdk.ui.napm.PONativeAlternativePaymentConfiguration.PaymentConfirmationConfiguration
5354
import com.processout.sdk.ui.savedpaymentmethods.POSavedPaymentMethodsLauncher
5455
import com.processout.sdk.ui.savedpaymentmethods.delegate.POSavedPaymentMethodsDelegate
@@ -128,8 +129,10 @@ internal class DynamicCheckoutActivity : BaseTransparentPortraitActivity() {
128129
private fun nativeAlternativePaymentConfiguration(): PONativeAlternativePaymentConfiguration {
129130
val paymentConfirmation = configuration.alternativePayment.paymentConfirmation
130131
return PONativeAlternativePaymentConfiguration(
131-
invoiceId = configuration.invoiceRequest.invoiceId,
132-
gatewayConfigurationId = String(),
132+
flow = Flow.Authorization(
133+
invoiceId = configuration.invoiceRequest.invoiceId,
134+
gatewayConfigurationId = String()
135+
),
133136
submitButton = configuration.submitButton.map(),
134137
cancelButton = configuration.cancelButton?.map(),
135138
paymentConfirmation = PaymentConfirmationConfiguration(

ui/src/main/kotlin/com/processout/sdk/ui/checkout/DynamicCheckoutInteractor.kt

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ import com.processout.sdk.ui.checkout.delegate.*
5656
import com.processout.sdk.ui.checkout.delegate.PODynamicCheckoutEvent.*
5757
import com.processout.sdk.ui.napm.*
5858
import com.processout.sdk.ui.napm.PONativeAlternativePaymentConfiguration.*
59+
import com.processout.sdk.ui.napm.PONativeAlternativePaymentConfiguration.Flow
5960
import com.processout.sdk.ui.napm.delegate.PONativeAlternativePaymentEvent
6061
import com.processout.sdk.ui.savedpaymentmethods.POSavedPaymentMethodsConfiguration
6162
import com.processout.sdk.ui.shared.extension.orElse
@@ -531,8 +532,10 @@ internal class DynamicCheckoutInteractor(
531532
gatewayConfigurationId: String,
532533
configuration: AlternativePaymentConfiguration
533534
) = copy(
534-
invoiceId = invoiceId,
535-
gatewayConfigurationId = gatewayConfigurationId,
535+
flow = Flow.Authorization(
536+
invoiceId = invoiceId,
537+
gatewayConfigurationId = gatewayConfigurationId
538+
),
536539
paymentConfirmation = paymentConfirmation.apply(configuration.paymentConfirmation),
537540
barcode = configuration.barcode,
538541
inlineSingleSelectValuesLimit = configuration.inlineSingleSelectValuesLimit

ui/src/main/kotlin/com/processout/sdk/ui/napm/NativeAlternativePaymentBottomSheet.kt

Lines changed: 9 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,10 @@ import androidx.core.content.ContextCompat
1616
import androidx.fragment.app.viewModels
1717
import androidx.lifecycle.compose.collectAsStateWithLifecycle
1818
import com.processout.sdk.api.dispatcher.PODefaultEventDispatchers
19-
import com.processout.sdk.core.*
19+
import com.processout.sdk.core.POUnit
20+
import com.processout.sdk.core.ProcessOutActivityResult
21+
import com.processout.sdk.core.ProcessOutResult
22+
import com.processout.sdk.core.toActivityResult
2023
import com.processout.sdk.ui.base.BaseBottomSheetDialogFragment
2124
import com.processout.sdk.ui.core.component.POIme.isImeVisibleAsState
2225
import com.processout.sdk.ui.core.theme.ProcessOutTheme
@@ -29,7 +32,7 @@ import com.processout.sdk.ui.napm.NativeAlternativePaymentEvent.PermissionReques
2932
import com.processout.sdk.ui.napm.NativeAlternativePaymentScreen.AnimationDurationMillis
3033
import com.processout.sdk.ui.napm.NativeAlternativePaymentSideEffect.PermissionRequest
3134
import com.processout.sdk.ui.napm.NativeAlternativePaymentViewModelState.Capture
32-
import com.processout.sdk.ui.napm.PONativeAlternativePaymentConfiguration.Button
35+
import com.processout.sdk.ui.napm.PONativeAlternativePaymentConfiguration.Flow
3336
import com.processout.sdk.ui.shared.component.screenModeAsState
3437
import com.processout.sdk.ui.shared.extension.collectImmediately
3538
import com.processout.sdk.ui.shared.extension.dpToPx
@@ -52,9 +55,10 @@ internal class NativeAlternativePaymentBottomSheet : BaseBottomSheetDialogFragme
5255
NativeAlternativePaymentViewModel.Factory(
5356
app = requireActivity().application,
5457
configuration = configuration ?: PONativeAlternativePaymentConfiguration(
55-
invoiceId = String(),
56-
gatewayConfigurationId = String(),
57-
submitButton = Button()
58+
flow = Flow.Authorization(
59+
invoiceId = String(),
60+
gatewayConfigurationId = String()
61+
)
5862
),
5963
legacyEventDispatcher = PODefaultEventDispatchers.defaultNativeAlternativePaymentMethod
6064
)
@@ -69,17 +73,6 @@ internal class NativeAlternativePaymentBottomSheet : BaseBottomSheetDialogFragme
6973
super.onAttach(context)
7074
@Suppress("DEPRECATION")
7175
configuration = arguments?.getParcelable(EXTRA_CONFIGURATION)
72-
configuration?.run {
73-
if (invoiceId.isBlank() || gatewayConfigurationId.isBlank()) {
74-
dismiss(
75-
ProcessOutResult.Failure(
76-
code = POFailure.Code.Generic(),
77-
message = "Invalid configuration: 'invoiceId' and 'gatewayConfigurationId' is required."
78-
)
79-
)
80-
return
81-
}
82-
}
8376
viewModel.start()
8477
}
8578

0 commit comments

Comments
 (0)