From 805d9865ec153d0ebb5d1a8137bb4ef05169cd1c Mon Sep 17 00:00:00 2001 From: AnasNaouchi Date: Thu, 23 Jan 2025 12:14:11 +0700 Subject: [PATCH 1/5] Remove files to be replaced by flutter bridging --- app/src/main/AndroidManifest.xml | 1 - .../java/co/omise/android/CardNumber.java | 90 --- .../co/omise/android/ui/AtomeFormFragment.kt | 291 -------- .../co/omise/android/ui/CardNameEditText.kt | 21 - .../co/omise/android/ui/CountryListAdapter.kt | 64 -- .../android/ui/CountryListDialogFragment.kt | 73 -- .../co/omise/android/ui/CreditCardActivity.kt | 365 ---------- .../co/omise/android/ui/CreditCardEditText.kt | 118 ---- .../ui/DuitNowOBWBankChooserFragment.kt | 68 -- .../omise/android/ui/EContextFormFragment.kt | 142 ---- .../co/omise/android/ui/ExpiryDateEditText.kt | 194 ------ .../android/ui/ExpiryMonthSpinnerAdapter.java | 24 - .../android/ui/ExpiryYearSpinnerAdapter.java | 23 - .../android/ui/FpxBankChooserFragment.kt | 76 -- .../omise/android/ui/FpxEmailFormFragment.kt | 63 -- .../co/omise/android/ui/GooglePayActivity.kt | 357 ---------- .../android/ui/InstallmentChooserFragment.kt | 104 --- .../ui/InstallmentTermChooserFragment.kt | 165 ----- .../ui/InternetBankingChooserFragment.kt | 74 -- .../ui/MobileBankingChooserFragment.kt | 72 -- .../android/ui/NumberRangeSpinnerAdapter.java | 113 --- .../java/co/omise/android/ui/OmiseEditText.kt | 40 -- .../java/co/omise/android/ui/OmiseFragment.kt | 62 -- .../co/omise/android/ui/OmiseListFragment.kt | 207 ------ .../android/ui/PaymentChooserFragment.kt | 132 ---- .../android/ui/PaymentCreatorActivity.kt | 406 ----------- .../android/ui/PaymentMethodResources.kt | 651 ------------------ .../omise/android/ui/SecurityCodeEditText.kt | 40 -- .../ui/SecurityCodeTooltipDialogFragment.kt | 79 --- .../omise/android/ui/TrueMoneyFormFragment.kt | 90 --- .../main/res/layout/activity_credit_card.xml | 316 --------- .../main/res/layout/activity_google_pay.xml | 37 - .../main/res/layout/dialog_country_list.xml | 24 - .../layout/dialog_security_code_tooltip.xml | 38 - .../main/res/layout/fragment_atome_form.xml | 286 -------- .../res/layout/fragment_econtext_form.xml | 104 --- .../res/layout/fragment_fpx_email_form.xml | 60 -- sdk/src/main/res/layout/fragment_list.xml | 64 -- .../res/layout/fragment_true_money_form.xml | 68 -- sdk/src/main/res/layout/list_country_item.xml | 20 - sdk/src/main/res/layout/list_item.xml | 58 -- sdk/src/main/res/menu/menu_toolbar.xml | 10 - .../co/omise/android/api/OmiseEndpoints.kt | 4 +- 43 files changed, 2 insertions(+), 5292 deletions(-) delete mode 100644 sdk/src/main/java/co/omise/android/CardNumber.java delete mode 100644 sdk/src/main/java/co/omise/android/ui/AtomeFormFragment.kt delete mode 100644 sdk/src/main/java/co/omise/android/ui/CardNameEditText.kt delete mode 100644 sdk/src/main/java/co/omise/android/ui/CountryListAdapter.kt delete mode 100644 sdk/src/main/java/co/omise/android/ui/CountryListDialogFragment.kt delete mode 100644 sdk/src/main/java/co/omise/android/ui/CreditCardEditText.kt delete mode 100644 sdk/src/main/java/co/omise/android/ui/DuitNowOBWBankChooserFragment.kt delete mode 100644 sdk/src/main/java/co/omise/android/ui/EContextFormFragment.kt delete mode 100644 sdk/src/main/java/co/omise/android/ui/ExpiryDateEditText.kt delete mode 100644 sdk/src/main/java/co/omise/android/ui/ExpiryMonthSpinnerAdapter.java delete mode 100644 sdk/src/main/java/co/omise/android/ui/ExpiryYearSpinnerAdapter.java delete mode 100644 sdk/src/main/java/co/omise/android/ui/FpxBankChooserFragment.kt delete mode 100644 sdk/src/main/java/co/omise/android/ui/FpxEmailFormFragment.kt delete mode 100644 sdk/src/main/java/co/omise/android/ui/GooglePayActivity.kt delete mode 100644 sdk/src/main/java/co/omise/android/ui/InstallmentChooserFragment.kt delete mode 100644 sdk/src/main/java/co/omise/android/ui/InstallmentTermChooserFragment.kt delete mode 100644 sdk/src/main/java/co/omise/android/ui/InternetBankingChooserFragment.kt delete mode 100644 sdk/src/main/java/co/omise/android/ui/MobileBankingChooserFragment.kt delete mode 100644 sdk/src/main/java/co/omise/android/ui/NumberRangeSpinnerAdapter.java delete mode 100644 sdk/src/main/java/co/omise/android/ui/OmiseEditText.kt delete mode 100644 sdk/src/main/java/co/omise/android/ui/OmiseFragment.kt delete mode 100644 sdk/src/main/java/co/omise/android/ui/OmiseListFragment.kt delete mode 100644 sdk/src/main/java/co/omise/android/ui/PaymentChooserFragment.kt delete mode 100644 sdk/src/main/java/co/omise/android/ui/PaymentMethodResources.kt delete mode 100644 sdk/src/main/java/co/omise/android/ui/SecurityCodeEditText.kt delete mode 100644 sdk/src/main/java/co/omise/android/ui/SecurityCodeTooltipDialogFragment.kt delete mode 100644 sdk/src/main/java/co/omise/android/ui/TrueMoneyFormFragment.kt delete mode 100644 sdk/src/main/res/layout/activity_credit_card.xml delete mode 100644 sdk/src/main/res/layout/activity_google_pay.xml delete mode 100644 sdk/src/main/res/layout/dialog_country_list.xml delete mode 100644 sdk/src/main/res/layout/dialog_security_code_tooltip.xml delete mode 100644 sdk/src/main/res/layout/fragment_atome_form.xml delete mode 100644 sdk/src/main/res/layout/fragment_econtext_form.xml delete mode 100644 sdk/src/main/res/layout/fragment_fpx_email_form.xml delete mode 100644 sdk/src/main/res/layout/fragment_list.xml delete mode 100644 sdk/src/main/res/layout/fragment_true_money_form.xml delete mode 100644 sdk/src/main/res/layout/list_country_item.xml delete mode 100644 sdk/src/main/res/layout/list_item.xml delete mode 100644 sdk/src/main/res/menu/menu_toolbar.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index eb4b64c86..538230495 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -37,7 +37,6 @@ - diff --git a/sdk/src/main/java/co/omise/android/CardNumber.java b/sdk/src/main/java/co/omise/android/CardNumber.java deleted file mode 100644 index 3e667be2a..000000000 --- a/sdk/src/main/java/co/omise/android/CardNumber.java +++ /dev/null @@ -1,90 +0,0 @@ -package co.omise.android; - -import co.omise.android.models.CardBrand; - -/** - * CardNumber provides helper methods for working with primary account numbers (credit and debit card numbers). - */ -public final class CardNumber { - - /** - * Normalize removes spaces and other non-numerical characters from the input string. - * - * @param number The card number to normalize. - * @return Normalized string or an empty string if the input is null. - */ - public static String normalize(String number) { - if (number == null) return ""; - return number.replaceAll("[^0-9]", ""); - } - - /** - * Format formats the given string by adding a single whitespace between group of - * four digits. - * - * @param number The card number to format. - * @return The input string with every four digits grouped together, or an empty string if the input is null. - */ - public static String format(String number) { - if (number == null) return ""; - - StringBuilder builder = new StringBuilder(); - char[] chars = number.toCharArray(); - for (char ch : chars) { - if ('0' <= ch && ch <= '9') { - if ((builder.length() - 4) % 5 == 0) { - builder.append(' '); - } - builder.append(ch); - } - } - - return builder.toString(); - } - - /** - * Brand returns {@link CardBrand} of a credit card given a number. The result from this method is - * intended purely for displaying the brand on user interfaces and does not guarantee correctness. - * - * @param number The card number to check against. - * @return A {@link CardBrand}, or null if the brand could not be determined. - */ - public static CardBrand brand(String number) { - number = normalize(number); - for (CardBrand brand : CardBrand.ALL) { - if (brand.match(number)) return brand; - } - - return null; - } - - /** - * Luhn checks the input card number for validity using the - * Luhn algorithm. - * - * @param number The card number to check against. - * @return true if the given card number passes the Luhn check, otherwise false. - */ - public static boolean luhn(String number) { - number = normalize(number); - - char[] chars = number.toCharArray(); - int[] digits = new int[chars.length]; - for (int i = 0; i < chars.length; i++) { - digits[i] = chars[i] - '0'; - } - - int oddSum = 0, evenSum = 0; - for (int i = digits.length - 1; i >= 0; i -= 2) { - oddSum += digits[i]; - } - for (int i = digits.length - 2; i >= 0; i -= 2) { - evenSum += digits[i] * 2; - if (digits[i] > 4) { // doubles > 9 - evenSum -= 9; - } - } - - return (oddSum + evenSum) % 10 == 0; - } -} diff --git a/sdk/src/main/java/co/omise/android/ui/AtomeFormFragment.kt b/sdk/src/main/java/co/omise/android/ui/AtomeFormFragment.kt deleted file mode 100644 index 43d473f7b..000000000 --- a/sdk/src/main/java/co/omise/android/ui/AtomeFormFragment.kt +++ /dev/null @@ -1,291 +0,0 @@ -package co.omise.android.ui - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.View.GONE -import android.view.View.VISIBLE -import android.view.ViewGroup -import android.widget.Button -import android.widget.CheckBox -import co.omise.android.R -import co.omise.android.extensions.setOnAfterTextChangeListener -import co.omise.android.extensions.setOnClickListener -import co.omise.android.models.Billing -import co.omise.android.models.Item -import co.omise.android.models.Shipping -import co.omise.android.models.Source -import co.omise.android.models.SourceType -import kotlinx.android.synthetic.main.fragment_atome_form.billing_address -import kotlinx.android.synthetic.main.fragment_atome_form.button_submit -import kotlinx.android.synthetic.main.fragment_atome_form.checkbox_billing_shipping -import kotlinx.android.synthetic.main.fragment_atome_form.edit_billing_city -import kotlinx.android.synthetic.main.fragment_atome_form.edit_billing_country -import kotlinx.android.synthetic.main.fragment_atome_form.edit_billing_postal -import kotlinx.android.synthetic.main.fragment_atome_form.edit_billing_street -import kotlinx.android.synthetic.main.fragment_atome_form.edit_email -import kotlinx.android.synthetic.main.fragment_atome_form.edit_full_name -import kotlinx.android.synthetic.main.fragment_atome_form.edit_phone_number -import kotlinx.android.synthetic.main.fragment_atome_form.edit_shipping_city -import kotlinx.android.synthetic.main.fragment_atome_form.edit_shipping_country -import kotlinx.android.synthetic.main.fragment_atome_form.edit_shipping_postal -import kotlinx.android.synthetic.main.fragment_atome_form.edit_shipping_street -import kotlinx.android.synthetic.main.fragment_atome_form.text_atome_email_error -import kotlinx.android.synthetic.main.fragment_atome_form.text_billing_address_error -import kotlinx.android.synthetic.main.fragment_atome_form.text_phone_number_error -import kotlinx.android.synthetic.main.fragment_atome_form.text_shipping_address_error - -/** - * AtomeFormFragment is the UI class for handling all Atome payment methods. - */ -class AtomeFormFragment : OmiseFragment() { - var requester: PaymentCreatorRequester? = null - - private val fullNameEdit: OmiseEditText by lazy { edit_full_name } - private val emailEdit: OmiseEditText by lazy { edit_email } - private val emailErrorText by lazy { text_atome_email_error } - private val phoneNumberEdit: OmiseEditText by lazy { edit_phone_number } - private val phoneNumberErrorText by lazy { text_phone_number_error } - - private val shippingStreetEdit by lazy { edit_shipping_street } - private val shippingPostalEdit by lazy { edit_shipping_postal } - private val shippingCityEdit by lazy { edit_shipping_city } - private val shippingCountryEdit by lazy { edit_shipping_country } - private val shippingAddressErrorText by lazy { text_shipping_address_error } - - private val billingStreetEdit by lazy { edit_billing_street } - private val billingPostalEdit by lazy { edit_billing_postal } - private val billingCityEdit by lazy { edit_billing_city } - private val billingCountryEdit by lazy { edit_billing_country } - private val billingAddressErrorText by lazy { text_billing_address_error } - - private val checkBoxBillingShipping by lazy { checkbox_billing_shipping } - private val submitButton: Button by lazy { button_submit } - - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle?, - ): View? { - return inflater.inflate(R.layout.fragment_atome_form, container, false) - } - - override fun onViewCreated( - view: View, - savedInstanceState: Bundle?, - ) { - super.onViewCreated(view, savedInstanceState) - - setHasOptionsMenu(true) - - with(emailEdit) { - setOnFocusChangeListener { _, hasFocus -> - updateEmailErrorText(hasFocus) - } - setOnAfterTextChangeListener(::updateSubmitButton) - } - - with(phoneNumberEdit) { - setOnFocusChangeListener { _, hasFocus -> - updatePhoneErrorText(hasFocus) - } - setOnAfterTextChangeListener(::updateSubmitButton) - } - - with(shippingStreetEdit) { - setOnFocusChangeListener { _, hasFocus -> - updateShippingAddressErrorText(hasFocus) - } - setOnAfterTextChangeListener(::updateSubmitButton) - } - - with(shippingPostalEdit) { - setOnAfterTextChangeListener(::updateSubmitButton) - } - - with(shippingCityEdit) { - setOnAfterTextChangeListener(::updateSubmitButton) - } - - with(shippingCountryEdit) { - setOnFocusChangeListener { _, hasFocus -> - updateShippingAddressErrorText(hasFocus) - } - setOnAfterTextChangeListener(::updateSubmitButton) - } - - with(billingCountryEdit) { - setOnFocusChangeListener { _, hasFocus -> - updateBillingAddressErrorText(hasFocus) - } - setOnAfterTextChangeListener(::updateSubmitButton) - } - - checkBoxBillingShipping.toggle() - checkBoxBillingShipping.setOnClickListener(::onBillingShippingCheckboxClicked) - - submitButton.setOnClickListener(::submitForm) - } - - private fun updateEmailErrorText(hasFocus: Boolean) { - if (hasFocus || emailEdit.text?.isEmpty() == true || isEmailValid(emailEdit)) { - with(emailErrorText) { - text = "" - visibility = GONE - } - return - } - - emailErrorText.visibility = VISIBLE - emailErrorText.text = getString(R.string.error_invalid_email) - } - - private fun updatePhoneErrorText(hasFocus: Boolean) { - if (hasFocus || isPhoneNumberValid(phoneNumberEdit)) { - with(phoneNumberErrorText) { - text = "" - visibility = GONE - } - return - } - - phoneNumberErrorText.visibility = VISIBLE - phoneNumberErrorText.text = getString(R.string.error_invalid_phone_number) - } - - private fun updateShippingAddressErrorText(hasFocus: Boolean) { - if (hasFocus || ( - shippingStreetEdit.isValid && - shippingPostalEdit.isValid && - shippingCityEdit.isValid && - isCountryCodeValid(shippingCountryEdit) - ) - ) { - with(shippingAddressErrorText) { - text = "" - visibility = GONE - } - return - } - - shippingAddressErrorText.visibility = VISIBLE - shippingAddressErrorText.text = getString(R.string.error_invalid_address) - } - - private fun updateBillingAddressErrorText(hasFocus: Boolean) { - if (hasFocus || billingCountryEdit.text?.isEmpty() == true || isCountryCodeValid(billingCountryEdit)) { - with(billingAddressErrorText) { - text = "" - visibility = GONE - } - return - } - - billingAddressErrorText.visibility = VISIBLE - billingAddressErrorText.text = getString(R.string.error_invalid_address) - } - - private fun isEmailValid(emailEdit: OmiseEditText): Boolean { - return android.util.Patterns.EMAIL_ADDRESS.matcher(emailEdit.text!!).matches() - } - - private fun isPhoneNumberValid(phoneNumberEdit: OmiseEditText): Boolean { - return android.util.Patterns.PHONE.matcher(phoneNumberEdit.text!!).matches() - } - - private fun isCountryCodeValid(countryCodeEdit: OmiseEditText): Boolean { - return countryCodeEdit.length() == 2 - } - - private fun onBillingShippingCheckboxClicked(view: View) { - if (view is CheckBox) { - val checked: Boolean = view.isChecked - - when (view.id) { - R.id.checkbox_billing_shipping -> { - if (checked) { - billing_address.visibility = GONE - } else { - billing_address.visibility = VISIBLE - } - } - } - } - } - - private fun updateSubmitButton() { - submitButton.isEnabled = (emailEdit.text?.isEmpty() == true || isEmailValid(emailEdit)) && - isPhoneNumberValid(phoneNumberEdit) && - shippingStreetEdit.isValid && - shippingPostalEdit.isValid && - shippingCityEdit.isValid && - isCountryCodeValid(shippingCountryEdit) && - (billingCityEdit.text?.isEmpty() == true || isCountryCodeValid(billingCountryEdit)) - } - - private fun submitForm() { - val requester = requester ?: return - val requestBuilder = Source.CreateSourceRequestBuilder(requester.amount, requester.currency, SourceType.Atome) - - val fullName = fullNameEdit.text?.toString()?.trim().orEmpty() - requestBuilder.name(fullName) - - val email = emailEdit.text?.toString()?.trim().orEmpty() - requestBuilder.email(email) - - val phoneNumber = phoneNumberEdit.text?.toString()?.trim().orEmpty() - requestBuilder.phoneNumber(phoneNumber) - - val shippingStreet = shippingStreetEdit.text?.toString()?.trim().orEmpty() - val shippingPostal = shippingPostalEdit.text?.toString()?.trim().orEmpty() - val shippingCity = shippingCityEdit.text?.toString()?.trim().orEmpty() - val shippingCountry = shippingCountryEdit.text?.toString()?.trim().orEmpty() - requestBuilder.shipping( - Shipping( - street1 = shippingStreet, - postalCode = shippingPostal, - city = shippingCity, - country = shippingCountry, - ), - ).items( - listOf( - Item( - "3427842", - "Shoes", - "Prada shoes", - "1", - requester.amount.toString(), - "www.kan.com/product/shoes", - "www.kan.com/product/shoes/image", - "Gucci", - ), - ), - ) - - if (!checkBoxBillingShipping.isChecked) { - requestBuilder.billing( - Billing( - street1 = billingStreetEdit.text?.toString()?.trim().orEmpty(), - postalCode = billingPostalEdit.text?.toString()?.trim().orEmpty(), - city = billingCityEdit.text?.toString()?.trim().orEmpty(), - country = billingCountryEdit.text?.toString()?.trim().orEmpty(), - ), - ) - } else { - requestBuilder.billing( - Billing( - street1 = shippingStreet, - postalCode = shippingPostal, - city = shippingCity, - country = shippingCountry, - ), - ) - } - - val request = requestBuilder.build() - view?.let { setAllViewsEnabled(it, false) } - requester.request(request) { - view?.let { setAllViewsEnabled(it, true) } - } - } -} diff --git a/sdk/src/main/java/co/omise/android/ui/CardNameEditText.kt b/sdk/src/main/java/co/omise/android/ui/CardNameEditText.kt deleted file mode 100644 index 7b45e8aed..000000000 --- a/sdk/src/main/java/co/omise/android/ui/CardNameEditText.kt +++ /dev/null @@ -1,21 +0,0 @@ -package co.omise.android.ui - -import android.content.Context -import android.text.InputType -import android.util.AttributeSet - -/** - * CardNameEditText is a custom EditText for the credit card name field. - */ -class CardNameEditText : OmiseEditText { - constructor(context: Context) : super(context) - constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) - constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) - - val cardName: String - get() = text.toString().trim() - - init { - inputType = InputType.TYPE_TEXT_VARIATION_PERSON_NAME - } -} diff --git a/sdk/src/main/java/co/omise/android/ui/CountryListAdapter.kt b/sdk/src/main/java/co/omise/android/ui/CountryListAdapter.kt deleted file mode 100644 index 94eaa6d43..000000000 --- a/sdk/src/main/java/co/omise/android/ui/CountryListAdapter.kt +++ /dev/null @@ -1,64 +0,0 @@ -package co.omise.android.ui - -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.widget.TextView -import androidx.recyclerview.widget.DiffUtil -import androidx.recyclerview.widget.ListAdapter -import androidx.recyclerview.widget.RecyclerView.ViewHolder -import co.omise.android.R -import co.omise.android.models.CountryInfo - -internal class CountryListAdapter(private val onClick: (CountryInfo) -> Unit) : - ListAdapter(CountryDiffCallback) { - override fun onCreateViewHolder( - parent: ViewGroup, - viewType: Int, - ): CountryViewHolder { - val view = LayoutInflater.from(parent.context).inflate(R.layout.list_country_item, parent, false) - return CountryViewHolder(view, onClick) - } - - override fun onBindViewHolder( - holder: CountryViewHolder, - position: Int, - ) { - val country = getItem(position) - holder.bind(country) - } - - class CountryViewHolder(itemView: View, val onClick: (CountryInfo) -> Unit) : ViewHolder(itemView) { - private var currentCountry: CountryInfo? = null - private val titleTextView: TextView = itemView.findViewById(R.id.text_item_title) - - init { - itemView.setOnClickListener { - currentCountry?.let { - onClick(it) - } - } - } - - fun bind(country: CountryInfo) { - currentCountry = country - titleTextView.text = country.name - } - } -} - -object CountryDiffCallback : DiffUtil.ItemCallback() { - override fun areItemsTheSame( - oldItem: CountryInfo, - newItem: CountryInfo, - ): Boolean { - return oldItem == newItem - } - - override fun areContentsTheSame( - oldItem: CountryInfo, - newItem: CountryInfo, - ): Boolean { - return oldItem.name == newItem.name - } -} diff --git a/sdk/src/main/java/co/omise/android/ui/CountryListDialogFragment.kt b/sdk/src/main/java/co/omise/android/ui/CountryListDialogFragment.kt deleted file mode 100644 index 18261ad06..000000000 --- a/sdk/src/main/java/co/omise/android/ui/CountryListDialogFragment.kt +++ /dev/null @@ -1,73 +0,0 @@ -package co.omise.android.ui - -import android.graphics.Color -import android.graphics.drawable.ColorDrawable -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.appcompat.widget.Toolbar -import androidx.fragment.app.DialogFragment -import androidx.recyclerview.widget.RecyclerView -import co.omise.android.R -import co.omise.android.models.CountryInfo -import kotlinx.android.synthetic.main.dialog_country_list.country_list -import kotlinx.android.synthetic.main.dialog_country_list.toolbar_country_list -import java.text.Collator - -/** - * [CountryListDialogFragment] provides a dialog for selecting a country. - */ -class CountryListDialogFragment : DialogFragment() { - /** - * The interface to receive [CountryInfo] object after selecting a country. - */ - interface CountryListDialogListener { - fun onCountrySelected(country: CountryInfo) - } - - private val listView: RecyclerView by lazy { country_list } - private val toolbar: Toolbar by lazy { toolbar_country_list } - - var listener: CountryListDialogListener? = null - - override fun getTheme(): Int { - return R.style.OmiseFullScreenDialogTheme - } - - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle?, - ): View? { - return inflater.inflate(R.layout.dialog_country_list, container) - } - - override fun onViewCreated( - view: View, - savedInstanceState: Bundle?, - ) { - super.onViewCreated(view, savedInstanceState) - - dialog?.window?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT)) - toolbar.setOnMenuItemClickListener { item -> - when (item.itemId) { - R.id.close_menu -> dismiss() - } - true - } - - val adapter = CountryListAdapter(::onCountryClick) - listView.adapter = adapter - adapter.submitList( - CountryInfo.ALL.sortedWith { o1, o2 -> - Collator.getInstance().compare(o1.name, o2.name) - }, - ) - } - - private fun onCountryClick(country: CountryInfo) { - listener?.onCountrySelected(country) - dismiss() - } -} diff --git a/sdk/src/main/java/co/omise/android/ui/CreditCardActivity.kt b/sdk/src/main/java/co/omise/android/ui/CreditCardActivity.kt index 01d7e8e7c..24c354d66 100644 --- a/sdk/src/main/java/co/omise/android/ui/CreditCardActivity.kt +++ b/sdk/src/main/java/co/omise/android/ui/CreditCardActivity.kt @@ -1,133 +1,13 @@ package co.omise.android.ui -import android.app.Activity -import android.content.Intent import android.os.Bundle -import android.view.MenuItem -import android.view.View import android.view.WindowManager -import android.widget.Button -import android.widget.EditText -import android.widget.ImageButton -import android.widget.LinearLayout -import android.widget.ScrollView -import android.widget.TextView -import androidx.activity.OnBackPressedCallback -import co.omise.android.CardNumber -import co.omise.android.R -import co.omise.android.api.Client -import co.omise.android.api.Request -import co.omise.android.api.RequestListener -import co.omise.android.extensions.getMessageFromResources -import co.omise.android.extensions.parcelable -import co.omise.android.extensions.setOnAfterTextChangeListener -import co.omise.android.extensions.setOnClickListener -import co.omise.android.extensions.textOrNull -import co.omise.android.models.APIError -import co.omise.android.models.BackendType -import co.omise.android.models.Capability -import co.omise.android.models.CardParam -import co.omise.android.models.CountryInfo -import co.omise.android.models.PaymentMethod -import co.omise.android.models.Source -import co.omise.android.models.Token -import co.omise.android.models.backendType -import com.google.android.material.snackbar.Snackbar -import kotlinx.android.synthetic.main.activity_credit_card.billing_address_container -import kotlinx.android.synthetic.main.activity_credit_card.button_security_code_tooltip -import kotlinx.android.synthetic.main.activity_credit_card.button_submit -import kotlinx.android.synthetic.main.activity_credit_card.edit_card_name -import kotlinx.android.synthetic.main.activity_credit_card.edit_card_number -import kotlinx.android.synthetic.main.activity_credit_card.edit_city -import kotlinx.android.synthetic.main.activity_credit_card.edit_country -import kotlinx.android.synthetic.main.activity_credit_card.edit_expiry_date -import kotlinx.android.synthetic.main.activity_credit_card.edit_postal_code -import kotlinx.android.synthetic.main.activity_credit_card.edit_security_code -import kotlinx.android.synthetic.main.activity_credit_card.edit_state -import kotlinx.android.synthetic.main.activity_credit_card.edit_street1 -import kotlinx.android.synthetic.main.activity_credit_card.scrollview -import kotlinx.android.synthetic.main.activity_credit_card.text_card_name_error -import kotlinx.android.synthetic.main.activity_credit_card.text_card_number_error -import kotlinx.android.synthetic.main.activity_credit_card.text_city_error -import kotlinx.android.synthetic.main.activity_credit_card.text_country_error -import kotlinx.android.synthetic.main.activity_credit_card.text_expiry_date_error -import kotlinx.android.synthetic.main.activity_credit_card.text_postal_code_error -import kotlinx.android.synthetic.main.activity_credit_card.text_security_code_error -import kotlinx.android.synthetic.main.activity_credit_card.text_state_error -import kotlinx.android.synthetic.main.activity_credit_card.text_street1_error -import org.jetbrains.annotations.TestOnly -import java.io.IOError -import java.util.Locale /** * CreditCardActivity is the UI class for taking credit card information input from the user. */ class CreditCardActivity : OmiseActivity() { private lateinit var pKey: String - private lateinit var client: Client - private val cardNumberEdit: CreditCardEditText by lazy { edit_card_number } - private val cardNameEdit: CardNameEditText by lazy { edit_card_name } - private val expiryDateEdit: ExpiryDateEditText by lazy { edit_expiry_date } - private val securityCodeEdit: SecurityCodeEditText by lazy { edit_security_code } - private val countryEdit: OmiseEditText by lazy { edit_country } - private val street1Edit: OmiseEditText by lazy { edit_street1 } - private val cityEdit: OmiseEditText by lazy { edit_city } - private val stateEdit: OmiseEditText by lazy { edit_state } - private val postalCodeEdit: OmiseEditText by lazy { edit_postal_code } - - private val submitButton: Button by lazy { button_submit } - private val scrollView: ScrollView by lazy { scrollview } - private val cardNumberErrorText: TextView by lazy { text_card_number_error } - private val cardNameErrorText: TextView by lazy { text_card_name_error } - private val expiryDateErrorText: TextView by lazy { text_expiry_date_error } - private val securityCodeErrorText: TextView by lazy { text_security_code_error } - private val countryErrorText: TextView by lazy { text_country_error } - private val street1ErrorText: TextView by lazy { text_street1_error } - private val cityErrorText: TextView by lazy { text_city_error } - private val stateErrorText: TextView by lazy { text_state_error } - private val postalCodeErrorText: TextView by lazy { text_postal_code_error } - - private val securityCodeTooltipButton: ImageButton by lazy { button_security_code_tooltip } - - private val billingAddressContainer: LinearLayout by lazy { billing_address_container } - - /** - * Target countries that supports AVS or the Address Verification System. - * @see [link](https://www.omise.co/How-to-improve-my-authorization-rate-for-US-UK-and-Canadian-cardholders) - */ - private val avsCountries = CountryInfo.ALL.filter { listOf("US", "GB", "CA").contains(it.code) } - - private val editTexts: Map by lazy { - mapOf( - cardNumberEdit to cardNumberErrorText, - cardNameEdit to cardNameErrorText, - expiryDateEdit to expiryDateErrorText, - securityCodeEdit to securityCodeErrorText, - countryEdit to countryErrorText, - street1Edit to street1ErrorText, - cityEdit to cityErrorText, - stateEdit to stateErrorText, - postalCodeEdit to postalCodeErrorText, - ) - } - - private val billingAddressEditTexts: Map by lazy { - mapOf( - countryEdit to countryErrorText, - street1Edit to street1ErrorText, - cityEdit to cityErrorText, - stateEdit to stateErrorText, - postalCodeEdit to postalCodeErrorText, - ) - } - - private var selectedCountry: CountryInfo? = null - set(value) { - field = value - value?.let { - invalidateBillingAddressForm() - } - } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -136,253 +16,8 @@ class CreditCardActivity : OmiseActivity() { window.addFlags(WindowManager.LayoutParams.FLAG_SECURE) } - setContentView(R.layout.activity_credit_card) - require(intent.hasExtra(EXTRA_PKEY)) { "Could not find ${::EXTRA_PKEY.name}." } pKey = requireNotNull(intent.getStringExtra(EXTRA_PKEY)) { "${::EXTRA_PKEY.name} must not be null." } - if (!this::client.isInitialized) { - client = Client(pKey) - } - val onBackPressedCallback = - object : OnBackPressedCallback(true) { - override fun handleOnBackPressed() { - setResult(RESULT_CANCELED) - finish() - } - } - - onBackPressedDispatcher.addCallback(this, onBackPressedCallback) - - initialize() - } - - private fun EditText.getErrorMessage(): String? { - return when (this) { - cardNumberEdit -> getString(R.string.error_invalid_card_number) - cardNameEdit -> getString(R.string.error_invalid_card_name) - expiryDateEdit -> getString(R.string.error_invalid_expiration_date) - securityCodeEdit -> getString(R.string.error_invalid_security_code) - street1Edit -> getString(R.string.error_required_street1) - cityEdit -> getString(R.string.error_required_city) - stateEdit -> getString(R.string.error_required_state) - postalCodeEdit -> getString(R.string.error_required_postal_code) - else -> null - } - } - - private fun initialize() { - setTitle(R.string.default_form_title) - supportActionBar?.setDisplayHomeAsUpEnabled(true) - submitButton.setOnClickListener(::submit) - securityCodeTooltipButton.setOnClickListener(::showSecurityCodeTooltipDialog) - countryEdit.setOnClickListener(::showCountryDropdownDialog) - - editTexts.forEach { (editText, errorText) -> - editText.setOnFocusChangeListener { _, hasFocus -> - if (!hasFocus) { - try { - editText.validate() - } catch (e: InputValidationException.InvalidInputException) { - errorText.text = editText.getErrorMessage() - } catch (e: InputValidationException.EmptyInputException) { - if (isBillingAddressRequired()) { - errorText.text = editText.getErrorMessage() - } else { - errorText.text = null - } - } - } else { - errorText.text = null - } - } - editText.setOnAfterTextChangeListener(::updateSubmitButton) - } - - invalidateBillingAddressForm() - - getCapability() - } - - @TestOnly - fun setClient(client: Client) { - this.client = client - } - - private fun getCapability() { - val getCapabilityRequest = Capability.GetCapabilitiesRequestBuilder().build() - client.send( - getCapabilityRequest, - object : RequestListener { - override fun onRequestSucceed(model: Capability) { - val countryCode = model.country ?: Locale.getDefault().country - selectedCountry = CountryInfo.ALL.find { it.code == countryCode } - } - - override fun onRequestFailed(throwable: Throwable) { - Snackbar.make(scrollView, throwable.message.toString(), Snackbar.LENGTH_LONG).show() - } - }, - ) - } - - override fun onOptionsItemSelected(item: MenuItem): Boolean { - when (item.itemId) { - android.R.id.home -> { - setResult(RESULT_CANCELED) - finish() - } - } - return super.onOptionsItemSelected(item) - } - - private fun disableForm() { - setFormEnabled(false) - } - - private fun enableForm() { - setFormEnabled(true) - } - - private fun setFormEnabled(enabled: Boolean) { - editTexts.forEach { (editText, _) -> editText.isEnabled = enabled } - submitButton.isEnabled = enabled - } - - private fun handleRequestFailed(throwable: Throwable) { - enableForm() - - val message = - when (throwable) { - is IOError -> getString(R.string.error_io, throwable.message) - is APIError -> throwable.getMessageFromResources(resources) - else -> getString(R.string.error_unknown, throwable.message) - } - - Snackbar.make(scrollView, message, Snackbar.LENGTH_LONG).show() - } - - private fun submit() { - disableForm() - - val cardParam = - CardParam( - name = cardNameEdit.cardName, - number = cardNumberEdit.cardNumber, - expirationMonth = expiryDateEdit.expiryMonth, - expirationYear = expiryDateEdit.expiryYear, - securityCode = securityCodeEdit.securityCode, - country = selectedCountry?.code, - street1 = street1Edit.textOrNull?.toString(), - city = cityEdit.textOrNull?.toString(), - state = stateEdit.textOrNull?.toString(), - postalCode = postalCodeEdit.textOrNull?.toString(), - ) - - val request = Token.CreateTokenRequestBuilder(cardParam).build() - val installmentTerms = intent.getIntExtra(EXTRA_SELECTED_INSTALLMENTS_TERM, 0) - // check if the user is coming from a WLB installment - var sourceRequest: Request? = null - if (installmentTerms != 0) { - // create a source request - val amount = requireNotNull(intent.getLongExtra(EXTRA_AMOUNT, 0)) { "${::EXTRA_AMOUNT.name} must not be null." } - - val currency = requireNotNull(intent.getStringExtra(EXTRA_CURRENCY)) { "${::EXTRA_CURRENCY.name} must not be null." } - val paymentMethod = - requireNotNull( - intent.parcelable(EXTRA_SELECTED_INSTALLMENTS_PAYMENT_METHOD), - ) { - "${::EXTRA_SELECTED_INSTALLMENTS_PAYMENT_METHOD.name} must not be null." - } - val sourceType = (paymentMethod.backendType as? BackendType.Source)?.sourceType ?: return - val capability = - requireNotNull( - intent.parcelable(EXTRA_CAPABILITY), - ) { "${::EXTRA_CAPABILITY.name} must not be null." } - sourceRequest = - Source.CreateSourceRequestBuilder(amount, currency, sourceType) - .installmentTerm(installmentTerms) - .zeroInterestInstallments(capability.zeroInterestInstallments) - .build() - } - client.send( - request, - object : RequestListener { - override fun onRequestSucceed(model: Token) { - val data = Intent() - data.putExtra(EXTRA_TOKEN, model.id) - data.putExtra(EXTRA_TOKEN_OBJECT, model) - data.putExtra(EXTRA_CARD_OBJECT, model.card) - if (sourceRequest == null) { - setResult(Activity.RESULT_OK, data) - finish() - } else { - // create source request - client.send( - sourceRequest, - object : RequestListener { - override fun onRequestSucceed(model: Source) { - data.putExtra(EXTRA_SOURCE_OBJECT, model) - setResult(Activity.RESULT_OK, data) - finish() - } - - override fun onRequestFailed(throwable: Throwable) { - handleRequestFailed(throwable) - } - }, - ) - } - } - - override fun onRequestFailed(throwable: Throwable) { - handleRequestFailed(throwable) - } - }, - ) - } - - private fun updateSubmitButton() { - val isFormValid = - editTexts.filterKeys { - if (!isBillingAddressRequired()) { - !billingAddressEditTexts.containsKey(it) - } else { - true - } - }.map { (editText, _) -> editText.isValid }.reduce { acc, b -> acc && b } - submitButton.isEnabled = isFormValid - } - - private fun showSecurityCodeTooltipDialog() { - val brand = CardNumber.brand(cardNumberEdit.cardNumber) - val dialog = SecurityCodeTooltipDialogFragment.newInstant(brand) - dialog.show(supportFragmentManager, null) - } - - private fun showCountryDropdownDialog() { - val dialog = CountryListDialogFragment() - dialog.listener = - object : CountryListDialogFragment.CountryListDialogListener { - override fun onCountrySelected(country: CountryInfo) { - selectedCountry = country - } - } - dialog.show(supportFragmentManager, null) - } - - private fun invalidateBillingAddressForm() { - countryEdit.setText(selectedCountry?.name) - billingAddressContainer.visibility = if (isBillingAddressRequired()) View.VISIBLE else View.GONE - billingAddressEditTexts.forEach { (editText, errorText) -> - if (editText != countryEdit) { - editText.text = null - errorText.text = null - } - } - } - - private fun isBillingAddressRequired(): Boolean { - return selectedCountry != null && avsCountries.contains(selectedCountry) } } diff --git a/sdk/src/main/java/co/omise/android/ui/CreditCardEditText.kt b/sdk/src/main/java/co/omise/android/ui/CreditCardEditText.kt deleted file mode 100644 index 5b1c5bf4f..000000000 --- a/sdk/src/main/java/co/omise/android/ui/CreditCardEditText.kt +++ /dev/null @@ -1,118 +0,0 @@ -package co.omise.android.ui - -import android.content.Context -import android.graphics.Bitmap -import android.graphics.BitmapFactory -import android.graphics.Canvas -import android.graphics.Paint -import android.text.Editable -import android.text.InputFilter -import android.text.InputType -import android.text.TextWatcher -import android.util.AttributeSet -import co.omise.android.CardNumber -import co.omise.android.models.CardBrand - -/** - * CreditCardEditText is a custom EditText for the credit card number field. This EditText - * both formats the card number and also draws the card brand logo for the given - * card number. - */ -class CreditCardEditText : OmiseEditText { - companion object { - private const val CARD_NUMBER_WITH_SPACE_LENGTH = 19 - private const val SEPARATOR = " " - } - - private var cardBrandImage: Bitmap? = null - private var cardBrandImagePaint: Paint? = null - - val cardNumber: String - get() = text.toString().trim().replace(SEPARATOR, "") - - val cardBrand: CardBrand? - get() = CardNumber.brand(cardNumber) - - constructor(context: Context) : super(context) - - constructor(context: Context, attrs: AttributeSet) : super(context, attrs) - - constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) - - init { - filters = arrayOf(InputFilter.LengthFilter(CARD_NUMBER_WITH_SPACE_LENGTH)) - inputType = InputType.TYPE_CLASS_PHONE - - addTextChangedListener( - object : TextWatcher { - override fun beforeTextChanged( - charSequence: CharSequence, - i: Int, - i1: Int, - i2: Int, - ) { - // Do nothing - } - - override fun onTextChanged( - charSequence: CharSequence, - i: Int, - i1: Int, - i2: Int, - ) { - // Do nothing - } - - override fun afterTextChanged(e: Editable) { - if (e.isEmpty() || e.length % 5 != 0) { - return - } - - val c = e[e.length - 1] - if (Character.isDigit(c)) { - // Insert space bar - e.insert(e.length - 1, SEPARATOR) - } else if (c == ' ') { - // Delete space bar - e.delete(e.length - 1, e.length) - } - - updateCardBrandImage() - } - }, - ) - - cardBrandImagePaint = Paint(Paint.ANTI_ALIAS_FLAG) - } - - override fun onDraw(canvas: Canvas) { - super.onDraw(canvas) - cardBrandImage?.let { - val imageLeftPosition = width.toFloat() - it.width - paddingRight - val imageTopPosition = (height - it.height) / 2f - canvas.drawBitmap(it, imageLeftPosition, imageTopPosition, cardBrandImagePaint) - } - } - - override fun validate() { - super.validate() - - val brand = cardBrand ?: throw InputValidationException.InvalidInputException - - if (!CardNumber.luhn(cardNumber) || !brand.valid(cardNumber)) { - throw InputValidationException.InvalidInputException - } - } - - private fun updateCardBrandImage() { - val number = text.toString() - if (number.length > 6) { - val brand = CardNumber.brand(number) - if (brand != null && brand.logoResourceId > -1) { - cardBrandImage = BitmapFactory.decodeResource(resources, brand.logoResourceId) - return - } - } - cardBrandImage = null - } -} diff --git a/sdk/src/main/java/co/omise/android/ui/DuitNowOBWBankChooserFragment.kt b/sdk/src/main/java/co/omise/android/ui/DuitNowOBWBankChooserFragment.kt deleted file mode 100644 index 5caa2f91f..000000000 --- a/sdk/src/main/java/co/omise/android/ui/DuitNowOBWBankChooserFragment.kt +++ /dev/null @@ -1,68 +0,0 @@ -package co.omise.android.ui - -import android.os.Bundle -import android.view.View -import co.omise.android.R -import co.omise.android.extensions.getParcelableArrayCompat -import co.omise.android.models.Bank -import co.omise.android.models.Source -import co.omise.android.models.SourceType - -/** - * DuitNowOBWBankChooserFragment is the UI class, extended from base [OmiseListFragment] to show available - * DuitNow OBW bank options list for the user to choose from. - */ -internal class DuitNowOBWBankChooserFragment : OmiseListFragment() { - var requester: PaymentCreatorRequester? = null - - override fun onViewCreated( - view: View, - savedInstanceState: Bundle?, - ) { - super.onViewCreated(view, savedInstanceState) - - title = getString(R.string.payment_method_duitnow_obw_title) - noDataText.text = getString(R.string.banks_no_data) - setHasOptionsMenu(true) - } - - override fun onListItemClicked(item: DuitNowOBWResource) { - val req = requester ?: return - val bankCode = item.bankCode.orEmpty() - - view?.let { setAllViewsEnabled(it, false) } - - val request = - Source.CreateSourceRequestBuilder(req.amount, req.currency, SourceType.DuitNowOBW) - .bank(bankCode) - .build() - - view?.let { setAllViewsEnabled(it, false) } - req.request(request) { view?.let { setAllViewsEnabled(it, true) } } - } - - override fun listItems(): List { - val capabilityBanks = arguments?.getParcelableArrayCompat(DUITNOWOBW_BANKS).orEmpty() - - return capabilityBanks.map { - DuitNowOBWResource( - iconRes = DuitNowOBWResource.getBankImageFromCode(it.code), - title = it.name, - bankCode = it.code, - enabled = it.active, - ) - } - } - - companion object { - private const val DUITNOWOBW_BANKS = "DuitNowOBWBankChooserFragment.banks" - - fun newInstance(banks: List?) = - DuitNowOBWBankChooserFragment().apply { - arguments = - Bundle().apply { - putParcelableArray(DUITNOWOBW_BANKS, banks?.toTypedArray()) - } - } - } -} diff --git a/sdk/src/main/java/co/omise/android/ui/EContextFormFragment.kt b/sdk/src/main/java/co/omise/android/ui/EContextFormFragment.kt deleted file mode 100644 index 575505099..000000000 --- a/sdk/src/main/java/co/omise/android/ui/EContextFormFragment.kt +++ /dev/null @@ -1,142 +0,0 @@ -package co.omise.android.ui - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.View.INVISIBLE -import android.view.View.VISIBLE -import android.view.ViewGroup -import android.widget.Button -import android.widget.TextView -import co.omise.android.R -import co.omise.android.extensions.getParcelableCompat -import co.omise.android.extensions.setOnAfterTextChangeListener -import co.omise.android.extensions.setOnClickListener -import co.omise.android.models.Source -import co.omise.android.models.SourceType -import co.omise.android.models.SupportedEcontext -import kotlinx.android.synthetic.main.fragment_econtext_form.button_submit -import kotlinx.android.synthetic.main.fragment_econtext_form.edit_email -import kotlinx.android.synthetic.main.fragment_econtext_form.edit_full_name -import kotlinx.android.synthetic.main.fragment_econtext_form.edit_phone_number -import kotlinx.android.synthetic.main.fragment_econtext_form.text_email_error -import kotlinx.android.synthetic.main.fragment_econtext_form.text_full_name_error -import kotlinx.android.synthetic.main.fragment_econtext_form.text_phone_number_error - -/** - * EContextFormFragment is the UI class for handling all EContext payment methods. - */ -class EContextFormFragment : OmiseFragment() { - var requester: PaymentCreatorRequester? = null - - private val type: SupportedEcontext? by lazy { - arguments?.getParcelableCompat(EXTRA_ECONTEXT_TYPE) - } - private val fullNameEdit: OmiseEditText by lazy { edit_full_name } - private val emailEdit: OmiseEditText by lazy { edit_email } - private val phoneNumberEdit: OmiseEditText by lazy { edit_phone_number } - private val fullNameErrorText by lazy { text_full_name_error } - private val emailErrorText by lazy { text_email_error } - private val phoneNumberErrorText by lazy { text_phone_number_error } - private val submitButton: Button by lazy { button_submit } - private val formInputWithErrorTexts: List> by lazy { - listOf( - Pair(fullNameEdit, fullNameErrorText), - Pair(emailEdit, emailErrorText), - Pair(phoneNumberEdit, phoneNumberErrorText), - ) - } - - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle?, - ): View? { - return inflater.inflate(R.layout.fragment_econtext_form, container, false) - } - - override fun onViewCreated( - view: View, - savedInstanceState: Bundle?, - ) { - super.onViewCreated(view, savedInstanceState) - - title = - when (type) { - SupportedEcontext.ConvenienceStore -> getString(R.string.title_convenience_store) - SupportedEcontext.PayEasy -> getString(R.string.title_pay_easy) - SupportedEcontext.Netbanking -> getString(R.string.title_netbank) - null -> getString(R.string.econtext_title) - } - setHasOptionsMenu(true) - - formInputWithErrorTexts.forEach { - it.first.setOnFocusChangeListener(::updateErrorText) - it.first.setOnAfterTextChangeListener(::updateSubmitButton) - } - - submitButton.setOnClickListener(::submitForm) - } - - private fun updateErrorText( - view: View, - hasFocus: Boolean, - ) { - val editText = view as OmiseEditText - val errorText = formInputWithErrorTexts.first { it.first == editText }.second - - if (hasFocus || editText.isValid) { - errorText.text = "" - errorText.visibility = INVISIBLE - return - } - - errorText.text = - when (editText) { - fullNameEdit -> getString(R.string.error_invalid_full_name) - emailEdit -> getString(R.string.error_invalid_email) - phoneNumberEdit -> getString(R.string.error_invalid_phone_number) - else -> getString(R.string.error_unknown_without_reason) - } - errorText.visibility = VISIBLE - } - - private fun updateSubmitButton() { - val isFormValid = - formInputWithErrorTexts.map { it.first.isValid } - .reduce { acc, b -> acc && b } - submitButton.isEnabled = isFormValid - } - - private fun submitForm() { - val requester = requester ?: return - - val fullName = fullNameEdit.text?.toString()?.trim().orEmpty() - val email = emailEdit.text?.toString()?.trim().orEmpty() - val phoneNumber = phoneNumberEdit.text?.toString()?.trim().orEmpty() - - val request = - Source.CreateSourceRequestBuilder(requester.amount, requester.currency, SourceType.Econtext) - .name(fullName) - .email(email) - .phoneNumber(phoneNumber) - .build() - - view?.let { setAllViewsEnabled(it, false) } - requester.request(request) { - view?.let { setAllViewsEnabled(it, true) } - } - } - - companion object { - private const val EXTRA_ECONTEXT_TYPE = "EContextFormFragment.econtextType" - - fun newInstance(eContext: SupportedEcontext): EContextFormFragment = - EContextFormFragment().apply { - arguments = - Bundle().apply { - putParcelable(EXTRA_ECONTEXT_TYPE, eContext) - } - } - } -} diff --git a/sdk/src/main/java/co/omise/android/ui/ExpiryDateEditText.kt b/sdk/src/main/java/co/omise/android/ui/ExpiryDateEditText.kt deleted file mode 100644 index 699fa9b59..000000000 --- a/sdk/src/main/java/co/omise/android/ui/ExpiryDateEditText.kt +++ /dev/null @@ -1,194 +0,0 @@ -package co.omise.android.ui - -import android.content.Context -import android.text.Editable -import android.text.InputFilter -import android.text.InputType -import android.text.TextWatcher -import android.util.AttributeSet -import co.omise.android.extensions.disableOptions -import java.util.Calendar -import java.util.GregorianCalendar - -/** - * ExpiryDateEditText is a custom EditText for credit card expiration date field. This EditText - * handles the formatting of the dates and also limits the month and year inputs to a valid - * range. - */ -class ExpiryDateEditText : OmiseEditText { - private var cursorPosition = 0 - - private var textWatcher = ExpiryDateTextWatcher() - private var textListener: ExpiryDateChangeListener? = null - private val startedYear: Int by lazy { - val currentYear = GregorianCalendar.getInstance().get(Calendar.YEAR) - currentYear - (currentYear % YEAR_LAST_TWO_DIGIT_MOD) - } - - var expiryMonth: Int = 0 - var expiryYear: Int = 0 - - constructor(context: Context) : super(context) - constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) - constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) - - init { - addTextChangedListener(textWatcher) - disableOptions() - filters = arrayOf(InputFilter.LengthFilter(MAX_CHARS)) - inputType = InputType.TYPE_CLASS_NUMBER - } - - override fun onSelectionChanged( - selStart: Int, - selEnd: Int, - ) { - super.onSelectionChanged(selStart, selEnd) - - text?.let { setSelection(it.length) } - } - - override fun validate() { - super.validate() - - val calendar = Calendar.getInstance() - val currentYear = calendar.get(Calendar.YEAR) - val currentMonth = calendar.get(Calendar.MONTH) - val expiryMonth = expiryMonth - val expiryYear = expiryYear - if (expiryYear < currentYear || (expiryMonth == currentMonth && expiryYear <= currentYear)) { - throw InputValidationException.InvalidInputException - } - } - - private inner class ExpiryDateTextWatcher : TextWatcher { - var beforeChangedText: String = "" - - override fun afterTextChanged(s: Editable?) { - // Do nothing - } - - override fun beforeTextChanged( - s: CharSequence, - start: Int, - count: Int, - after: Int, - ) { - text?.let { cursorPosition = it.length - selectionStart } - beforeChangedText = s.toString() - } - - override fun onTextChanged( - s: CharSequence, - start: Int, - before: Int, - count: Int, - ) { - // On deleting - if (s.length < beforeChangedText.length) { - if (beforeChangedText[beforeChangedText.length - 1].toString() == DATE_SEPARATOR) { - val afterDeletedText = s.substring(0, s.length - 1) - setText(afterDeletedText) - notifyExpiryDateChanged(afterDeletedText) - } else { - notifyExpiryDateChanged(s.toString()) - } - return - } - - setExpiryDateText(s.toString()) - notifyExpiryDateChanged(text.toString()) - } - } - - private fun setExpiryDateText(dateString: String) { - removeTextChangedListener(textWatcher) - - val formattedString = formatString(dateString) - setText(formattedString) - setSelection(text.toString().length - cursorPosition) - - addTextChangedListener(textWatcher) - } - - private fun notifyExpiryDateChanged(formattedString: String = "") { - if (formattedString.isEmpty()) { - expiryMonth = 0 - expiryYear = 0 - textListener?.textFormatted(null, null) - return - } - - val (month, year) = formattedString.separateDates() - - expiryMonth = month ?: 0 - expiryYear = year?.plus(startedYear) ?: 0 - - textListener?.textFormatted(expiryMonth, expiryYear) - } - - // Have to add this internal listener because if we add an external TextWatcher to this custom EditText - // the first character would be set twice - fun setInternalTextChangedListener(textListener: ExpiryDateChangeListener) { - this.textListener = textListener - } - - @Throws(IllegalArgumentException::class) - fun setExpiryDate( - month: Int, - year: Int, - ) { - if (month < 1 || month > 12 || year < 1 || year > 99) { - throw IllegalArgumentException("Invalid month or year.") - } - text?.append(month.toString()) - text?.append(year.toString()) - } - - companion object { - private const val DATE_SEPARATOR = "/" - private const val MAX_CHARS = 5 // Included separator - private const val MAX_MONTH = 12 - private const val YEAR_LAST_TWO_DIGIT_MOD = 100 - } - - interface ExpiryDateChangeListener { - fun textFormatted( - month: Int?, - year: Int?, - ) - } - - private fun formatString(str: String = ""): String { - return when { - str.length == 1 && str.toInt() > 1 -> str.addZeroPrefixIfNeed() - str.length == 2 && str.toInt() > MAX_MONTH -> MAX_MONTH.toString() - else -> str - }.addDateSeparatorIfNeed() - } - - private fun String.addDateSeparatorIfNeed(): String { - if (this.length != 2 || this.contains(DATE_SEPARATOR)) return this - return this + DATE_SEPARATOR - } - - private fun String.addZeroPrefixIfNeed(): String { - return if (this.toInt() > 1) { - "0$this" - } else { - this - } - } - - private fun String.separateDates(): Pair { - if (this.isNotEmpty() && !this.contains(DATE_SEPARATOR)) return Pair(this.toIntOrNull(), null) - - val dates = this.split(DATE_SEPARATOR.toRegex()) - - return if (dates.size > 1) { - Pair(dates[0].toIntOrNull(), dates[1].toIntOrNull()) - } else { - Pair(dates[0].toIntOrNull(), null) - } - } -} diff --git a/sdk/src/main/java/co/omise/android/ui/ExpiryMonthSpinnerAdapter.java b/sdk/src/main/java/co/omise/android/ui/ExpiryMonthSpinnerAdapter.java deleted file mode 100644 index a32c52a79..000000000 --- a/sdk/src/main/java/co/omise/android/ui/ExpiryMonthSpinnerAdapter.java +++ /dev/null @@ -1,24 +0,0 @@ -package co.omise.android.ui; - -import java.util.Locale; - -/** - * @deprecated replaced by {@link ExpiryDateEditText} - */ -@Deprecated -public class ExpiryMonthSpinnerAdapter extends NumberRangeSpinnerAdapter { - protected ExpiryMonthSpinnerAdapter() { - super(1, 12); - } - - @Override - protected String getItemDropDownLabel(int number) { - return String.format(Locale.getDefault(), "%02d", number); - } - - @Override - protected String getItemLabel(int number) { - return String.format(Locale.getDefault(), "%02d", number); - } - -} diff --git a/sdk/src/main/java/co/omise/android/ui/ExpiryYearSpinnerAdapter.java b/sdk/src/main/java/co/omise/android/ui/ExpiryYearSpinnerAdapter.java deleted file mode 100644 index 7da02f288..000000000 --- a/sdk/src/main/java/co/omise/android/ui/ExpiryYearSpinnerAdapter.java +++ /dev/null @@ -1,23 +0,0 @@ -package co.omise.android.ui; - -import org.joda.time.YearMonth; - -/** - * @deprecated replaced by {@link ExpiryDateEditText} - */ -@Deprecated -public class ExpiryYearSpinnerAdapter extends NumberRangeSpinnerAdapter { - protected ExpiryYearSpinnerAdapter() { - super(YearMonth.now().getYear(), YearMonth.now().getYear() + 12); - } - - @Override - protected String getItemDropDownLabel(int number) { - return Integer.toString(number); - } - - @Override - protected String getItemLabel(int number) { - return Integer.toString(number).substring(2, 4); - } -} diff --git a/sdk/src/main/java/co/omise/android/ui/FpxBankChooserFragment.kt b/sdk/src/main/java/co/omise/android/ui/FpxBankChooserFragment.kt deleted file mode 100644 index 8997d3655..000000000 --- a/sdk/src/main/java/co/omise/android/ui/FpxBankChooserFragment.kt +++ /dev/null @@ -1,76 +0,0 @@ -package co.omise.android.ui - -import android.os.Bundle -import android.view.View -import co.omise.android.R -import co.omise.android.extensions.getParcelableArrayCompat -import co.omise.android.models.Bank -import co.omise.android.models.Source -import co.omise.android.models.SourceType - -/** - * FpxBankChooserFragment is the UI class, extended from base [OmiseListFragment] to show - * available FPX bank options list for the user to choose from. - */ -internal class FpxBankChooserFragment : OmiseListFragment() { - var requester: PaymentCreatorRequester? = null - - override fun onViewCreated( - view: View, - savedInstanceState: Bundle?, - ) { - super.onViewCreated(view, savedInstanceState) - - title = getString(R.string.payment_method_fpx_title) - noDataText.text = getString(R.string.banks_no_data) - setHasOptionsMenu(true) - } - - override fun onListItemClicked(item: FpxResource) { - val req = requester ?: return - val email = arguments?.getString(FPX_EMAIL).orEmpty() - val bankCode = item.bankCode.orEmpty() - - view?.let { setAllViewsEnabled(it, false) } - - val request = - Source.CreateSourceRequestBuilder(req.amount, req.currency, SourceType.Fpx()) - .email(email) - .bank(bankCode) - .build() - - view?.let { setAllViewsEnabled(it, false) } - req.request(request) { - view?.let { setAllViewsEnabled(it, true) } - } - } - - override fun listItems(): List { - val capabilityBanks = arguments?.getParcelableArrayCompat(FPX_BANKS).orEmpty() - - return capabilityBanks.map { - FpxResource( - iconRes = FpxResource.getBankImageFromCode(it.code), - title = it.name, - bankCode = it.code, - enabled = it.active, - ) - } - } - - companion object { - private const val FPX_EMAIL = "FpxBankChooserFragment.email" - private const val FPX_BANKS = "FpxBankChooserFragment.banks" - - fun newInstance( - banks: List?, - email: String, - ) = FpxBankChooserFragment().apply { - arguments = - Bundle().apply { - putParcelableArray(FPX_BANKS, banks?.toTypedArray()) - putString(FPX_EMAIL, email) - } - } - } -} diff --git a/sdk/src/main/java/co/omise/android/ui/FpxEmailFormFragment.kt b/sdk/src/main/java/co/omise/android/ui/FpxEmailFormFragment.kt deleted file mode 100644 index f055a9215..000000000 --- a/sdk/src/main/java/co/omise/android/ui/FpxEmailFormFragment.kt +++ /dev/null @@ -1,63 +0,0 @@ -package co.omise.android.ui - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.widget.Button -import co.omise.android.R -import co.omise.android.extensions.setOnAfterTextChangeListener -import co.omise.android.extensions.setOnClickListener -import co.omise.android.models.Source -import kotlinx.android.synthetic.main.fragment_fpx_email_form.button_submit -import kotlinx.android.synthetic.main.fragment_fpx_email_form.edit_email - -/** - * FpxEmailFormFragment is the UI class to show an email form for FPX payments. - */ -internal class FpxEmailFormFragment : OmiseFragment() { - var navigation: PaymentCreatorNavigation? = null - var requester: PaymentCreatorRequester? = null - - private val emailEdit: OmiseEditText by lazy { edit_email } - private val submitButton: Button by lazy { button_submit } - private val allowedEmailFormat = "\\A[\\w+\\-.]+@[a-z\\d\\-.]+\\.[a-z]{2,}\\z" - - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle?, - ): View? { - return inflater.inflate(R.layout.fragment_fpx_email_form, container, false) - } - - override fun onViewCreated( - view: View, - savedInstanceState: Bundle?, - ) { - super.onViewCreated(view, savedInstanceState) - - title = getString(R.string.payment_method_fpx_title) - setHasOptionsMenu(true) - - with(emailEdit) { - setOnAfterTextChangeListener(::updateSubmitButton) - } - - submitButton.setOnClickListener(::submitForm) - } - - private fun updateSubmitButton() { - val text = emailEdit.text.toString() - - submitButton.isEnabled = text.isEmpty() || allowedEmailFormat.toRegex().matches(text) - } - - private fun submitForm() { - val requester = requester ?: return - val banks = requester.capability.paymentMethods?.find { it.name.equals("fpx") }?.banks - val email = emailEdit.text?.toString()?.trim().orEmpty() - - navigation?.navigateToFpxBankChooser(banks, email) - } -} diff --git a/sdk/src/main/java/co/omise/android/ui/GooglePayActivity.kt b/sdk/src/main/java/co/omise/android/ui/GooglePayActivity.kt deleted file mode 100644 index fbef36063..000000000 --- a/sdk/src/main/java/co/omise/android/ui/GooglePayActivity.kt +++ /dev/null @@ -1,357 +0,0 @@ -package co.omise.android.ui - -import android.app.Activity -import android.content.Intent -import android.os.Bundle -import android.util.Log -import android.view.MenuItem -import android.view.View -import android.widget.Toast -import androidx.activity.OnBackPressedCallback -import androidx.appcompat.app.AppCompatActivity -import co.omise.android.R -import co.omise.android.api.Client -import co.omise.android.api.RequestListener -import co.omise.android.extensions.getMessageFromResources -import co.omise.android.models.APIError -import co.omise.android.models.Token -import co.omise.android.models.TokenizationParam -import co.omise.android.request.GooglePay -import com.google.android.gms.common.api.ApiException -import com.google.android.gms.wallet.AutoResolveHelper -import com.google.android.gms.wallet.IsReadyToPayRequest -import com.google.android.gms.wallet.PaymentData -import com.google.android.gms.wallet.PaymentDataRequest -import com.google.android.gms.wallet.PaymentsClient -import kotlinx.android.synthetic.main.activity_google_pay.googlePayButton -import org.json.JSONException -import org.json.JSONObject -import java.io.IOError - -class GooglePayActivity : AppCompatActivity() { - private lateinit var pKey: String - private lateinit var googlePay: GooglePay - private lateinit var paymentsClient: PaymentsClient - private lateinit var cardNetworks: ArrayList - private var price: Long = 0 - private lateinit var currencyCode: String - private lateinit var merchantId: String - private var requestBillingAddress: Boolean = false - private var requestPhoneNumber: Boolean = false - - /** - * Arbitrarily-picked constant integer you define to track a request for payment data activity. - * - * @value #LOAD_PAYMENT_DATA_REQUEST_CODE - */ - private val loadPaymentDataRequestCode = 991 - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setContentView(R.layout.activity_google_pay) - - onBackPressedDispatcher.addCallback(this, onBackPressedCallback) - - initialize() - - setTitle(R.string.googlepay) - supportActionBar?.setDisplayHomeAsUpEnabled(true) - - googlePay = - GooglePay( - pKey, - cardNetworks, - price, - currencyCode, - merchantId, - requestBillingAddress, - requestPhoneNumber, - ) - paymentsClient = googlePay.createPaymentsClient(this) - possiblyShowGooglePayButton() - - googlePayButton.setOnClickListener { requestPayment() } - } - - private fun initialize() { - pKey = - requireNotNull(intent.getStringExtra(OmiseActivity.EXTRA_PKEY)) { - "${OmiseActivity.Companion::EXTRA_PKEY.name} must not be null." - } - cardNetworks = - requireNotNull(intent.getStringArrayListExtra(OmiseActivity.EXTRA_CARD_BRANDS)) { - "${OmiseActivity.Companion::EXTRA_CARD_BRANDS.name} must not be null." - } - price = - requireNotNull(intent.getLongExtra(OmiseActivity.EXTRA_AMOUNT, 0)) { - "${OmiseActivity.Companion::EXTRA_AMOUNT.name} must not be null." - } - currencyCode = - requireNotNull(intent.getStringExtra(OmiseActivity.EXTRA_CURRENCY)) { - "${OmiseActivity.Companion::EXTRA_CURRENCY.name} must not be null." - } - merchantId = - requireNotNull(intent.getStringExtra(OmiseActivity.EXTRA_GOOGLEPAY_MERCHANT_ID)) { - "${OmiseActivity.Companion::EXTRA_GOOGLEPAY_MERCHANT_ID.name} must not be null." - } - requestBillingAddress = - requireNotNull(intent.getBooleanExtra(OmiseActivity.EXTRA_GOOGLEPAY_REQUEST_BILLING_ADDRESS, false)) { - "${OmiseActivity.Companion::EXTRA_GOOGLEPAY_REQUEST_BILLING_ADDRESS.name} must not be null." - } - requestPhoneNumber = - requireNotNull(intent.getBooleanExtra(OmiseActivity.EXTRA_GOOGLEPAY_REQUEST_PHONE_NUMBER, false)) { - "${OmiseActivity.Companion::EXTRA_GOOGLEPAY_REQUEST_PHONE_NUMBER.name} must not be null." - } - } - - /** - * Determine the viewer's ability to pay with a payment method supported by your app and display a - * Google Pay payment button. - * - * @see [](https://developers.google.com/android/reference/com/google/android/gms/wallet/PaymentsClient.html.isReadyToPay - ) */ - private fun possiblyShowGooglePayButton() { - val isReadyToPayJson = googlePay.isReadyToPayRequest() ?: return - val request = IsReadyToPayRequest.fromJson(isReadyToPayJson.toString()) - - // The call to isReadyToPay is asynchronous and returns a Task. We need to provide an - // OnCompleteListener to be triggered when the result of the call is known. - val task = paymentsClient.isReadyToPay(request) - task.addOnCompleteListener { completedTask -> - try { - completedTask.getResult(ApiException::class.java)?.let(::setGooglePayAvailable) - } catch (exception: ApiException) { - Log.w("isReadyToPay failed", exception) - Toast.makeText( - this, - "Internal error occurred, please try a different payment method", - Toast.LENGTH_LONG, - ).show() - onBackPressedCallback.handleOnBackPressed() - } - } - } - - /** - * If isReadyToPay returned `true`, show the button and hide the "checking" text. Otherwise, - * notify the user that Google Pay is not available. Please adjust to fit in with your current - * user flow. You are not required to explicitly let the user know if isReadyToPay returns `false`. - * - * @param available isReadyToPay API response. - */ - private fun setGooglePayAvailable(available: Boolean) { - if (available) { - googlePayButton.visibility = View.VISIBLE - } else { - Toast.makeText( - this, - "Unfortunately, Google Pay is not available on this device", - Toast.LENGTH_LONG, - ).show() - onBackPressedCallback.handleOnBackPressed() - } - } - - private fun requestPayment() { - // Disables the button to prevent multiple clicks. - googlePayButton.isClickable = false - - val paymentDataRequestJson = googlePay.getPaymentDataRequest() - if (paymentDataRequestJson == null) { - Log.e("RequestPayment", "Can't fetch payment data request") - return - } - val request = PaymentDataRequest.fromJson(paymentDataRequestJson.toString()) - - // Since loadPaymentData may show the UI asking the user to select a payment method, we use - // AutoResolveHelper to wait for the user interacting with it. Once completed, - // onActivityResult will be called with the result. - AutoResolveHelper.resolveTask( - paymentsClient.loadPaymentData(request), - this, - loadPaymentDataRequestCode, - ) - } - - // can't move from deprecated onActivityResult due to https://github.com/Adyen/adyen-android/issues/771 - - /** - * Handle a resolved activity from the Google Pay payment sheet. - * - * @param requestCode Request code originally supplied to AutoResolveHelper in requestPayment(). - * @param resultCode Result code returned by the Google Pay API. - * @param data Intent from the Google Pay API containing payment or error data. - * @see [Getting a result - * from an Activity](https://developer.android.com/training/basics/intents/result) - */ - public override fun onActivityResult( - requestCode: Int, - resultCode: Int, - data: Intent?, - ) { - super.onActivityResult(requestCode, resultCode, data) - - when (requestCode) { - loadPaymentDataRequestCode -> { - when (resultCode) { - RESULT_OK -> - data?.let { intent -> - PaymentData.getFromIntent(intent)?.let(::handlePaymentSuccess) - } - - RESULT_CANCELED -> { - onBackPressedCallback.handleOnBackPressed() - } - - AutoResolveHelper.RESULT_ERROR -> { - AutoResolveHelper.getStatusFromIntent(data)?.let { - handleError(it.statusCode) - } - } - } - - // Re-enables the Google Pay payment button. - googlePayButton.isClickable = true - } - } - } - - /** - * PaymentData response object contains the payment information. - * We will call our tokens API here and add a listener to wait for its response. - * - * @param paymentData A response object returned by Google after a payer approves payment. - * @see [Payment - * Data](https://developers.google.com/pay/api/android/reference/object.PaymentData) - */ - private fun handlePaymentSuccess(paymentData: PaymentData) { - val paymentInformation = paymentData.toJson() - var billingName = "" - var billingCity = "" - var billingCountry = "" - var billingPostalCode = "" - var billingState = "" - var billingStreet1 = "" - var billingStreet2 = "" - var billingPhoneNumber = "" - - try { - // Token will be null if PaymentDataRequest was not constructed using fromJson(String). - val paymentMethodData = JSONObject(paymentInformation).getJSONObject("paymentMethodData") - - val paymentToken = - paymentMethodData - .getJSONObject("tokenizationData") - .getString("token") - - if (requestBillingAddress) { - val billingAddress = - paymentMethodData.getJSONObject("info") - .getJSONObject("billingAddress") - - billingName = billingAddress.getString("name") - billingCity = billingAddress.getString("locality") - billingCountry = billingAddress.getString("countryCode") - billingPostalCode = billingAddress.getString("postalCode") - billingState = billingAddress.getString("administrativeArea") - billingStreet1 = billingAddress.getString("address1") - billingStreet2 = - listOf( - billingAddress.getString("address2"), - billingAddress.getString("address3"), - ).filter { s -> s != "" }.joinToString(" ") - - if (requestPhoneNumber) { - billingPhoneNumber = billingAddress.getString("phoneNumber") - } - } - - val tokenParam = - TokenizationParam( - method = "googlepay", - data = paymentToken, - billingName = billingName, - billingCity = billingCity, - billingCountry = billingCountry, - billingPostalCode = billingPostalCode, - billingState = billingState, - billingStreet1 = billingStreet1, - billingStreet2 = billingStreet2, - billingPhoneNumber = billingPhoneNumber, - ) - - googlePayButton.isClickable = false - - val request = - Token.CreateTokenRequestBuilder(tokenization = tokenParam).build() - - val listener = CreateTokenRequestListener() - try { - Client(pKey).send(request, listener) - } catch (ex: Exception) { - listener.onRequestFailed(ex) - } - } catch (e: JSONException) { - Log.e("handlePaymentSuccess", "Error: $e") - } - } - - /** - * At this stage, the user has already seen a popup informing them an error occurred. Normally, - * only logging is required. - * - * @param statusCode will hold the value of any constant from CommonStatusCode or one of the - * WalletConstants.ERROR_CODE_* constants. - * @see [ - * Wallet Constants Library](https://developers.google.com/android/reference/com/google/android/gms/wallet/WalletConstants.constant-summary) - */ - private fun handleError(statusCode: Int) { - Log.w("loadPaymentData failed", String.format("Error code: %d", statusCode)) - } - - private inner class CreateTokenRequestListener : RequestListener { - override fun onRequestSucceed(model: Token) { - val data = Intent() - data.putExtra(OmiseActivity.EXTRA_TOKEN, model.id) - data.putExtra(OmiseActivity.EXTRA_TOKEN_OBJECT, model) - data.putExtra(OmiseActivity.EXTRA_CARD_OBJECT, model.card) - - setResult(Activity.RESULT_OK, data) - finish() - } - - override fun onRequestFailed(throwable: Throwable) { - val message = - when (throwable) { - is IOError -> getString(R.string.error_io, throwable.message) - is APIError -> throwable.getMessageFromResources(resources) - else -> getString(R.string.error_unknown, throwable.message) - } - - Toast.makeText( - baseContext, - message, - Toast.LENGTH_LONG, - ).show() - onBackPressedCallback.handleOnBackPressed() - } - } - - override fun onOptionsItemSelected(item: MenuItem): Boolean { - when (item.itemId) { - android.R.id.home -> { - setResult(RESULT_CANCELED) - finish() - } - } - return super.onOptionsItemSelected(item) - } - - private val onBackPressedCallback = - object : OnBackPressedCallback(true) { - override fun handleOnBackPressed() { - setResult(RESULT_CANCELED) - finish() - } - } -} diff --git a/sdk/src/main/java/co/omise/android/ui/InstallmentChooserFragment.kt b/sdk/src/main/java/co/omise/android/ui/InstallmentChooserFragment.kt deleted file mode 100644 index 6b22f28bb..000000000 --- a/sdk/src/main/java/co/omise/android/ui/InstallmentChooserFragment.kt +++ /dev/null @@ -1,104 +0,0 @@ -package co.omise.android.ui - -import android.os.Bundle -import android.view.View -import android.widget.LinearLayout -import co.omise.android.R -import co.omise.android.extensions.getParcelableArrayCompat -import co.omise.android.models.BackendType -import co.omise.android.models.PaymentMethod -import co.omise.android.models.SourceType -import co.omise.android.models.backendType - -/** - * InstallmentChooserFragment is the UI class, extended from base [OmiseListFragment] to show - * available Installment options list for the user to choose from. - */ -internal class InstallmentChooserFragment : OmiseListFragment() { - private val paymentMethods: List by lazy { - val args = arguments ?: return@lazy emptyList() - return@lazy (args.getParcelableArrayCompat(EXTRA_INSTALLMENT_METHODS)).toList() - } - private val requestedInstallmentAmount: Long by lazy { - val args = arguments ?: return@lazy 0 - return@lazy (args.getLong(EXTRA_REQUESTED_INSTALLMENT_AMOUNT)) - } - private val capabilityInstallmentAmount: Long by lazy { - val args = arguments ?: return@lazy 0 - return@lazy (args.getLong(EXTRA_CAPABILITY_INSTALLMENT_AMOUNT)) - } - private val allowedInstallments: List by lazy { - return@lazy paymentMethods.filter { - it.backendType is BackendType.Source && (it.backendType as BackendType.Source).sourceType is SourceType.Installment - } - .map { (it.backendType as BackendType.Source).sourceType as SourceType.Installment } - } - var navigation: PaymentCreatorNavigation? = null - - override fun onViewCreated( - view: View, - savedInstanceState: Bundle?, - ) { - super.onViewCreated(view, savedInstanceState) - - title = getString(R.string.installments_title) - setHasOptionsMenu(true) - if (requestedInstallmentAmount < capabilityInstallmentAmount) { - addNoBanksSupportedMessage() - } - } - - override fun listItems(): List { - val filteredSources = allowedInstallments.installmentResources.toMutableList() - val sourceNames = allowedInstallments.installmentResources.map { it.sourceType.name } - - allowedInstallments.installmentResources.forEach { source -> - // check if the source is white label installment - val sourceKey = source.sourceType.name!! - val isWlbSource = sourceKey.contains("_wlb_") - // if wlb then remove the source that is of the same bank but non wlb - if (isWlbSource) { - val containWlbAndNonWlb = sourceNames.contains(sourceKey.replace("_wlb_", "_")) - if (containWlbAndNonWlb) { - // remove the non wlb source - filteredSources.removeAll { it.sourceType.name == source.sourceType.name!!.replace("_wlb_", "_") } - } - } - } - - return filteredSources.map { resource -> - resource.apply { - enabled = requestedInstallmentAmount >= capabilityInstallmentAmount - } - } - } - - private fun addNoBanksSupportedMessage() { - val noBanksMessageLayOut = view?.findViewById(R.id.message_layout) - noBanksMessageLayOut?.visibility = View.VISIBLE - } - - override fun onListItemClicked(item: InstallmentResource) { - val choseInstallment = paymentMethods.first { (it.backendType as BackendType.Source).sourceType == item.sourceType } - navigation?.navigateToInstallmentTermChooser(choseInstallment) - } - - companion object { - private const val EXTRA_INSTALLMENT_METHODS = "InstallmentChooserFragment.installmentMethods" - private const val EXTRA_REQUESTED_INSTALLMENT_AMOUNT = "InstallmentChooserFragment.requestedInstallmentAmount" - private const val EXTRA_CAPABILITY_INSTALLMENT_AMOUNT = "InstallmentChooserFragment.capabilityInstallmentAmount" - - fun newInstance( - availableBanks: List, - requestedInstallmentAmount: Long, - capabilityInstallmentAmount: Long, - ) = InstallmentChooserFragment().apply { - arguments = - Bundle().apply { - putParcelableArray(EXTRA_INSTALLMENT_METHODS, availableBanks.toTypedArray()) - putLong(EXTRA_REQUESTED_INSTALLMENT_AMOUNT, requestedInstallmentAmount) - putLong(EXTRA_CAPABILITY_INSTALLMENT_AMOUNT, capabilityInstallmentAmount) - } - } - } -} diff --git a/sdk/src/main/java/co/omise/android/ui/InstallmentTermChooserFragment.kt b/sdk/src/main/java/co/omise/android/ui/InstallmentTermChooserFragment.kt deleted file mode 100644 index 63e3a1465..000000000 --- a/sdk/src/main/java/co/omise/android/ui/InstallmentTermChooserFragment.kt +++ /dev/null @@ -1,165 +0,0 @@ -package co.omise.android.ui - -import android.content.Intent -import android.os.Bundle -import android.view.View -import co.omise.android.R -import co.omise.android.extensions.getParcelableCompat -import co.omise.android.models.Amount -import co.omise.android.models.BackendType -import co.omise.android.models.PaymentMethod -import co.omise.android.models.Source -import co.omise.android.models.SourceType -import co.omise.android.models.backendType -import co.omise.android.ui.PaymentCreatorActivity.Companion.REQUEST_CREDIT_CARD_WITH_SOURCE - -/** - * InstallmentTermChooserFragment is the UI class, extended from base [OmiseListFragment] to show - * available Installment terms list for the user to choose from. User would be directed to this page - * from [InstallmentChooserFragment] page. - */ -internal class InstallmentTermChooserFragment : OmiseListFragment() { - var requester: PaymentCreatorRequester? = null - private val installment: PaymentMethod? by lazy { - arguments?.getParcelableCompat(EXTRA_INSTALLMENT) - } - - override fun onViewCreated( - view: View, - savedInstanceState: Bundle?, - ) { - super.onViewCreated(view, savedInstanceState) - title = - InstallmentResource.all - .find { it.sourceType == (installment?.backendType as BackendType.Source).sourceType } - ?.let { item -> item.titleRes?.let { getString(it) } ?: title } - setHasOptionsMenu(true) - } - - override fun listItems(): List { - val currency = requester!!.currency - val minimumInstallmentAmountPerType = - mapOf( - SourceType.Installment.Bay to Amount.fromLocalAmount(500.0, currency), - SourceType.Installment.BayWlb to Amount.fromLocalAmount(500.0, currency), - SourceType.Installment.FirstChoice to Amount.fromLocalAmount(300.0, currency), - SourceType.Installment.FirstChoiceWlb to Amount.fromLocalAmount(300.0, currency), - SourceType.Installment.Bbl to Amount.fromLocalAmount(500.0, currency), - SourceType.Installment.BblWlb to Amount.fromLocalAmount(500.0, currency), - SourceType.Installment.Mbb to Amount.fromLocalAmount(83.33, currency), - SourceType.Installment.Ktc to Amount.fromLocalAmount(300.0, currency), - SourceType.Installment.KtcWlb to Amount.fromLocalAmount(300.0, currency), - SourceType.Installment.KBank to Amount.fromLocalAmount(300.0, currency), - SourceType.Installment.KBankWlb to Amount.fromLocalAmount(300.0, currency), - SourceType.Installment.Scb to Amount.fromLocalAmount(500.0, currency), - SourceType.Installment.ScbWlb to Amount.fromLocalAmount(500.0, currency), - SourceType.Installment.Ttb to Amount.fromLocalAmount(500.0, currency), - SourceType.Installment.TtbWlb to Amount.fromLocalAmount(500.0, currency), - SourceType.Installment.Uob to Amount.fromLocalAmount(500.0, currency), - SourceType.Installment.UobWlb to Amount.fromLocalAmount(500.0, currency), - ) - val interestRatePerType = - mapOf( - SourceType.Installment.Bay to 0.0074, - SourceType.Installment.BayWlb to 0.0074, - SourceType.Installment.FirstChoice to 0.0116, - SourceType.Installment.FirstChoiceWlb to 0.0116, - SourceType.Installment.Bbl to 0.0074, - SourceType.Installment.BblWlb to 0.0074, - SourceType.Installment.Mbb to 0.0, - SourceType.Installment.Ktc to 0.0074, - SourceType.Installment.KtcWlb to 0.0074, - SourceType.Installment.KBank to 0.0065, - SourceType.Installment.KBankWlb to 0.0065, - SourceType.Installment.Scb to 0.0074, - SourceType.Installment.ScbWlb to 0.0074, - SourceType.Installment.Ttb to 0.008, - SourceType.Installment.TtbWlb to 0.008, - SourceType.Installment.Uob to 0.0064, - SourceType.Installment.UobWlb to 0.0064, - ) - val sourceType = (installment?.backendType as? BackendType.Source)?.sourceType!! - // White label installments require token and source - val requiresTokenRequest = sourceType.name!!.lowercase().contains("_wlb_") - return installment - ?.installmentTerms - .orEmpty() - .filter { term -> - val minimumAmount = minimumInstallmentAmountPerType[sourceType] - val req = requester - val amount = req!!.amount - val zeroInterestInstallments = req.capability.zeroInterestInstallments - var interestAmount = 0.0 - if (!zeroInterestInstallments) { - val rate = interestRatePerType[sourceType] ?: 0.0 - interestAmount = amount.toDouble() * rate - } - val installmentAmountPerMonth = (amount + interestAmount) / term - minimumAmount == null || installmentAmountPerMonth >= minimumAmount.amount - } - .map { term -> - InstallmentTermResource( - title = - with(term) { - if (this > 1) { - getString(R.string.payment_method_installment_term_months_title, this) - } else { - getString(R.string.payment_method_installment_term_month_title, this) - } - }, - installmentTerm = term, - indicatorIconRes = if (requiresTokenRequest) R.drawable.ic_next else R.drawable.ic_redirect, - ) - } - } - - override fun onListItemClicked(item: InstallmentTermResource) { - val req = requester ?: return - val sourceType = (installment?.backendType as? BackendType.Source)?.sourceType!! - // White label installments require token and source - val requiresTokenRequest = sourceType.name!!.lowercase().contains("_wlb_") - - if (requiresTokenRequest) { - // navigate to credit card screen - val intent = - Intent(activity, CreditCardActivity::class.java).apply { - putExtra(OmiseActivity.EXTRA_PKEY, activity?.intent?.getStringExtra(OmiseActivity.EXTRA_PKEY)) - putExtra( - OmiseActivity.EXTRA_IS_SECURE, - activity?.intent?.getBooleanExtra( - OmiseActivity.EXTRA_IS_SECURE, - true, - ), - ) - putExtra(OmiseActivity.EXTRA_SELECTED_INSTALLMENTS_TERM, item.installmentTerm) - putExtra(OmiseActivity.EXTRA_SELECTED_INSTALLMENTS_PAYMENT_METHOD, installment) - putExtra(OmiseActivity.EXTRA_CURRENCY, req.currency) - putExtra(OmiseActivity.EXTRA_CAPABILITY, req.capability) - putExtra(OmiseActivity.EXTRA_AMOUNT, req.amount) - } - activity?.startActivityForResult(intent, REQUEST_CREDIT_CARD_WITH_SOURCE) - } else { - view?.let { setAllViewsEnabled(it, false) } - val request = - Source.CreateSourceRequestBuilder(req.amount, req.currency, sourceType) - .installmentTerm(item.installmentTerm) - .zeroInterestInstallments(req.capability.zeroInterestInstallments) - .build() - requester?.request(request) { - view?.let { setAllViewsEnabled(it, true) } - } - } - } - - companion object { - private const val EXTRA_INSTALLMENT = "InstallmentTermChooserFragment.installment" - - fun newInstance(installment: PaymentMethod) = - InstallmentTermChooserFragment().apply { - arguments = - Bundle().apply { - putParcelable(EXTRA_INSTALLMENT, installment) - } - } - } -} diff --git a/sdk/src/main/java/co/omise/android/ui/InternetBankingChooserFragment.kt b/sdk/src/main/java/co/omise/android/ui/InternetBankingChooserFragment.kt deleted file mode 100644 index 23d9235b3..000000000 --- a/sdk/src/main/java/co/omise/android/ui/InternetBankingChooserFragment.kt +++ /dev/null @@ -1,74 +0,0 @@ -package co.omise.android.ui - -import android.os.Bundle -import android.view.View -import co.omise.android.R -import co.omise.android.extensions.getParcelableArrayCompat -import co.omise.android.models.BackendType -import co.omise.android.models.PaymentMethod -import co.omise.android.models.Source -import co.omise.android.models.SourceType -import co.omise.android.models.backendType - -/** - * InternetBankingChooserFragment is the UI class, extended from base [OmiseListFragment] to show - * available Internet Banking options list for the user to choose from. - */ -internal class InternetBankingChooserFragment : OmiseListFragment() { - private val allowedBanks: List by lazy { - val args = arguments ?: return@lazy emptyList() - val paymentMethods = - args.getParcelableArrayCompat(EXTRA_INTERNET_BANKING_METHODS) - return@lazy paymentMethods - .filter { - it.backendType is BackendType.Source && - (it.backendType as BackendType.Source).sourceType is SourceType.InternetBanking - } - .map { (it.backendType as BackendType.Source).sourceType as SourceType.InternetBanking } - } - - var requester: PaymentCreatorRequester? = null - - override fun onViewCreated( - view: View, - savedInstanceState: Bundle?, - ) { - super.onViewCreated(view, savedInstanceState) - - title = getString(R.string.internet_banking_chooser_title) - setHasOptionsMenu(true) - } - - override fun onListItemClicked(item: InternetBankingResource) { - val req = requester ?: return - - view?.let { setAllViewsEnabled(it, false) } - - val sourceType = item.sourceType - val request = - Source.CreateSourceRequestBuilder(req.amount, req.currency, sourceType).build() - requester?.request(request) { - view?.let { setAllViewsEnabled(it, true) } - } - } - - override fun listItems(): List { - return allowedBanks.internetBankingResources - } - - companion object { - private const val EXTRA_INTERNET_BANKING_METHODS = - "InternetBankingChooserFragment.internetBankingMethods" - - fun newInstance(availableBanks: List) = - InternetBankingChooserFragment().apply { - arguments = - Bundle().apply { - putParcelableArray( - EXTRA_INTERNET_BANKING_METHODS, - availableBanks.toTypedArray(), - ) - } - } - } -} diff --git a/sdk/src/main/java/co/omise/android/ui/MobileBankingChooserFragment.kt b/sdk/src/main/java/co/omise/android/ui/MobileBankingChooserFragment.kt deleted file mode 100644 index 2e72bd3a5..000000000 --- a/sdk/src/main/java/co/omise/android/ui/MobileBankingChooserFragment.kt +++ /dev/null @@ -1,72 +0,0 @@ -package co.omise.android.ui - -import android.os.Bundle -import android.view.View -import co.omise.android.R -import co.omise.android.extensions.getParcelableArrayCompat -import co.omise.android.models.BackendType -import co.omise.android.models.PaymentMethod -import co.omise.android.models.Source -import co.omise.android.models.SourceType -import co.omise.android.models.backendType - -/** - * MobileBankingChooserFragment is the UI class, extended from base [OmiseListFragment] to show - * available Mobile Banking options list for the user to choose from. - */ -internal class MobileBankingChooserFragment : OmiseListFragment() { - private val allowedBanks: List by lazy { - val args = arguments ?: return@lazy emptyList() - val paymentMethods = - args.getParcelableArrayCompat(EXTRA_MOBILE_BANKING_METHODS) - return@lazy paymentMethods - .filter { - it.backendType is BackendType.Source && - (it.backendType as BackendType.Source).sourceType is SourceType.MobileBanking - } - .map { (it.backendType as BackendType.Source).sourceType as SourceType.MobileBanking } - } - - var requester: PaymentCreatorRequester? = null - - override fun onViewCreated( - view: View, - savedInstanceState: Bundle?, - ) { - super.onViewCreated(view, savedInstanceState) - - title = getString(R.string.mobile_banking_chooser_title) - setHasOptionsMenu(true) - } - - override fun onListItemClicked(item: MobileBankingResource) { - val req = requester ?: return - - view?.let { setAllViewsEnabled(it, false) } - - val sourceType = item.sourceType - val request = - Source.CreateSourceRequestBuilder(req.amount, req.currency, sourceType).build() - requester?.request(request) { view?.let { setAllViewsEnabled(it, true) } } - } - - override fun listItems(): List { - return allowedBanks.mobileBankingResources - } - - companion object { - private const val EXTRA_MOBILE_BANKING_METHODS = - "MobileBankingChooserFragment.mobileBankingMethods" - - fun newInstance(availableBanks: List) = - MobileBankingChooserFragment().apply { - arguments = - Bundle().apply { - putParcelableArray( - EXTRA_MOBILE_BANKING_METHODS, - availableBanks.toTypedArray(), - ) - } - } - } -} diff --git a/sdk/src/main/java/co/omise/android/ui/NumberRangeSpinnerAdapter.java b/sdk/src/main/java/co/omise/android/ui/NumberRangeSpinnerAdapter.java deleted file mode 100644 index c938a93fa..000000000 --- a/sdk/src/main/java/co/omise/android/ui/NumberRangeSpinnerAdapter.java +++ /dev/null @@ -1,113 +0,0 @@ -package co.omise.android.ui; - -import android.content.Context; -import android.database.DataSetObserver; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.SpinnerAdapter; -import android.widget.TextView; - -import java.util.ArrayList; -import java.util.List; - -/** - * @deprecated replaced by {@link ExpiryDateEditText} - */ -@Deprecated -public abstract class NumberRangeSpinnerAdapter implements SpinnerAdapter { - private List observers = new ArrayList<>(); - - private final int min; - private final int max; - - protected NumberRangeSpinnerAdapter(int min, int max) { - this.min = min; - this.max = max; - } - - protected String getItemDropDownLabel(int number) { - return Integer.toString(number); - } - - protected String getItemLabel(int number) { - return Integer.toString(number); - } - - public int getPosition(int number) { - return number - min; - } - - @Override - public View getDropDownView(int position, View convertView, ViewGroup parent) { - Context context = parent.getContext(); - if (convertView == null) { - LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); - convertView = inflater.inflate(android.R.layout.simple_dropdown_item_1line, parent, false); - } - - TextView textView = convertView.findViewById(android.R.id.text1); - textView.setText(getItemDropDownLabel((Integer) getItem(position))); - - return convertView; - } - - @Override - public View getView(int position, View convertView, ViewGroup parent) { - Context context = parent.getContext(); - if (convertView == null) { - LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); - convertView = inflater.inflate(android.R.layout.simple_spinner_item, parent, false); - } - - TextView textView = convertView.findViewById(android.R.id.text1); - textView.setText(getItemLabel((Integer) getItem(position))); - - return convertView; - } - - @Override - public void registerDataSetObserver(DataSetObserver observer) { - observers.add(observer); - } - - @Override - public void unregisterDataSetObserver(DataSetObserver observer) { - observers.remove(observer); - } - - @Override - public int getCount() { - return max - min + 1; - } - - @Override - public Object getItem(int position) { - return min + position; - } - - @Override - public long getItemId(int position) { - return getItem(position).hashCode(); - } - - @Override - public boolean hasStableIds() { - return true; - } - - @Override - public int getItemViewType(int position) { - return 0; - } - - @Override - public int getViewTypeCount() { - return 1; - } - - @Override - public boolean isEmpty() { - return false; - } -} diff --git a/sdk/src/main/java/co/omise/android/ui/OmiseEditText.kt b/sdk/src/main/java/co/omise/android/ui/OmiseEditText.kt deleted file mode 100644 index 8721b48bc..000000000 --- a/sdk/src/main/java/co/omise/android/ui/OmiseEditText.kt +++ /dev/null @@ -1,40 +0,0 @@ -package co.omise.android.ui - -import android.content.Context -import android.util.AttributeSet -import androidx.appcompat.widget.AppCompatEditText - -/** - * OmiseEditText is the base class for all other custom EditTexts in the SDK. This base - * class performs a basic validation check on the input. - */ -open class OmiseEditText : AppCompatEditText { - val isValid: Boolean - get() = - try { - validate() - true - } catch (e: InputValidationException) { - false - } - - @Throws(InputValidationException::class) - open fun validate() { - val value = text.toString().trim() - if (value.isEmpty()) { - throw InputValidationException.EmptyInputException - } - } - - constructor(context: Context) : super(context) - - constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) - - constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) -} - -sealed class InputValidationException : Exception() { - object EmptyInputException : InputValidationException() - - object InvalidInputException : InputValidationException() -} diff --git a/sdk/src/main/java/co/omise/android/ui/OmiseFragment.kt b/sdk/src/main/java/co/omise/android/ui/OmiseFragment.kt deleted file mode 100644 index fccf68397..000000000 --- a/sdk/src/main/java/co/omise/android/ui/OmiseFragment.kt +++ /dev/null @@ -1,62 +0,0 @@ -package co.omise.android.ui - -import android.os.Bundle -import android.view.Menu -import android.view.MenuInflater -import android.view.MenuItem -import android.view.View -import android.view.ViewGroup -import androidx.appcompat.app.ActionBar -import androidx.appcompat.app.AppCompatActivity -import androidx.fragment.app.Fragment - -/** - * OmiseFragment is the base class for all other fragments in the SDK. - */ -abstract class OmiseFragment : Fragment() { - var title: String? = null - - private val actionBar: ActionBar? - get() = (activity as? AppCompatActivity)?.supportActionBar - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - parentFragmentManager.addOnBackStackChangedListener { - actionBar?.title = title - } - } - - override fun onCreateOptionsMenu( - menu: Menu, - inflater: MenuInflater, - ) { - menu.clear() - } - - override fun onOptionsItemSelected(item: MenuItem): Boolean { - when (item.itemId) { - android.R.id.home -> { - fragmentManager?.popBackStack() - return true - } - } - return super.onOptionsItemSelected(item) - } - - protected fun setAllViewsEnabled( - view: View, - isEnabled: Boolean, - ) { - view.isEnabled = isEnabled - if (view is ViewGroup) { - for (index in 0 until view.childCount) { - val targetView = view.getChildAt(index) - targetView.isEnabled = isEnabled - if (targetView is ViewGroup) { - setAllViewsEnabled(targetView, isEnabled) - } - } - } - } -} diff --git a/sdk/src/main/java/co/omise/android/ui/OmiseListFragment.kt b/sdk/src/main/java/co/omise/android/ui/OmiseListFragment.kt deleted file mode 100644 index 2a8b67638..000000000 --- a/sdk/src/main/java/co/omise/android/ui/OmiseListFragment.kt +++ /dev/null @@ -1,207 +0,0 @@ -package co.omise.android.ui - -import android.content.Context -import android.graphics.Canvas -import android.graphics.Rect -import android.graphics.drawable.Drawable -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.widget.ImageView -import android.widget.LinearLayout -import android.widget.TextView -import androidx.appcompat.content.res.AppCompatResources -import androidx.recyclerview.widget.LinearLayoutManager -import androidx.recyclerview.widget.RecyclerView -import androidx.recyclerview.widget.RecyclerView.NO_POSITION -import co.omise.android.R -import kotlinx.android.synthetic.main.fragment_list.no_data_text -import kotlinx.android.synthetic.main.fragment_list.recycler_view - -/** - * OmiseListFragment is the base class for all list-based UI classes. - */ -abstract class OmiseListFragment : OmiseFragment() { - abstract fun onListItemClicked(item: T) - - abstract fun listItems(): List - - protected val noDataText: TextView by lazy { no_data_text } - private val recyclerView: RecyclerView by lazy { recycler_view } - - private val onClickListener = - object : OmiseListItemClickListener { - override fun onClick(item: OmiseListItem) { - onListItemClicked(item as T) - } - } - - private val adapter: OmiseListAdapter by lazy { OmiseListAdapter(listItems(), onClickListener) } - - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle?, - ): View? { - return inflater.inflate(R.layout.fragment_list, container, false) - } - - override fun onViewCreated( - view: View, - savedInstanceState: Bundle?, - ) { - super.onViewCreated(view, savedInstanceState) - - val layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false) - - recyclerView.layoutManager = layoutManager - context?.let { recyclerView.addItemDecoration(OmiseItemDecoration(it)) } - - if (adapter.itemCount == 0) { - noDataText.visibility = View.VISIBLE - } else { - noDataText.visibility = View.INVISIBLE - } - - recyclerView.adapter = adapter - } - - protected fun setUiEnabled(isEnabled: Boolean) { - recyclerView.isEnabled = isEnabled - } -} - -class OmiseListAdapter(val list: List, val listener: OmiseListItemClickListener?) : - RecyclerView.Adapter() { - override fun onCreateViewHolder( - parent: ViewGroup, - viewType: Int, - ): OmiseItemViewHolder { - val itemView = - LayoutInflater.from(parent.context).inflate(R.layout.list_item, parent, false) - return OmiseItemViewHolder(itemView, listener) - } - - override fun getItemCount(): Int = list.size - - override fun onBindViewHolder( - holder: OmiseItemViewHolder, - position: Int, - ) { - val option = list[position] - holder.bind(option) - } -} - -class OmiseItemViewHolder(val view: View, val listener: OmiseListItemClickListener?) : - RecyclerView.ViewHolder(view) { - fun bind(item: OmiseListItem) { - val listItemView = view.findViewById(R.id.list_item_view) - val optionImage = view.findViewById(R.id.image_item_icon) - val nameText = view.findViewById(R.id.text_item_title) - val subtitleText = view.findViewById(R.id.subtext_item_title) - val typeImage = view.findViewById(R.id.image_indicator_icon) - - if (item.iconRes != null) { - optionImage.setImageResource(item.iconRes!!) - optionImage.visibility = View.VISIBLE - } else { - optionImage.visibility = View.GONE - } - - nameText.text = item.titleRes?.let { view.context.getString(it) } ?: item.title - subtitleText.text = item.subtitleRes?.let { view.context.getString(it) } ?: item.subtitle - - if (item.enabled == true) { - item.indicatorIconRes?.let { typeImage.setImageResource(it) } - listItemView.isEnabled = true - listItemView.alpha = 1F - } else { - item.indicatorIconRes?.let { typeImage.setImageResource(android.R.color.transparent) } - listItemView.isEnabled = false - listItemView.alpha = 0.2F - } - - if (subtitleText.text.isNotBlank()) { - subtitleText.visibility = View.VISIBLE - } else { - subtitleText.visibility = View.GONE - } - - view.setOnClickListener { listener?.onClick(item) } - } -} - -interface OmiseListItem { - val iconRes: Int? - get() = null - val title: String? - get() = null - val titleRes: Int? - get() = null - val subtitle: String? - get() = null - val subtitleRes: Int? - get() = null - val indicatorIconRes: Int? - get() = null - val enabled: Boolean? - get() = true -} - -interface OmiseListItemClickListener { - fun onClick(item: OmiseListItem) -} - -private class OmiseItemDecoration(val context: Context) : RecyclerView.ItemDecoration() { - private val divider: Drawable? = - AppCompatResources.getDrawable(context, R.drawable.item_decoration) - - override fun onDrawOver( - c: Canvas, - parent: RecyclerView, - state: RecyclerView.State, - ) { - val divider = divider ?: return - - val childCount = parent.childCount - - loop@ for (i in 0 until childCount) { - val view = parent.getChildAt(i) - val pos = parent.getChildAdapterPosition(view) - - if (pos == NO_POSITION) { - continue@loop - } - - val params = view.layoutParams as RecyclerView.LayoutParams - - val titleText = view.findViewById(R.id.text_item_title) - val left = titleText.left - val right = parent.width - val top = view.bottom + params.bottomMargin - val bottom = top + divider.intrinsicHeight - divider.setBounds(left, top, right, bottom) - divider.draw(c) - } - } - - override fun getItemOffsets( - outRect: Rect, - view: View, - parent: RecyclerView, - state: RecyclerView.State, - ) { - super.getItemOffsets(outRect, view, parent, state) - - val divider = divider ?: return - - val pos = parent.getChildAdapterPosition(view) - if (pos == NO_POSITION) { - return - } - - outRect.bottom = divider.intrinsicHeight - } -} diff --git a/sdk/src/main/java/co/omise/android/ui/PaymentChooserFragment.kt b/sdk/src/main/java/co/omise/android/ui/PaymentChooserFragment.kt deleted file mode 100644 index 32b5394a0..000000000 --- a/sdk/src/main/java/co/omise/android/ui/PaymentChooserFragment.kt +++ /dev/null @@ -1,132 +0,0 @@ -package co.omise.android.ui - -import android.app.Activity -import android.os.Bundle -import android.view.Menu -import android.view.MenuInflater -import android.view.MenuItem -import android.view.View -import co.omise.android.R -import co.omise.android.extensions.getParcelableCompat -import co.omise.android.models.Capability -import co.omise.android.models.Source -import co.omise.android.models.SourceType -import co.omise.android.models.SupportedEcontext -import co.omise.android.models.installmentMethods -import co.omise.android.models.internetBankingMethods -import co.omise.android.models.mobileBankingMethods - -/** - * PaymentChooserFragment is the UI class, extended from base [OmiseListFragment] to show - * available payment method options list for the user to choose from. - */ -internal class PaymentChooserFragment : OmiseListFragment() { - var navigation: PaymentCreatorNavigation? = null - var requester: PaymentCreatorRequester? = null - val capability: Capability by lazy { - requireNotNull( - arguments?.getParcelableCompat(EXTRA_CAPABILITY), - ) { "Capability must not be null." } - } - - override fun listItems(): List { - return capability.paymentMethodResources - } - - override fun onListItemClicked(item: PaymentMethodResource) { - val navigation = this.navigation ?: throw IllegalArgumentException("PaymentCreatorNavigation must not be null.") - when (item) { - PaymentMethodResource.CreditCard -> navigation.navigateToCreditCardForm() - PaymentMethodResource.Installments -> capability.installmentMethods.let(navigation::navigateToInstallmentChooser) - PaymentMethodResource.InternetBankings -> capability.internetBankingMethods.let(navigation::navigateToInternetBankingChooser) - PaymentMethodResource.MobileBankings -> capability.mobileBankingMethods.let(navigation::navigateToMobileBankingChooser) - PaymentMethodResource.ConvenienceStore -> - navigation.navigateToEContextForm( - SupportedEcontext.ConvenienceStore, - ) - PaymentMethodResource.PayEasy -> navigation.navigateToEContextForm(SupportedEcontext.PayEasy) - PaymentMethodResource.Netbanking -> navigation.navigateToEContextForm(SupportedEcontext.Netbanking) - PaymentMethodResource.TrueMoney -> navigation.navigateToTrueMoneyForm() - PaymentMethodResource.TrueMoneyJumpApp, - PaymentMethodResource.TescoLotus, - PaymentMethodResource.Alipay, - PaymentMethodResource.PayNow, - PaymentMethodResource.PromptPay, - PaymentMethodResource.AlipayCn, - PaymentMethodResource.AlipayHk, - PaymentMethodResource.Dana, - PaymentMethodResource.Gcash, - PaymentMethodResource.Kakaopay, - PaymentMethodResource.TouchNGo, - PaymentMethodResource.RabbitLinepay, - PaymentMethodResource.OcbcDigital, - PaymentMethodResource.Boost, - PaymentMethodResource.ShopeePay, - PaymentMethodResource.ShopeePayJumpApp, - PaymentMethodResource.DuitNowQR, - PaymentMethodResource.MaybankQR, - PaymentMethodResource.GrabPay, - PaymentMethodResource.PayPay, - PaymentMethodResource.GrabPayRMS, - PaymentMethodResource.TouchNGoAlipay, - PaymentMethodResource.WeChatPay, - -> item.sourceType?.let(::sendRequest) - PaymentMethodResource.Fpx -> navigation.navigateToFpxEmailForm() - PaymentMethodResource.GooglePay -> navigation.navigateToGooglePayForm() - PaymentMethodResource.DuitNowOBW -> navigation.navigateToDuitNowOBWBankChooser(capability) - PaymentMethodResource.Atome -> navigation.navigateToAtomeForm() - } - } - - override fun onViewCreated( - view: View, - savedInstanceState: Bundle?, - ) { - super.onViewCreated(view, savedInstanceState) - title = getString(R.string.payment_chooser_title) - setHasOptionsMenu(true) - } - - override fun onCreateOptionsMenu( - menu: Menu, - inflater: MenuInflater, - ) { - inflater.inflate(R.menu.menu_toolbar, menu) - } - - override fun onOptionsItemSelected(item: MenuItem): Boolean { - when (item.itemId) { - R.id.close_menu -> { - activity?.let { - it.setResult(Activity.RESULT_CANCELED) - it.finish() - } - } - } - return super.onOptionsItemSelected(item) - } - - private fun sendRequest(sourceType: SourceType) { - val requester = requester ?: return - - val request = Source.CreateSourceRequestBuilder(requester.amount, requester.currency, sourceType).build() - view?.let { setAllViewsEnabled(it, false) } - - requester.request(request) { - view?.let { setAllViewsEnabled(it, true) } - } - } - - companion object { - private const val EXTRA_CAPABILITY = "PaymentChooserFragment.capability" - - fun newInstance(capability: Capability): PaymentChooserFragment { - return PaymentChooserFragment().apply { - arguments = - Bundle().apply { - putParcelable(EXTRA_CAPABILITY, capability) - } - } - } - } -} diff --git a/sdk/src/main/java/co/omise/android/ui/PaymentCreatorActivity.kt b/sdk/src/main/java/co/omise/android/ui/PaymentCreatorActivity.kt index 8fd480808..f8e831045 100644 --- a/sdk/src/main/java/co/omise/android/ui/PaymentCreatorActivity.kt +++ b/sdk/src/main/java/co/omise/android/ui/PaymentCreatorActivity.kt @@ -49,27 +49,9 @@ class PaymentCreatorActivity : OmiseActivity() { private lateinit var pkey: String private var amount: Long = 0L private lateinit var currency: String - private lateinit var capability: Capability - private var cardBrands = arrayListOf() private lateinit var googlepayMerchantId: String private var googlepayRequestBillingAddress: Boolean = false private var googlepayRequestPhoneNumber: Boolean = false - private val snackbar: Snackbar by lazy { Snackbar.make(payment_creator_container, "", Snackbar.LENGTH_SHORT) } - - private lateinit var client: Client - - @TestOnly - fun setClient(client: Client) { - this.client = client - } - - private lateinit var requester: PaymentCreatorRequester - - @VisibleForTesting - lateinit var navigation: PaymentCreatorNavigation - - private lateinit var progressBar: ProgressBar - private lateinit var errorMessage: TextView override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -91,155 +73,6 @@ class PaymentCreatorActivity : OmiseActivity() { finish() } - // Set the menu button to close the view by the user - override fun onCreateOptionsMenu(menu: Menu): Boolean { - if (supportFragmentManager.findFragmentById(R.id.payment_creator_container) !is PaymentChooserFragment) { - menuInflater.inflate(R.menu.menu_toolbar, menu) - return true - } - return false - } - - override fun onOptionsItemSelected(item: MenuItem): Boolean { - when (item.itemId) { - R.id.close_menu -> { - setResult(Activity.RESULT_CANCELED) - finish() - } - } - return super.onOptionsItemSelected(item) - } - - private fun loadCapability() { - // Start loading - progressBar.visibility = ProgressBar.VISIBLE - // Hide error message - errorMessage.visibility = TextView.GONE - // Get capability - val capabilityRequest = Capability.GetCapabilitiesRequestBuilder().build() - client.send( - capabilityRequest, - object : RequestListener { - override fun onRequestSucceed(model: Capability) { - updateActivityWithCapability(model) - // Invalidate the options menu to trigger a refresh and hide the menu button - // as new button will come from the next view - invalidateOptionsMenu() - // Hide loading - progressBar.visibility = ProgressBar.GONE - } - - override fun onRequestFailed(throwable: Throwable) { - progressBar.visibility = ProgressBar.GONE - // Show the error message - errorMessage.text = getString(R.string.error_loading_payment_methods) - errorMessage.visibility = TextView.VISIBLE - } - }, - ) - } - - // Detect if the current activity is still active - private fun isActivityActive(): Boolean { - return !isFinishing && !isDestroyed - } - - private fun updateActivityWithCapability(newCapability: Capability) { - capability = newCapability - requester = PaymentCreatorRequesterImpl(client, amount, currency, newCapability) - navigation = - PaymentCreatorNavigationImpl( - this, - pkey, - amount, - currency, - cardBrands, - googlepayMerchantId, - googlepayRequestBillingAddress, - googlepayRequestPhoneNumber, - REQUEST_CREDIT_CARD, - requester, - newCapability, - ) - capability = filterCapabilities(newCapability) - - // Replace the capability passed from merchant by the new capability - intent.putExtra(EXTRA_CAPABILITY, capability) - // Open the payment method chooser if the activity is still active - if (isActivityActive()) { - navigation.navigateToPaymentChooser(capability) - } - - requester.listener = - object : PaymentCreatorRequestListener { - override fun onSourceCreated(result: Result) { - if (result.isSuccess) { - result.getOrNull()?.let { - navigation.createSourceFinished(it) - } - } else { - val message = - when (val error = result.exceptionOrNull()) { - is IOError -> getString(R.string.error_io, error.message) - is APIError -> error.getMessageFromResources(resources) - else -> getString(R.string.error_unknown, error?.message) - } - result.exceptionOrNull()?.let { - snackbar.setText(message) - snackbar.show() - } - } - } - } - supportFragmentManager.addOnBackStackChangedListener { - supportActionBar?.let { - if (supportFragmentManager.findFragmentById(R.id.payment_creator_container) is PaymentChooserFragment) { - it.setDisplayHomeAsUpEnabled(false) - it.setHomeButtonEnabled(false) - } else { - it.setDisplayHomeAsUpEnabled(true) - it.setHomeButtonEnabled(true) - } - } - } - } - - // Filter the capabilities based on the merchant request and what is available in the capabilities of the merchant account - private fun filterCapabilities(capability: Capability): Capability { - val merchantPassedCapabilities = intent.parcelableNullable(EXTRA_CAPABILITY) - val filteredPaymentMethods: List? - val filteredTokenizationMethods: List? - if (merchantPassedCapabilities != null) { - val selectedPaymentMethods = merchantPassedCapabilities.paymentMethods - val selectedTokenizationMethods = merchantPassedCapabilities.tokenizationMethods - val isMerchantOnlyChangingInterest = selectedPaymentMethods.isNullOrEmpty() && selectedTokenizationMethods.isNullOrEmpty() - if (selectedPaymentMethods != null && !isMerchantOnlyChangingInterest) { - filteredPaymentMethods = - capability.paymentMethods?.filter { capMethod -> - selectedPaymentMethods.map { it.name }.contains(capMethod.name) - } - capability.paymentMethods = filteredPaymentMethods?.toMutableList() - } - if (selectedTokenizationMethods != null && !isMerchantOnlyChangingInterest) { - filteredTokenizationMethods = - capability.tokenizationMethods?.filter { - selectedTokenizationMethods.contains(it) - } - capability.tokenizationMethods = filteredTokenizationMethods - } - capability.zeroInterestInstallments = merchantPassedCapabilities.zeroInterestInstallments - } - // Add the tokenization methods into payment methods since the SDK only shows paymentMethods - val combinedMethods = capability.paymentMethods?.toMutableList() - capability.tokenizationMethods?.forEach { method -> - run { - combinedMethods?.add(PaymentMethod(method)) - } - } - capability.paymentMethods = combinedMethods - return capability - } - // TODO: find a way to unit test ActivityResult launcher in order to be able to move from deprecated onActivityResult override fun onActivityResult( requestCode: Int, @@ -279,9 +112,6 @@ class PaymentCreatorActivity : OmiseActivity() { require(intent.hasExtra(it)) { "Could not found $it." } } pkey = requireNotNull(intent.getStringExtra(EXTRA_PKEY)) { "${::EXTRA_PKEY.name} must not be null." } - if (!this::client.isInitialized) { - client = Client(pkey) - } amount = intent.getLongExtra(EXTRA_AMOUNT, 0) currency = requireNotNull(intent.getStringExtra(EXTRA_CURRENCY)) { "${::EXTRA_CURRENCY.name} must not be null." } googlepayMerchantId = intent.getStringExtra(EXTRA_GOOGLEPAY_MERCHANT_ID) ?: "[GOOGLEPAY_MERCHANT_ID]" @@ -297,239 +127,3 @@ class PaymentCreatorActivity : OmiseActivity() { const val REQUEST_CREDIT_CARD_WITH_SOURCE = 101 } } - -interface PaymentCreatorNavigation { - fun navigateToPaymentChooser(capability: Capability) - - fun navigateToCreditCardForm() - - fun navigateToInternetBankingChooser(allowedBanks: List) - - fun navigateToMobileBankingChooser(allowedBanks: List) - - fun navigateToInstallmentChooser(allowedInstalls: List) - - fun navigateToInstallmentTermChooser(installment: PaymentMethod) - - fun navigateToEContextForm(eContext: SupportedEcontext) - - fun navigateToAtomeForm() - - fun createSourceFinished(source: Source) - - fun navigateToTrueMoneyForm() - - fun navigateToFpxEmailForm() - - fun navigateToFpxBankChooser( - banks: List?, - email: String, - ) - - fun navigateToGooglePayForm() - - fun navigateToDuitNowOBWBankChooser(capability: Capability) -} - -private class PaymentCreatorNavigationImpl( - private val activity: PaymentCreatorActivity, - private val pkey: String, - private val amount: Long, - private val currency: String, - private var cardBrands: ArrayList, - private var googlepayMerchantId: String, - private var googlepayRequestBillingAddress: Boolean, - private var googlepayRequestPhoneNumber: Boolean, - private val requestCode: Int, - private val requester: PaymentCreatorRequester, - private val capability: Capability, -) : PaymentCreatorNavigation { - companion object { - const val FRAGMENT_STACK = "PaymentCreatorNavigation.fragmentStack" - } - - private val supportFragmentManager = activity.supportFragmentManager - - private fun addFragmentToBackStack(fragment: Fragment) { - supportFragmentManager.beginTransaction() - .add(R.id.payment_creator_container, fragment) - .addToBackStack(FRAGMENT_STACK) - .commit() - } - - override fun navigateToPaymentChooser(capability: Capability) { - val fragment = - PaymentChooserFragment.newInstance(capability).apply { - navigation = this@PaymentCreatorNavigationImpl - requester = this@PaymentCreatorNavigationImpl.requester - } - addFragmentToBackStack(fragment) - } - - override fun navigateToCreditCardForm() { - val intent = - Intent(activity, CreditCardActivity::class.java).apply { - putExtra(EXTRA_PKEY, pkey) - putExtra(EXTRA_IS_SECURE, activity.intent.getBooleanExtra(EXTRA_IS_SECURE, true)) - } - activity.startActivityForResult(intent, requestCode) - } - - override fun navigateToInternetBankingChooser(allowedBanks: List) { - val fragment = - InternetBankingChooserFragment.newInstance(allowedBanks).apply { - requester = this@PaymentCreatorNavigationImpl.requester - } - addFragmentToBackStack(fragment) - } - - override fun navigateToMobileBankingChooser(allowedBanks: List) { - val fragment = - MobileBankingChooserFragment.newInstance(allowedBanks).apply { - requester = this@PaymentCreatorNavigationImpl.requester - } - addFragmentToBackStack(fragment) - } - - override fun navigateToInstallmentChooser(allowedInstalls: List) { - val minInstallmentAmount = capability.limits?.installmentAmount?.min ?: 0 - - val fragment = - InstallmentChooserFragment.newInstance(allowedInstalls, amount, minInstallmentAmount).apply { - navigation = this@PaymentCreatorNavigationImpl - } - addFragmentToBackStack(fragment) - } - - override fun navigateToInstallmentTermChooser(installment: PaymentMethod) { - val fragment = - InstallmentTermChooserFragment.newInstance(installment).apply { - requester = this@PaymentCreatorNavigationImpl.requester - } - addFragmentToBackStack(fragment) - } - - override fun navigateToEContextForm(eContext: SupportedEcontext) { - val fragment = - EContextFormFragment.newInstance(eContext).apply { - requester = this@PaymentCreatorNavigationImpl.requester - } - addFragmentToBackStack(fragment) - } - - override fun navigateToAtomeForm() { - val fragment = - AtomeFormFragment().apply { - requester = this@PaymentCreatorNavigationImpl.requester - } - addFragmentToBackStack(fragment) - } - - override fun createSourceFinished(source: Source) { - val intent = - Intent().apply { - putExtra(EXTRA_SOURCE_OBJECT, source) - } - activity.setResult(Activity.RESULT_OK, intent) - activity.finish() - } - - override fun navigateToTrueMoneyForm() { - val fragment = - TrueMoneyFormFragment().apply { - requester = this@PaymentCreatorNavigationImpl.requester - } - addFragmentToBackStack(fragment) - } - - override fun navigateToFpxEmailForm() { - val fragment = - FpxEmailFormFragment().apply { - navigation = this@PaymentCreatorNavigationImpl - requester = this@PaymentCreatorNavigationImpl.requester - } - addFragmentToBackStack(fragment) - } - - override fun navigateToFpxBankChooser( - banks: List?, - email: String, - ) { - val fragment = - FpxBankChooserFragment.newInstance(banks, email).apply { - requester = this@PaymentCreatorNavigationImpl.requester - } - addFragmentToBackStack(fragment) - } - - override fun navigateToGooglePayForm() { - val intent = - Intent(activity, GooglePayActivity::class.java).apply { - putExtra(EXTRA_PKEY, pkey) - putExtra(EXTRA_AMOUNT, amount) - putExtra(EXTRA_CURRENCY, currency) - putStringArrayListExtra(EXTRA_CARD_BRANDS, cardBrands) - putExtra(EXTRA_GOOGLEPAY_MERCHANT_ID, googlepayMerchantId) - putExtra(EXTRA_GOOGLEPAY_REQUEST_BILLING_ADDRESS, googlepayRequestBillingAddress) - putExtra(EXTRA_GOOGLEPAY_REQUEST_PHONE_NUMBER, googlepayRequestPhoneNumber) - } - activity.startActivityForResult(intent, requestCode) - } - - override fun navigateToDuitNowOBWBankChooser(capability: Capability) { - val banks = capability.paymentMethods?.find { it.name == SourceType.DuitNowOBW.name }?.banks ?: emptyList() - - val fragment = - DuitNowOBWBankChooserFragment.newInstance(banks).apply { - requester = this@PaymentCreatorNavigationImpl.requester - } - - addFragmentToBackStack(fragment) - } -} - -interface PaymentCreatorRequester { - val amount: Long - val currency: String - val capability: Capability - - fun request( - request: Request, - result: ((Result) -> Unit)? = null, - ) - - var listener: PaymentCreatorRequestListener? -} - -interface PaymentCreatorRequestListener { - fun onSourceCreated(result: Result) -} - -private class PaymentCreatorRequesterImpl( - private val client: Client, - override val amount: Long, - override val currency: String, - override val capability: Capability, -) : PaymentCreatorRequester { - override var listener: PaymentCreatorRequestListener? = null - - override fun request( - request: Request, - result: ((Result) -> Unit)?, - ) { - client.send( - request, - object : RequestListener { - override fun onRequestSucceed(model: Source) { - result?.invoke(Result.success(model)) - listener?.onSourceCreated(Result.success(model)) - } - - override fun onRequestFailed(throwable: Throwable) { - result?.invoke(Result.failure(throwable)) - listener?.onSourceCreated(Result.failure(throwable)) - } - }, - ) - } -} diff --git a/sdk/src/main/java/co/omise/android/ui/PaymentMethodResources.kt b/sdk/src/main/java/co/omise/android/ui/PaymentMethodResources.kt deleted file mode 100644 index 755f15a12..000000000 --- a/sdk/src/main/java/co/omise/android/ui/PaymentMethodResources.kt +++ /dev/null @@ -1,651 +0,0 @@ -package co.omise.android.ui - -import androidx.annotation.DrawableRes -import androidx.annotation.StringRes -import co.omise.android.R -import co.omise.android.models.BackendType -import co.omise.android.models.Capability -import co.omise.android.models.SourceType -import co.omise.android.models.TokenizationMethod -import co.omise.android.models.backendType - -internal val Capability.paymentMethodResources: List - get() { - val items = mutableListOf() - this.paymentMethods - .orEmpty() - .forEach { paymentMethod -> - when (paymentMethod.backendType) { - is BackendType.Token -> - when ((paymentMethod.backendType as BackendType.Token).tokenizationMethod) { - is TokenizationMethod.Card -> items.add(PaymentMethodResource.CreditCard) - else -> - PaymentMethodResource.all.find { - it.tokenizationMethod == (paymentMethod.backendType as? BackendType.Token)?.tokenizationMethod - }?.let { items.add(it) } - } - is BackendType.Source -> - when ((paymentMethod.backendType as BackendType.Source).sourceType) { - is SourceType.Installment -> items.add(PaymentMethodResource.Installments) - is SourceType.InternetBanking -> items.add(PaymentMethodResource.InternetBankings) - is SourceType.MobileBanking -> items.add(PaymentMethodResource.MobileBankings) - is SourceType.Econtext -> - items.addAll( - listOf( - PaymentMethodResource.ConvenienceStore, - PaymentMethodResource.PayEasy, - PaymentMethodResource.Netbanking, - ), - ) - is SourceType.TouchNGo -> { - when (paymentMethod.provider) { - PaymentMethodResource.ALIPAY_PLUS_PROVIDER -> - items.add( - PaymentMethodResource.TouchNGoAlipay, - ) - else -> items.add(PaymentMethodResource.TouchNGo) - } - } - - // TrueMoneyJumpApp replaces legacy TrueMoney. - // When TrueMoneyJumpApp is available in capability response, prefer it over legacy TrueMoney. - // When legacy TrueMoney is available in capability response without TrueMoneyJumpApp use it. - is SourceType.TrueMoneyJumpApp -> { - items.remove(PaymentMethodResource.TrueMoney) - items.add(PaymentMethodResource.TrueMoneyJumpApp) - } - - // when ShopeepayJumpApp is available will use is instead of ShopeePay normal flow - is SourceType.ShopeePayJumpApp -> { - // if ShopeePay is not in the list items list will got no modify - items.remove(PaymentMethodResource.ShopeePay) - items.add(PaymentMethodResource.ShopeePayJumpApp) - } - is SourceType.GrabPay -> { - when (paymentMethod.provider) { - PaymentMethodResource.RMS_PROVIDER -> - items.add( - PaymentMethodResource.GrabPayRMS, - ) - else -> items.add(PaymentMethodResource.GrabPay) - } - } - else -> - PaymentMethodResource.all.find { - it.sourceType == (paymentMethod.backendType as? BackendType.Source)?.sourceType - }?.let { items.add(it) } - } - } - } - return items.distinct() - } - -internal val List.installmentResources: List - get() = this.mapNotNull { sourceType -> InstallmentResource.all.find { it.sourceType == sourceType } } - -internal val List.internetBankingResources: List - get() = this.mapNotNull { sourceType -> InternetBankingResource.all.find { it.sourceType == sourceType } } - -internal val List.mobileBankingResources: List - get() = this.mapNotNull { sourceType -> MobileBankingResource.all.find { it.sourceType == sourceType } } - -internal sealed class PaymentMethodResource( - @DrawableRes override val iconRes: Int, - @StringRes override val titleRes: Int?, - @StringRes override val subtitleRes: Int? = null, - @DrawableRes override val indicatorIconRes: Int, - val isCreditCard: Boolean = false, - val sourceType: SourceType? = null, - val tokenizationMethod: TokenizationMethod? = null, -) : OmiseListItem { - object CreditCard : PaymentMethodResource( - iconRes = R.drawable.payment_card, - titleRes = R.string.payment_method_credit_card_title, - indicatorIconRes = R.drawable.ic_next, - isCreditCard = true, - ) - - object GooglePay : PaymentMethodResource( - iconRes = R.drawable.googlepay, - titleRes = R.string.googlepay, - indicatorIconRes = R.drawable.ic_next, - tokenizationMethod = TokenizationMethod.GooglePay, - ) - - object Installments : PaymentMethodResource( - iconRes = R.drawable.payment_installment, - titleRes = R.string.payment_method_installments_title, - indicatorIconRes = R.drawable.ic_next, - ) - - object InternetBankings : PaymentMethodResource( - iconRes = R.drawable.payment_banking, - titleRes = R.string.payment_method_internet_banking_title, - indicatorIconRes = R.drawable.ic_next, - ) - - object MobileBankings : PaymentMethodResource( - iconRes = R.drawable.payment_mobile, - titleRes = R.string.payment_method_mobile_banking_title, - indicatorIconRes = R.drawable.ic_next, - ) - - object TescoLotus : PaymentMethodResource( - iconRes = R.drawable.payment_tesco, - titleRes = R.string.payment_method_tesco_lotus_title, - indicatorIconRes = R.drawable.ic_redirect, - sourceType = SourceType.BillPaymentTescoLotus, - ) - - object ConvenienceStore : PaymentMethodResource( - iconRes = R.drawable.payment_conbini, - titleRes = R.string.payment_method_convenience_store_title, - indicatorIconRes = R.drawable.ic_next, - sourceType = SourceType.Econtext, - ) - - object PayEasy : PaymentMethodResource( - iconRes = R.drawable.payment_payeasy, - titleRes = R.string.payment_method_pay_easy_title, - indicatorIconRes = R.drawable.ic_next, - sourceType = SourceType.Econtext, - ) - - object Netbanking : PaymentMethodResource( - iconRes = R.drawable.payment_netbank, - titleRes = R.string.payment_method_netbank_title, - indicatorIconRes = R.drawable.ic_next, - sourceType = SourceType.Econtext, - ) - - object Alipay : PaymentMethodResource( - iconRes = R.drawable.payment_alipay, - titleRes = R.string.payment_method_alipay_title, - indicatorIconRes = R.drawable.ic_redirect, - sourceType = SourceType.Alipay, - ) - - object PromptPay : PaymentMethodResource( - iconRes = R.drawable.payment_promptpay, - titleRes = R.string.payment_method_promptpay_title, - indicatorIconRes = R.drawable.ic_redirect, - sourceType = SourceType.PromptPay, - ) - - object PayNow : PaymentMethodResource( - iconRes = R.drawable.payment_paynow, - titleRes = R.string.payment_method_paynow_title, - indicatorIconRes = R.drawable.ic_redirect, - sourceType = SourceType.PayNow, - ) - - object TrueMoney : PaymentMethodResource( - iconRes = R.drawable.payment_truemoney, - titleRes = R.string.payment_truemoney_title, - indicatorIconRes = R.drawable.ic_next, - sourceType = SourceType.TrueMoney, - ) - - object TrueMoneyJumpApp : PaymentMethodResource( - iconRes = R.drawable.payment_truemoney, - titleRes = R.string.payment_truemoney_title, - indicatorIconRes = R.drawable.ic_redirect, - sourceType = SourceType.TrueMoneyJumpApp, - ) - - object Fpx : PaymentMethodResource( - iconRes = R.drawable.payment_fpx, - titleRes = R.string.payment_method_fpx_title, - indicatorIconRes = R.drawable.ic_next, - sourceType = SourceType.Fpx(), - ) - - object AlipayCn : PaymentMethodResource( - iconRes = R.drawable.payment_alipay_cn, - titleRes = R.string.payment_method_alipay_cn_title, - subtitleRes = R.string.payment_method_alipayplus_footnote, - indicatorIconRes = R.drawable.ic_redirect, - sourceType = SourceType.AlipayCn, - ) - - object AlipayHk : PaymentMethodResource( - iconRes = R.drawable.payment_alipay_hk, - titleRes = R.string.payment_method_alipay_hk_title, - subtitleRes = R.string.payment_method_alipayplus_footnote, - indicatorIconRes = R.drawable.ic_redirect, - sourceType = SourceType.AlipayHk, - ) - - object Dana : PaymentMethodResource( - iconRes = R.drawable.payment_dana, - titleRes = R.string.payment_method_dana_title, - subtitleRes = R.string.payment_method_alipayplus_footnote, - indicatorIconRes = R.drawable.ic_redirect, - sourceType = SourceType.Dana, - ) - - object Gcash : PaymentMethodResource( - iconRes = R.drawable.payment_gcash, - titleRes = R.string.payment_method_gcash_title, - subtitleRes = R.string.payment_method_alipayplus_footnote, - indicatorIconRes = R.drawable.ic_redirect, - sourceType = SourceType.Gcash, - ) - - object Kakaopay : PaymentMethodResource( - iconRes = R.drawable.payment_kakaopay, - titleRes = R.string.payment_method_kakaopay_title, - subtitleRes = R.string.payment_method_alipayplus_footnote, - indicatorIconRes = R.drawable.ic_redirect, - sourceType = SourceType.Kakaopay, - ) - - object TouchNGo : PaymentMethodResource( - iconRes = R.drawable.payment_touch_n_go, - titleRes = R.string.payment_method_touch_n_go_title, - subtitleRes = null, - indicatorIconRes = R.drawable.ic_redirect, - sourceType = SourceType.TouchNGo(), - ) - - object TouchNGoAlipay : PaymentMethodResource( - iconRes = R.drawable.payment_touch_n_go, - titleRes = R.string.payment_method_touch_n_go_title, - subtitleRes = R.string.payment_method_alipayplus_footnote, - indicatorIconRes = R.drawable.ic_redirect, - sourceType = SourceType.TouchNGo(), - ) - - object RabbitLinepay : PaymentMethodResource( - iconRes = R.drawable.payment_rabbit_linepay, - titleRes = R.string.payment_method_rabbit_linepay_title, - indicatorIconRes = R.drawable.ic_redirect, - sourceType = SourceType.RabbitLinePay, - ) - - object OcbcDigital : PaymentMethodResource( - iconRes = R.drawable.payment_ocbc_digital, - titleRes = R.string.payment_method_ocbc_digital_title, - indicatorIconRes = R.drawable.ic_redirect, - sourceType = SourceType.OcbcDigital, - ) - - object Boost : PaymentMethodResource( - iconRes = R.drawable.payment_boost, - titleRes = R.string.payment_method_boots_title, - indicatorIconRes = R.drawable.ic_redirect, - sourceType = SourceType.Boost, - ) - - object ShopeePay : PaymentMethodResource( - iconRes = R.drawable.payment_shopeepay, - titleRes = R.string.payment_method_shopeepay_title, - indicatorIconRes = R.drawable.ic_redirect, - sourceType = SourceType.ShopeePay, - ) - - object ShopeePayJumpApp : PaymentMethodResource( - iconRes = R.drawable.payment_shopeepay, - titleRes = R.string.payment_method_shopeepay_title, - indicatorIconRes = R.drawable.ic_redirect, - sourceType = SourceType.ShopeePayJumpApp, - ) - - object DuitNowOBW : PaymentMethodResource( - iconRes = R.drawable.payment_duitnow_obw, - titleRes = R.string.payment_method_duitnow_obw_title, - indicatorIconRes = R.drawable.ic_redirect, - sourceType = SourceType.DuitNowOBW, - ) - - object DuitNowQR : PaymentMethodResource( - iconRes = R.drawable.payment_duitnow_qr, - titleRes = R.string.payment_method_duitnow_qr_title, - indicatorIconRes = R.drawable.ic_redirect, - sourceType = SourceType.DuitNowQR, - ) - - object MaybankQR : PaymentMethodResource( - iconRes = R.drawable.payment_mae_maybank, - titleRes = R.string.payment_method_maybank_qr_title, - indicatorIconRes = R.drawable.ic_redirect, - sourceType = SourceType.MaybankQR, - ) - - object GrabPay : PaymentMethodResource( - iconRes = R.drawable.payment_grabpay, - titleRes = R.string.payment_method_grabpay_title, - subtitleRes = R.string.payment_method_grabpay_footnote, - indicatorIconRes = R.drawable.ic_redirect, - sourceType = SourceType.GrabPay(), - ) - - object GrabPayRMS : PaymentMethodResource( - iconRes = R.drawable.payment_grabpay, - titleRes = R.string.payment_method_grabpay_rms_title, - indicatorIconRes = R.drawable.ic_redirect, - sourceType = SourceType.GrabPay(), - ) - - object PayPay : PaymentMethodResource( - iconRes = R.drawable.payment_paypay, - titleRes = R.string.payment_method_paypay_title, - indicatorIconRes = R.drawable.ic_redirect, - sourceType = SourceType.PayPay, - ) - - object Atome : PaymentMethodResource( - iconRes = R.drawable.payment_atome, - titleRes = R.string.payment_method_atome_title, - indicatorIconRes = R.drawable.ic_redirect, - sourceType = SourceType.Atome, - ) - - object WeChatPay : PaymentMethodResource( - iconRes = R.drawable.wechat_pay, - titleRes = R.string.payment_method_wechat_pay_title, - indicatorIconRes = R.drawable.ic_redirect, - sourceType = SourceType.WeChatPay, - ) - - companion object { - const val ALIPAY_PLUS_PROVIDER = "Alipay_plus" - const val RMS_PROVIDER = "RMS" - val all: List - get() = PaymentMethodResource::class.nestedClasses.mapNotNull { it.objectInstance as? PaymentMethodResource } - } -} - -internal sealed class InstallmentResource( - @DrawableRes override val iconRes: Int, - override val title: String? = null, - @StringRes override val titleRes: Int? = null, - @StringRes override val subtitleRes: Int? = null, - @DrawableRes override val indicatorIconRes: Int, - val sourceType: SourceType, - override var enabled: Boolean? = true, -) : OmiseListItem { - companion object { - val all: List - get() = InstallmentResource::class.nestedClasses.mapNotNull { it.objectInstance as? InstallmentResource } - } - - object Bbl : InstallmentResource( - iconRes = R.drawable.payment_bbl, - titleRes = R.string.payment_method_installment_bbl_title, - indicatorIconRes = R.drawable.ic_next, - sourceType = SourceType.Installment.Bbl, - ) - - object BblWlb : InstallmentResource( - iconRes = R.drawable.payment_bbl, - titleRes = R.string.payment_method_installment_bbl_title, - indicatorIconRes = R.drawable.ic_next, - sourceType = SourceType.Installment.BblWlb, - ) - - object Mbb : InstallmentResource( - iconRes = R.drawable.payment_maybank, - titleRes = R.string.payment_method_installment_mbb_title, - indicatorIconRes = R.drawable.ic_next, - sourceType = SourceType.Installment.Mbb, - ) - - object KBank : InstallmentResource( - iconRes = R.drawable.payment_kasikorn, - titleRes = R.string.payment_method_installment_kasikorn_title, - indicatorIconRes = R.drawable.ic_next, - sourceType = SourceType.Installment.KBank, - ) - - object KBankWlb : InstallmentResource( - iconRes = R.drawable.payment_kasikorn, - titleRes = R.string.payment_method_installment_kasikorn_title, - indicatorIconRes = R.drawable.ic_next, - sourceType = SourceType.Installment.KBankWlb, - ) - - object Bay : InstallmentResource( - iconRes = R.drawable.payment_bay, - titleRes = R.string.payment_method_installment_bay_title, - indicatorIconRes = R.drawable.ic_next, - sourceType = SourceType.Installment.Bay, - ) - - object BayWlb : InstallmentResource( - iconRes = R.drawable.payment_bay, - titleRes = R.string.payment_method_installment_bay_title, - indicatorIconRes = R.drawable.ic_next, - sourceType = SourceType.Installment.BayWlb, - ) - - object FirstChoice : InstallmentResource( - iconRes = R.drawable.payment_first_choice, - titleRes = R.string.payment_method_installment_first_choice_title, - indicatorIconRes = R.drawable.ic_next, - sourceType = SourceType.Installment.FirstChoice, - ) - - object FirstChoiceWlb : InstallmentResource( - iconRes = R.drawable.payment_first_choice, - titleRes = R.string.payment_method_installment_first_choice_title, - indicatorIconRes = R.drawable.ic_next, - sourceType = SourceType.Installment.FirstChoiceWlb, - ) - - object Ktc : InstallmentResource( - iconRes = R.drawable.payment_ktc, - titleRes = R.string.payment_method_installment_ktc_title, - indicatorIconRes = R.drawable.ic_next, - sourceType = SourceType.Installment.Ktc, - ) - - object KtcWlb : InstallmentResource( - iconRes = R.drawable.payment_ktc, - titleRes = R.string.payment_method_installment_ktc_title, - indicatorIconRes = R.drawable.ic_next, - sourceType = SourceType.Installment.KtcWlb, - ) - - object Scb : InstallmentResource( - iconRes = R.drawable.payment_scb, - titleRes = R.string.payment_method_installment_scb_title, - indicatorIconRes = R.drawable.ic_next, - sourceType = SourceType.Installment.Scb, - ) - - object ScbWlb : InstallmentResource( - iconRes = R.drawable.payment_scb, - titleRes = R.string.payment_method_installment_scb_title, - indicatorIconRes = R.drawable.ic_next, - sourceType = SourceType.Installment.ScbWlb, - ) - - object Ttb : InstallmentResource( - iconRes = R.drawable.payment_ttb, - titleRes = R.string.payment_method_installment_ttb_title, - indicatorIconRes = R.drawable.ic_next, - sourceType = SourceType.Installment.Ttb, - ) - - object TtbWlb : InstallmentResource( - iconRes = R.drawable.payment_ttb, - titleRes = R.string.payment_method_installment_ttb_title, - indicatorIconRes = R.drawable.ic_next, - sourceType = SourceType.Installment.TtbWlb, - ) - - object Uob : InstallmentResource( - iconRes = R.drawable.payment_uob, - titleRes = R.string.payment_method_installment_uob_title, - indicatorIconRes = R.drawable.ic_next, - sourceType = SourceType.Installment.Uob, - ) - - object UobWlb : InstallmentResource( - iconRes = R.drawable.payment_uob, - titleRes = R.string.payment_method_installment_uob_title, - indicatorIconRes = R.drawable.ic_next, - sourceType = SourceType.Installment.UobWlb, - ) -} - -internal data class InstallmentTermResource( - @DrawableRes override val iconRes: Int? = null, - override val title: String, - val installmentTerm: Int, - @DrawableRes override val indicatorIconRes: Int = R.drawable.ic_redirect, -) : OmiseListItem - -internal sealed class InternetBankingResource( - @DrawableRes override val iconRes: Int, - override val title: String? = null, - @StringRes override val titleRes: Int? = null, - @StringRes override val subtitleRes: Int? = null, - @DrawableRes override val indicatorIconRes: Int, - val sourceType: SourceType, -) : OmiseListItem { - companion object { - val all: List - get() = InternetBankingResource::class.nestedClasses.mapNotNull { it.objectInstance as? InternetBankingResource } - } - - object Bbl : InternetBankingResource( - iconRes = R.drawable.payment_bbl, - titleRes = R.string.payment_method_internet_banking_bbl_title, - indicatorIconRes = R.drawable.ic_redirect, - sourceType = SourceType.InternetBanking.Bbl, - ) - - object Bay : InternetBankingResource( - iconRes = R.drawable.payment_bay, - titleRes = R.string.payment_method_internet_banking_bay_title, - indicatorIconRes = R.drawable.ic_redirect, - sourceType = SourceType.InternetBanking.Bay, - ) -} - -internal sealed class MobileBankingResource( - @DrawableRes override val iconRes: Int, - override val title: String? = null, - @StringRes override val titleRes: Int? = null, - @StringRes override val subtitleRes: Int? = null, - @DrawableRes override val indicatorIconRes: Int, - val sourceType: SourceType, -) : OmiseListItem { - companion object { - val all: List - get() = MobileBankingResource::class.nestedClasses.mapNotNull { it.objectInstance as? MobileBankingResource } - } - - object Bay : MobileBankingResource( - iconRes = R.drawable.kma, - titleRes = R.string.payment_method_mobile_banking_bay_title, - indicatorIconRes = R.drawable.ic_redirect, - sourceType = SourceType.MobileBanking.Bay, - ) - - object Bbl : MobileBankingResource( - iconRes = R.drawable.bblm, - titleRes = R.string.payment_method_mobile_banking_bbl_title, - indicatorIconRes = R.drawable.ic_redirect, - sourceType = SourceType.MobileBanking.Bbl, - ) - - object KBank : MobileBankingResource( - iconRes = R.drawable.payment_kplus, - titleRes = R.string.payment_method_mobile_banking_kbank_title, - indicatorIconRes = R.drawable.ic_redirect, - sourceType = SourceType.MobileBanking.KBank, - ) - - object KTB : MobileBankingResource( - iconRes = R.drawable.payment_ktb_next, - titleRes = R.string.payment_method_mobile_banking_ktb_title, - indicatorIconRes = R.drawable.ic_redirect, - sourceType = SourceType.MobileBanking.KTB, - ) - - object Scb : MobileBankingResource( - iconRes = R.drawable.scb_easy, - titleRes = R.string.payment_method_mobile_banking_scb_title, - indicatorIconRes = R.drawable.ic_redirect, - sourceType = SourceType.MobileBanking.Scb, - ) -} - -internal class FpxResource( - @DrawableRes override val iconRes: Int, - override val title: String? = null, - @DrawableRes override val indicatorIconRes: Int = R.drawable.ic_redirect, - override val enabled: Boolean? = false, - val bankCode: String? = null, -) : OmiseListItem { - companion object { - val all: List - get() = FpxResource::class.nestedClasses.mapNotNull { it.objectInstance as? FpxResource } - - fun getBankImageFromCode(code: String?): Int { - return when (code) { - "affin" -> R.drawable.payment_affin - "alliance" -> R.drawable.payment_alliance - "agro" -> R.drawable.payment_agro - "ambank" -> R.drawable.payment_ambank - "islam" -> R.drawable.payment_islam - "muamalat" -> R.drawable.payment_muamalat - "rakyat" -> R.drawable.payment_rakyat - "bocm" -> R.drawable.payment_bocm - "bsn" -> R.drawable.payment_bsn - "cimb" -> R.drawable.payment_cimb - "hongleong" -> R.drawable.payment_hongleong - "hsbc" -> R.drawable.payment_hsbc - "kfh" -> R.drawable.payment_kfh - "maybank2e" -> R.drawable.payment_maybank - "maybank2u" -> R.drawable.payment_maybank - "ocbc" -> R.drawable.payment_ocbc - "public" -> R.drawable.payment_publicbank - "rhb" -> R.drawable.payment_rhb - "sc" -> R.drawable.payment_sc - "uob" -> R.drawable.payment_uob - else -> R.drawable.payment_unknown - } - } - } -} - -internal class DuitNowOBWResource( - @DrawableRes override val iconRes: Int, - override val title: String? = null, - @DrawableRes override val indicatorIconRes: Int = R.drawable.ic_redirect, - val bankCode: String? = null, - override val enabled: Boolean? = false, -) : OmiseListItem { - companion object { - val all: List - get() = DuitNowOBWResource::class.nestedClasses.mapNotNull { it.objectInstance as? DuitNowOBWResource } - - fun getBankImageFromCode(code: String?): Int { - return when (code) { - "affin" -> R.drawable.payment_affin - "alliance" -> R.drawable.payment_alliance - "agro" -> R.drawable.payment_agro - "ambank" -> R.drawable.payment_ambank - "islam" -> R.drawable.payment_islam - "muamalat" -> R.drawable.payment_muamalat - "rakyat" -> R.drawable.payment_rakyat - "bsn" -> R.drawable.payment_bsn - "cimb" -> R.drawable.payment_cimb - "hongleong" -> R.drawable.payment_hongleong - "hsbc" -> R.drawable.payment_hsbc - "kfh" -> R.drawable.payment_kfh - "maybank2u" -> R.drawable.payment_maybank - "ocbc" -> R.drawable.payment_ocbc - "public" -> R.drawable.payment_publicbank - "rhb" -> R.drawable.payment_rhb - "sc" -> R.drawable.payment_sc - "uob" -> R.drawable.payment_uob - else -> R.drawable.payment_unknown - } - } - } -} diff --git a/sdk/src/main/java/co/omise/android/ui/SecurityCodeEditText.kt b/sdk/src/main/java/co/omise/android/ui/SecurityCodeEditText.kt deleted file mode 100644 index 607dd3573..000000000 --- a/sdk/src/main/java/co/omise/android/ui/SecurityCodeEditText.kt +++ /dev/null @@ -1,40 +0,0 @@ -package co.omise.android.ui - -import android.content.Context -import android.text.InputFilter -import android.text.InputType -import android.util.AttributeSet - -/** - * SecurityCodeEditText is a custom EditText for credit card security code field. This - * EditText applies the security code range limitation and InputType. - */ -class SecurityCodeEditText : OmiseEditText { - companion object { - private const val CVV_LENGTH = 4 - private const val CVV_REGEX = "[0-9]{3,4}" - } - - val securityCode: String - get() = text.toString().trim() - - constructor(context: Context) : super(context) - - constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) - - constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) - - init { - filters = arrayOf(InputFilter.LengthFilter(CVV_LENGTH)) - inputType = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_VARIATION_PASSWORD - } - - override fun validate() { - super.validate() - - val value = text.toString().trim() - if (!CVV_REGEX.toRegex().matches(value)) { - throw InputValidationException.InvalidInputException - } - } -} diff --git a/sdk/src/main/java/co/omise/android/ui/SecurityCodeTooltipDialogFragment.kt b/sdk/src/main/java/co/omise/android/ui/SecurityCodeTooltipDialogFragment.kt deleted file mode 100644 index bd4a607db..000000000 --- a/sdk/src/main/java/co/omise/android/ui/SecurityCodeTooltipDialogFragment.kt +++ /dev/null @@ -1,79 +0,0 @@ -package co.omise.android.ui - -import android.graphics.Color -import android.graphics.drawable.ColorDrawable -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.widget.ImageButton -import android.widget.ImageView -import android.widget.TextView -import androidx.fragment.app.DialogFragment -import co.omise.android.R -import co.omise.android.extensions.getParcelableCompat -import co.omise.android.models.CardBrand -import kotlinx.android.synthetic.main.dialog_security_code_tooltip.close_button -import kotlinx.android.synthetic.main.dialog_security_code_tooltip.cvv_description_text -import kotlinx.android.synthetic.main.dialog_security_code_tooltip.cvv_image - -/** - * SecurityCodeTooltipDialogFragment is a UI class to show the user information about - * the security code and where it is found on the card. - */ -class SecurityCodeTooltipDialogFragment : DialogFragment() { - private val cvvImage: ImageView by lazy { cvv_image } - private val cvvDescriptionText: TextView by lazy { cvv_description_text } - private val closeButton: ImageButton by lazy { close_button } - private var cardBrand: CardBrand? = null - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - cardBrand = arguments?.getParcelableCompat(EXTRA_CARD_BRAND) - } - - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle?, - ): View? { - return inflater.inflate(R.layout.dialog_security_code_tooltip, container) - } - - override fun onViewCreated( - view: View, - savedInstanceState: Bundle?, - ) { - super.onViewCreated(view, savedInstanceState) - - dialog?.window?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT)) - - closeButton.setOnClickListener { dismiss() } - - when (cardBrand) { - CardBrand.AMEX -> { - cvvImage.setImageResource(R.drawable.cvv_4_digits) - cvvDescriptionText.setText(R.string.cvv_tooltip_4_digits) - } - else -> { - cvvImage.setImageResource(R.drawable.cvv_3_digits) - cvvDescriptionText.setText(R.string.cvv_tooltip_3_digits) - } - } - } - - companion object { - const val EXTRA_CARD_BRAND = "SecurityCodeTooltipDialogFragment.CardBrand" - - fun newInstant(brand: CardBrand? = null): SecurityCodeTooltipDialogFragment { - val argument = Bundle() - argument.putParcelable(EXTRA_CARD_BRAND, brand) - - val dialogFragment = SecurityCodeTooltipDialogFragment() - dialogFragment.arguments = argument - - return dialogFragment - } - } -} diff --git a/sdk/src/main/java/co/omise/android/ui/TrueMoneyFormFragment.kt b/sdk/src/main/java/co/omise/android/ui/TrueMoneyFormFragment.kt deleted file mode 100644 index 78f050d76..000000000 --- a/sdk/src/main/java/co/omise/android/ui/TrueMoneyFormFragment.kt +++ /dev/null @@ -1,90 +0,0 @@ -package co.omise.android.ui - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.View.INVISIBLE -import android.view.ViewGroup -import android.widget.Button -import co.omise.android.R -import co.omise.android.extensions.setOnAfterTextChangeListener -import co.omise.android.extensions.setOnClickListener -import co.omise.android.models.Source -import co.omise.android.models.SourceType -import kotlinx.android.synthetic.main.fragment_true_money_form.button_submit -import kotlinx.android.synthetic.main.fragment_true_money_form.edit_phone_number -import kotlinx.android.synthetic.main.fragment_true_money_form.text_phone_number_error - -/** - * TrueMoneyFormFragment is the UI class for handling TrueMoney payment method. - */ -class TrueMoneyFormFragment : OmiseFragment() { - var requester: PaymentCreatorRequester? = null - - private val phoneNumberEdit: OmiseEditText by lazy { edit_phone_number } - private val phoneNumberErrorText by lazy { text_phone_number_error } - private val submitButton: Button by lazy { button_submit } - - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle?, - ): View? { - return inflater.inflate(R.layout.fragment_true_money_form, container, false) - } - - override fun onViewCreated( - view: View, - savedInstanceState: Bundle?, - ) { - super.onViewCreated(view, savedInstanceState) - - title = getString(R.string.payment_truemoney_title) - setHasOptionsMenu(true) - - with(phoneNumberEdit) { - setOnFocusChangeListener { _, hasFocus -> - updateErrorText(hasFocus) - } - setOnAfterTextChangeListener(::updateSubmitButton) - } - - submitButton.setOnClickListener(::submitForm) - } - - private fun updateErrorText(hasFocus: Boolean) { - if (hasFocus || phoneNumberEdit.isValid) { - with(phoneNumberErrorText) { - text = "" - visibility = INVISIBLE - } - return - } - - phoneNumberErrorText.text = getString(R.string.error_invalid_phone_number) - } - - private fun updateSubmitButton() { - submitButton.isEnabled = phoneNumberEdit.isValid - } - - private fun submitForm() { - val requester = requester ?: return - - val phoneNumber = phoneNumberEdit.text?.toString()?.trim().orEmpty() - - val request = - Source.CreateSourceRequestBuilder( - requester.amount, - requester.currency, - SourceType.TrueMoney, - ) - .phoneNumber(phoneNumber) - .build() - - view?.let { setAllViewsEnabled(it, false) } - requester.request(request) { - view?.let { setAllViewsEnabled(it, true) } - } - } -} diff --git a/sdk/src/main/res/layout/activity_credit_card.xml b/sdk/src/main/res/layout/activity_credit_card.xml deleted file mode 100644 index 5cecbd50c..000000000 --- a/sdk/src/main/res/layout/activity_credit_card.xml +++ /dev/null @@ -1,316 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -