Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • videostreaming/futopayclientlibraries
  • alex/futopayclientlibraries
2 results
Show changes
Commits on Source (17)
Showing
with 157 additions and 611 deletions
# Default ignored files
/shelf/
/workspace.xml
PolycentricCore
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="AndroidTestResultsUserPreferences">
<option name="androidTestResultsTableState">
<map>
<entry key="-2030737957">
<value>
<AndroidTestResultsTableState>
<option name="preferredColumnWidths">
<map>
<entry key="Duration" value="90" />
<entry key="Pixel_3a_API_33_x86_64" value="120" />
<entry key="Tests" value="360" />
</map>
</option>
</AndroidTestResultsTableState>
</value>
</entry>
<entry key="-1888365331">
<value>
<AndroidTestResultsTableState>
<option name="preferredColumnWidths">
<map>
<entry key="Duration" value="90" />
<entry key="R5CNC07TZCY" value="120" />
<entry key="Tests" value="360" />
<entry key="samsung&#10; SM-G998B" value="120" />
</map>
</option>
</AndroidTestResultsTableState>
</value>
</entry>
<entry key="-1766545009">
<value>
<AndroidTestResultsTableState>
<option name="preferredColumnWidths">
<map>
<entry key="Duration" value="90" />
<entry key="R5CNC07TZCY" value="120" />
<entry key="Tests" value="360" />
<entry key="samsung&#10; SM-G998B" value="120" />
</map>
</option>
</AndroidTestResultsTableState>
</value>
</entry>
<entry key="-1603504896">
<value>
<AndroidTestResultsTableState>
<option name="preferredColumnWidths">
<map>
<entry key="Duration" value="90" />
<entry key="R5CNC07TZCY" value="120" />
<entry key="Tests" value="360" />
<entry key="samsung&#10; SM-G998B" value="120" />
</map>
</option>
</AndroidTestResultsTableState>
</value>
</entry>
<entry key="-1533817792">
<value>
<AndroidTestResultsTableState>
<option name="preferredColumnWidths">
<map>
<entry key="Duration" value="90" />
<entry key="Pixel_3a_API_33_x86_64" value="120" />
<entry key="R5CNC07TZCY" value="120" />
<entry key="Tests" value="360" />
<entry key="samsung&#10; SM-G998B" value="120" />
</map>
</option>
</AndroidTestResultsTableState>
</value>
</entry>
<entry key="-1348472475">
<value>
<AndroidTestResultsTableState>
<option name="preferredColumnWidths">
<map>
<entry key="29211JEGR13699" value="120" />
<entry key="Duration" value="90" />
<entry key="Google&#10; Pixel 6a" value="120" />
<entry key="R5CNC07TZCY" value="120" />
<entry key="Tests" value="360" />
<entry key="samsung&#10; SM-G998B" value="120" />
</map>
</option>
</AndroidTestResultsTableState>
</value>
</entry>
<entry key="-1191746456">
<value>
<AndroidTestResultsTableState>
<option name="preferredColumnWidths">
<map>
<entry key="Duration" value="90" />
<entry key="R5CNC07TZCY" value="120" />
<entry key="Tests" value="360" />
<entry key="samsung&#10; SM-G998B" value="120" />
</map>
</option>
</AndroidTestResultsTableState>
</value>
</entry>
<entry key="-1089424001">
<value>
<AndroidTestResultsTableState>
<option name="preferredColumnWidths">
<map>
<entry key="Duration" value="90" />
<entry key="R5CNC07TZCY" value="120" />
<entry key="Tests" value="360" />
<entry key="samsung&#10; SM-G998B" value="120" />
</map>
</option>
</AndroidTestResultsTableState>
</value>
</entry>
<entry key="-1030942155">
<value>
<AndroidTestResultsTableState>
<option name="preferredColumnWidths">
<map>
<entry key="29211JEGR13699" value="120" />
<entry key="Duration" value="90" />
<entry key="Google&#10; Pixel 6a" value="120" />
<entry key="Pixel_3a_API_33_x86_64" value="120" />
<entry key="Pixel_C_API_33" value="120" />
<entry key="Tests" value="360" />
</map>
</option>
</AndroidTestResultsTableState>
</value>
</entry>
<entry key="-1013244967">
<value>
<AndroidTestResultsTableState>
<option name="preferredColumnWidths">
<map>
<entry key="Duration" value="90" />
<entry key="R5CNC07TZCY" value="120" />
<entry key="Tests" value="360" />
<entry key="samsung&#10; SM-G998B" value="120" />
</map>
</option>
</AndroidTestResultsTableState>
</value>
</entry>
<entry key="-998275601">
<value>
<AndroidTestResultsTableState>
<option name="preferredColumnWidths">
<map>
<entry key="29211JEGR13699" value="120" />
<entry key="Duration" value="90" />
<entry key="Google&#10; Pixel 6a" value="120" />
<entry key="Pixel_3a_API_33_x86_64" value="120" />
<entry key="Tests" value="360" />
</map>
</option>
</AndroidTestResultsTableState>
</value>
</entry>
<entry key="-693989101">
<value>
<AndroidTestResultsTableState>
<option name="preferredColumnWidths">
<map>
<entry key="Duration" value="90" />
<entry key="R5CNC07TZCY" value="120" />
<entry key="Tests" value="360" />
<entry key="samsung&#10; SM-G998B" value="120" />
</map>
</option>
</AndroidTestResultsTableState>
</value>
</entry>
<entry key="-483192549">
<value>
<AndroidTestResultsTableState>
<option name="preferredColumnWidths">
<map>
<entry key="Duration" value="90" />
<entry key="Pixel_3a_API_33_x86_64" value="120" />
<entry key="Tests" value="360" />
</map>
</option>
</AndroidTestResultsTableState>
</value>
</entry>
<entry key="-396783379">
<value>
<AndroidTestResultsTableState>
<option name="preferredColumnWidths">
<map>
<entry key="29211JEGR13699" value="120" />
<entry key="Duration" value="90" />
<entry key="Google&#10; Pixel 6a" value="120" />
<entry key="Pixel_3a_API_33_x86_64" value="120" />
<entry key="Pixel_C_API_33" value="120" />
<entry key="R5CNC07TZCY" value="120" />
<entry key="Tests" value="360" />
<entry key="samsung&#10; SM-G998B" value="120" />
</map>
</option>
</AndroidTestResultsTableState>
</value>
</entry>
<entry key="-285941453">
<value>
<AndroidTestResultsTableState>
<option name="preferredColumnWidths">
<map>
<entry key="Duration" value="90" />
<entry key="Pixel_3a_API_33_x86_64" value="120" />
<entry key="R5CNC07TZCY" value="120" />
<entry key="Tests" value="360" />
<entry key="samsung&#10; SM-G998B" value="120" />
</map>
</option>
</AndroidTestResultsTableState>
</value>
</entry>
<entry key="-43042216">
<value>
<AndroidTestResultsTableState>
<option name="preferredColumnWidths">
<map>
<entry key="Duration" value="90" />
<entry key="Pixel_3a_API_33_x86_64" value="120" />
<entry key="Tests" value="360" />
</map>
</option>
</AndroidTestResultsTableState>
</value>
</entry>
<entry key="143109477">
<value>
<AndroidTestResultsTableState>
<option name="preferredColumnWidths">
<map>
<entry key="29211JEGR13699" value="120" />
<entry key="Duration" value="90" />
<entry key="Google&#10; Pixel 6a" value="120" />
<entry key="R5CNC07TZCY" value="120" />
<entry key="Tests" value="360" />
<entry key="samsung&#10; SM-G998B" value="120" />
</map>
</option>
</AndroidTestResultsTableState>
</value>
</entry>
<entry key="217836404">
<value>
<AndroidTestResultsTableState>
<option name="preferredColumnWidths">
<map>
<entry key="Duration" value="90" />
<entry key="Pixel_3a_API_33_x86_64" value="120" />
<entry key="Tests" value="360" />
</map>
</option>
</AndroidTestResultsTableState>
</value>
</entry>
<entry key="309039273">
<value>
<AndroidTestResultsTableState>
<option name="preferredColumnWidths">
<map>
<entry key="Duration" value="90" />
<entry key="R5CNC07TZCY" value="120" />
<entry key="Tests" value="360" />
<entry key="samsung&#10; SM-G998B" value="120" />
</map>
</option>
</AndroidTestResultsTableState>
</value>
</entry>
<entry key="568179418">
<value>
<AndroidTestResultsTableState>
<option name="preferredColumnWidths">
<map>
<entry key="Duration" value="90" />
<entry key="R5CNC07TZCY" value="120" />
<entry key="Tests" value="360" />
<entry key="samsung&#10; SM-G998B" value="120" />
</map>
</option>
</AndroidTestResultsTableState>
</value>
</entry>
<entry key="728115857">
<value>
<AndroidTestResultsTableState>
<option name="preferredColumnWidths">
<map>
<entry key="Duration" value="90" />
<entry key="Pixel_3a_API_33_x86_64" value="120" />
<entry key="Tests" value="360" />
</map>
</option>
</AndroidTestResultsTableState>
</value>
</entry>
<entry key="762617096">
<value>
<AndroidTestResultsTableState>
<option name="preferredColumnWidths">
<map>
<entry key="29211JEGR13699" value="120" />
<entry key="Duration" value="90" />
<entry key="Google&#10; Pixel 6a" value="120" />
<entry key="R5CNC07TZCY" value="120" />
<entry key="Tests" value="360" />
<entry key="samsung&#10; SM-G998B" value="120" />
</map>
</option>
</AndroidTestResultsTableState>
</value>
</entry>
<entry key="1154603043">
<value>
<AndroidTestResultsTableState>
<option name="preferredColumnWidths">
<map>
<entry key="29211JEGR13699" value="120" />
<entry key="Duration" value="90" />
<entry key="Google&#10; Pixel 6a" value="120" />
<entry key="Tests" value="360" />
</map>
</option>
</AndroidTestResultsTableState>
</value>
</entry>
<entry key="1367930197">
<value>
<AndroidTestResultsTableState>
<option name="preferredColumnWidths">
<map>
<entry key="Duration" value="90" />
<entry key="Pixel_3a_API_33_x86_64" value="120" />
<entry key="Tests" value="360" />
</map>
</option>
</AndroidTestResultsTableState>
</value>
</entry>
<entry key="1392532197">
<value>
<AndroidTestResultsTableState>
<option name="preferredColumnWidths">
<map>
<entry key="Duration" value="90" />
<entry key="Pixel_3a_API_33_x86_64" value="120" />
<entry key="Tests" value="360" />
</map>
</option>
</AndroidTestResultsTableState>
</value>
</entry>
<entry key="1445499490">
<value>
<AndroidTestResultsTableState>
<option name="preferredColumnWidths">
<map>
<entry key="Duration" value="90" />
<entry key="Pixel_3a_API_33_x86_64" value="120" />
<entry key="Tests" value="360" />
</map>
</option>
</AndroidTestResultsTableState>
</value>
</entry>
<entry key="1489134328">
<value>
<AndroidTestResultsTableState>
<option name="preferredColumnWidths">
<map>
<entry key="29211JEGR13699" value="120" />
<entry key="Duration" value="90" />
<entry key="Google&#10; Pixel 6a" value="120" />
<entry key="Tests" value="360" />
</map>
</option>
</AndroidTestResultsTableState>
</value>
</entry>
<entry key="1814633309">
<value>
<AndroidTestResultsTableState>
<option name="preferredColumnWidths">
<map>
<entry key="Duration" value="90" />
<entry key="R5CNC07TZCY" value="120" />
<entry key="Tests" value="360" />
<entry key="samsung&#10; SM-G998B" value="120" />
</map>
</option>
</AndroidTestResultsTableState>
</value>
</entry>
<entry key="1823866028">
<value>
<AndroidTestResultsTableState>
<option name="preferredColumnWidths">
<map>
<entry key="Duration" value="90" />
<entry key="R5CNC07TZCY" value="120" />
<entry key="Tests" value="360" />
<entry key="samsung&#10; SM-G998B" value="120" />
</map>
</option>
</AndroidTestResultsTableState>
</value>
</entry>
<entry key="1824280393">
<value>
<AndroidTestResultsTableState>
<option name="preferredColumnWidths">
<map>
<entry key="Duration" value="90" />
<entry key="R5CNC07TZCY" value="120" />
<entry key="Tests" value="360" />
<entry key="samsung&#10; SM-G998B" value="120" />
</map>
</option>
</AndroidTestResultsTableState>
</value>
</entry>
<entry key="1956518097">
<value>
<AndroidTestResultsTableState>
<option name="preferredColumnWidths">
<map>
<entry key="29211JEGR13699" value="120" />
<entry key="Duration" value="90" />
<entry key="Google&#10; Pixel 6a" value="120" />
<entry key="Pixel_3a_API_33_x86_64" value="120" />
<entry key="R5CNC07TZCY" value="120" />
<entry key="Tests" value="360" />
<entry key="samsung&#10; SM-G998B" value="120" />
</map>
</option>
</AndroidTestResultsTableState>
</value>
</entry>
<entry key="2076253436">
<value>
<AndroidTestResultsTableState>
<option name="preferredColumnWidths">
<map>
<entry key="29211JEGR13699" value="120" />
<entry key="Duration" value="90" />
<entry key="Google&#10; Pixel 6a" value="120" />
<entry key="Tests" value="360" />
</map>
</option>
</AndroidTestResultsTableState>
</value>
</entry>
</map>
</option>
</component>
</project>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<bytecodeTargetLevel target="17" />
</component>
</project>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="deploymentTargetDropDown">
<targetSelectedWithDropDown>
<Target>
<type value="QUICK_BOOT_TARGET" />
<deviceKey>
<Key>
<type value="VIRTUAL_DEVICE_PATH" />
<value value="$USER_HOME$/.android/avd/Pixel_C_API_33.avd" />
</Key>
</deviceKey>
</Target>
</targetSelectedWithDropDown>
<timeTargetWasSelectedWithDropDown value="2023-08-03T14:42:06.113626110Z" />
</component>
</project>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GradleMigrationSettings" migrationVersion="1" />
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="testRunner" value="GRADLE" />
<option name="distributionType" value="DEFAULT_WRAPPED" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleJvm" value="Embedded JDK" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/app" />
</set>
</option>
</GradleProjectSettings>
</option>
</component>
</project>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="KotlinJpsPluginSettings">
<option name="version" value="1.7.20" />
</component>
</project>
\ No newline at end of file
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" project-jdk-name="jbr-17" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">
<option name="id" value="Android" />
</component>
</project>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>
\ No newline at end of file
plugins { plugins {
id 'com.android.library' id 'com.android.library'
id 'org.jetbrains.kotlin.android' id 'org.jetbrains.kotlin.android'
id 'org.jetbrains.kotlin.plugin.serialization' version '1.6.10' id 'org.jetbrains.kotlin.plugin.serialization' version '1.9.21'
} }
android { android {
namespace 'com.futo.futopay' namespace 'com.futo.futopay'
compileSdk 33 compileSdk 34
defaultConfig { defaultConfig {
minSdk 26 minSdk 26
targetSdk 32 targetSdk 34
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
} }
...@@ -31,14 +31,14 @@ android { ...@@ -31,14 +31,14 @@ android {
} }
dependencies { dependencies {
implementation 'androidx.core:core-ktx:1.9.0' implementation 'androidx.core:core-ktx:1.12.0'
implementation 'androidx.appcompat:appcompat:1.4.1' implementation 'androidx.appcompat:appcompat:1.6.1'
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3"
implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.4.1" implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.2"
implementation 'androidx.recyclerview:recyclerview:1.3.0' implementation 'androidx.recyclerview:recyclerview:1.3.2'
implementation 'com.caverock:androidsvg-aar:1.4' implementation 'com.caverock:androidsvg-aar:1.4'
implementation 'com.google.code.gson:gson:2.10.1' implementation 'com.google.code.gson:gson:2.10.1'
implementation 'com.stripe:stripe-android:20.28.3' implementation 'com.stripe:stripe-android:20.35.1'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4' implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
testImplementation 'junit:junit:4.13.2' testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.5' androidTestImplementation 'androidx.test.ext:junit:1.1.5'
......
package com.futo.futopay
import junit.framework.TestCase.assertEquals
import org.junit.Test
class LicenseValidatorTests {
@Test
fun testValidate() {
val validator = LicenseValidator("MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqyDuxsRtD5gmBoLCNoZa" +
"XSRTwyUxgzcPHzLZkvomXVSQqzD+3aOKngcTKAZ83rm4GvoyMlBukxQMLShannSx" +
"k8GQGTCT7VStQKNc4lKVER5ASB6aEaypaFMIYI3rXN1xLF1LqY/j7cu5GgMsvAuU" +
"VYFBexYFF6xcC5JDBZW6Pw/KYoJm3rswFixjPMGESmZRFCjjdAkHk47BhRPFBlvz" +
"wv9Ez1stdHcTpa/odEXIeJWIsZk9DHtCNCZyt6B6FXojVzrXsF2TxCNHGcHhlX43" +
"ALgQikiRcof1FsxoewTQhjLwMiDqB02mHCdRxssdnW3xadqyK678kQKfoIB1KB2N" +
"/QIDAQAB")
assert(validator.validate("UX8Z-9D7F-8BPX-8Q9J-WD7W-2PH1-D55A-QUG5", "VjpUEu20t4yW_Qmc3_Gji8FXmBfa95OFNx15Zf7OCJFxw1Kq2XF_Rv0dg4EdWkGwVaHzulIsDz6_ndTEhTWKCPm7kh4kT2VAkf8LCEwLjp5w7sz46Lb1jltA2orDt6UH90uKlT516VmsTXlEd9kIQ72Yt3jqzyI8JIG7Q1P5zj_e-wQ8XOyAJNiRVz-Ot0Xe-3qtufyaIbpyVut-OCAPEpHVaVUNHCDM9EuintQxuSZe_zwOaoFIR1hLnTKuc-xizO_da5qhWKjfnd5Dl4xwH-1ff47s7ltUavYI4ovcIC4LpSV84_5VBAsUr84B8fwNqu-b6AWRL5N1wkollIBzAg"))
}
}
\ No newline at end of file
...@@ -6,12 +6,12 @@ class PaymentConfigurations { ...@@ -6,12 +6,12 @@ class PaymentConfigurations {
companion object { companion object {
val PAYMENT_METHODS = listOf( val PAYMENT_METHODS = listOf(
PaymentMethodDescriptor("stripe", R.drawable.stripe, "Standard Payment Methods", PaymentMethodDescriptor("stripe", R.drawable.stripe, R.string.standard_payment_methods,
"Stripe is a secure online payment service that accepts major credit cards, debit cards, and various localized payment methods."), R.string.stripe_is_a_secure_online_payment_service_that_accepts_major_credit_cards_debit_cards_and_various_localized_payment_methods),
PaymentMethodDescriptor("_", R.drawable.ic_construction, "Mobile Carrier Methods", PaymentMethodDescriptor("_", R.drawable.ic_construction, R.string.mobile_carrier_methods,
"Under Construction", true), R.string.under_construction, true),
PaymentMethodDescriptor("_", R.drawable.ic_construction, "Crypto Currency Payment", PaymentMethodDescriptor("_", R.drawable.ic_construction, R.string.crypto_currency_payment,
"Under Construction", true) R.string.under_construction, true)
); );
val COUNTRIES = listOf( val COUNTRIES = listOf(
...@@ -116,6 +116,7 @@ class PaymentConfigurations { ...@@ -116,6 +116,7 @@ class PaymentConfigurations {
CountryDescriptor("QA", "Qatar", "قطر", "qar", "qa"), CountryDescriptor("QA", "Qatar", "قطر", "qar", "qa"),
CountryDescriptor("RO", "Romania", "România", "ron", "ro"), CountryDescriptor("RO", "Romania", "România", "ron", "ro"),
CountryDescriptor("RS", "Serbia", "Србија", "rsd", "rs"), CountryDescriptor("RS", "Serbia", "Србија", "rsd", "rs"),
CountryDescriptor("RU", "Russia", "Российская Федерация", "rub", "ru"),
CountryDescriptor("RW", "Rwanda", "Rwanda", "rwf", "rw"), CountryDescriptor("RW", "Rwanda", "Rwanda", "rwf", "rw"),
CountryDescriptor("SA", "Saudi Arabia", "المملكة العربية السعودية", "sar", "sa"), CountryDescriptor("SA", "Saudi Arabia", "المملكة العربية السعودية", "sar", "sa"),
CountryDescriptor("SE", "Sweden", "Sweden", "sek", "se"), CountryDescriptor("SE", "Sweden", "Sweden", "sek", "se"),
...@@ -214,6 +215,7 @@ class PaymentConfigurations { ...@@ -214,6 +215,7 @@ class PaymentConfigurations {
CurrencyDescriptor("qar", "Qatari Rial", "ريال قطري", "ر.ق.", "qa"), //QA CurrencyDescriptor("qar", "Qatari Rial", "ريال قطري", "ر.ق.", "qa"), //QA
CurrencyDescriptor("ron", "Romanian Leu", "leu românesc", "RON", "ro"), //RO CurrencyDescriptor("ron", "Romanian Leu", "leu românesc", "RON", "ro"), //RO
CurrencyDescriptor("rsd", "Serbian Dinar", "српски динар", "RSD", "rs"), //RS CurrencyDescriptor("rsd", "Serbian Dinar", "српски динар", "RSD", "rs"), //RS
CurrencyDescriptor("rub", "Russian Rubble", "Российский рубль", "RUB", "ru"), //RU
CurrencyDescriptor("rwf", "Rwandan Franc", "Rwandan Franc", "RF", "rw"), //RW CurrencyDescriptor("rwf", "Rwandan Franc", "Rwandan Franc", "RF", "rw"), //RW
CurrencyDescriptor("sar", "Saudi Riyal", "ريال سعودي", "ر.س.", "sa"), //SA CurrencyDescriptor("sar", "Saudi Riyal", "ريال سعودي", "ر.س.", "sa"), //SA
CurrencyDescriptor("sek", "Swedish Krona", "Swedish Krona", "kr", "se"), //SE CurrencyDescriptor("sek", "Swedish Krona", "Swedish Krona", "kr", "se"), //SE
...@@ -234,8 +236,8 @@ class PaymentConfigurations { ...@@ -234,8 +236,8 @@ class PaymentConfigurations {
data class PaymentMethodDescriptor( data class PaymentMethodDescriptor(
val id: String, val id: String,
val image: Int, val image: Int,
val name: String, val name: Int,
val description: String, val description: Int,
val isDisabled: Boolean = false val isDisabled: Boolean = false
); );
data class CountryDescriptor( data class CountryDescriptor(
......
...@@ -50,7 +50,7 @@ class PaymentManager { ...@@ -50,7 +50,7 @@ class PaymentManager {
scope.launch(Dispatchers.IO){ scope.launch(Dispatchers.IO){
try{ try{
val availableCurrencies = _paymentState.getAvailableCurrencies(productId); val availableCurrencies = _paymentState.getAvailableCurrencies(productId);
val country = paymentState.getPaymentCountryFromIP()?.let { c -> PaymentConfigurations.COUNTRIES.find { it.id.equals(c, ignoreCase = true) } }; val country = paymentState.getPaymentCountryFromIP(true)?.let { c -> PaymentConfigurations.COUNTRIES.find { it.id.equals(c, ignoreCase = true) } };
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
SlideUpPayment.startPayment(paymentState, _overlayContainer, productId, country, availableCurrencies) { method, request -> SlideUpPayment.startPayment(paymentState, _overlayContainer, productId, country, availableCurrencies) { method, request ->
when(method) { when(method) {
...@@ -62,7 +62,7 @@ class PaymentManager { ...@@ -62,7 +62,7 @@ class PaymentManager {
catch(ex: Throwable) { catch(ex: Throwable) {
Log.e(TAG, "startPayment failed", ex); Log.e(TAG, "startPayment failed", ex);
scope.launch(Dispatchers.Main){ scope.launch(Dispatchers.Main){
UIDialogs.showGeneralErrorDialog(_fragment.requireContext(), "Failed to get required payment data", ex); UIDialogs.showGeneralErrorDialog(_fragment.requireContext(), _overlayContainer.context.getString(R.string.failed_to_get_required_payment_data), ex);
} }
} }
} }
...@@ -98,7 +98,8 @@ class PaymentManager { ...@@ -98,7 +98,8 @@ class PaymentManager {
catch(ex: Throwable) { catch(ex: Throwable) {
Log.e(TAG, "Payment failed: ${ex.message}", ex); Log.e(TAG, "Payment failed: ${ex.message}", ex);
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
UIDialogs.showGeneralErrorDialog(_fragment.requireContext(), "Payment failed\nIf you are charged you should always receive the key in your mail.", ex); val c = _fragment.requireContext();
UIDialogs.showGeneralErrorDialog(c, c.getString(R.string.payment_failed_if_you_are_charged_you_should_always_receive_the_key_in_your_mail), ex);
} }
} }
} }
......
...@@ -103,7 +103,7 @@ abstract class PaymentState { ...@@ -103,7 +103,7 @@ abstract class PaymentState {
if(result.body == null) if(result.body == null)
throw IllegalStateException("Could not get currencies:\nEmpty response"); throw IllegalStateException("Could not get currencies:\nEmpty response");
val listResult = _json.decodeFromString<List<String>>(result.body!!); val listResult = _json.decodeFromString<List<String>>(result.body);
synchronized(_currencyCache) { synchronized(_currencyCache) {
_currencyCache[productId] = listResult; _currencyCache[productId] = listResult;
return _currencyCache[productId]!!; return _currencyCache[productId]!!;
...@@ -121,7 +121,7 @@ abstract class PaymentState { ...@@ -121,7 +121,7 @@ abstract class PaymentState {
if(result.body == null) if(result.body == null)
throw IllegalStateException("Could not get currencies:\nEmpty response"); throw IllegalStateException("Could not get currencies:\nEmpty response");
val listResult = _json.decodeFromString<HashMap<String, Long>>(result.body!!); val listResult = _json.decodeFromString<HashMap<String, Long>>(result.body);
synchronized(_priceCache) { synchronized(_priceCache) {
_priceCache[productId] = listResult; _priceCache[productId] = listResult;
return _priceCache[productId]!!; return _priceCache[productId]!!;
...@@ -138,7 +138,7 @@ abstract class PaymentState { ...@@ -138,7 +138,7 @@ abstract class PaymentState {
throw IllegalStateException("Could not get payment breakdown [${result.code}]:\n" + result.body); throw IllegalStateException("Could not get payment breakdown [${result.code}]:\n" + result.body);
if(result.body == null) if(result.body == null)
throw IllegalStateException("Could not get payment breakdown:\nEmpty response"); throw IllegalStateException("Could not get payment breakdown:\nEmpty response");
return _json.decodeFromString(result.body!!); return _json.decodeFromString(result.body);
} }
fun getPaymentIntent(productId: String, currency: String, email: String, country: String? = null, zipcode: String? = null): PaymentIntentInfo { fun getPaymentIntent(productId: String, currency: String, email: String, country: String? = null, zipcode: String? = null): PaymentIntentInfo {
val result = httpGET(URL_PAYMENT_STRIPE_INTENT + val result = httpGET(URL_PAYMENT_STRIPE_INTENT +
...@@ -152,7 +152,7 @@ abstract class PaymentState { ...@@ -152,7 +152,7 @@ abstract class PaymentState {
throw IllegalStateException("Could not get payment intent:\n" + result.body); throw IllegalStateException("Could not get payment intent:\n" + result.body);
if(result.body == null) if(result.body == null)
throw IllegalStateException("Could not get payment intent:\nEmpty response"); throw IllegalStateException("Could not get payment intent:\nEmpty response");
return _json.decodeFromString(result.body!!); return _json.decodeFromString(result.body);
} }
fun getPaymentStatus(purchaseId: String): PaymentStatus { fun getPaymentStatus(purchaseId: String): PaymentStatus {
...@@ -161,30 +161,37 @@ abstract class PaymentState { ...@@ -161,30 +161,37 @@ abstract class PaymentState {
throw IllegalStateException("Could not get payment intent:\n" + result.body); throw IllegalStateException("Could not get payment intent:\n" + result.body);
if(result.body == null) if(result.body == null)
throw IllegalStateException("Could not get payment intent:\nEmpty response"); throw IllegalStateException("Could not get payment intent:\nEmpty response");
return _json.decodeFromString(result.body!!); return _json.decodeFromString(result.body);
} }
fun getPaymentCountryFromIP(): String? { fun getPaymentCountryFromIP(allowFail: Boolean = false): String? {
val urlString = "https://freeipapi.com/api/json" try {
val urlString = "https://freeipapi.com/api/json"
val url = URL(urlString) val url = URL(urlString)
val connection = url.openConnection() as HttpURLConnection val connection = url.openConnection() as HttpURLConnection
connection.requestMethod = "GET" connection.requestMethod = "GET"
val reader = BufferedReader(InputStreamReader(connection.inputStream)) val reader = BufferedReader(InputStreamReader(connection.inputStream))
val response = StringBuilder() val response = StringBuilder()
var line: String? var line: String?
while (reader.readLine().also { line = it } != null) { while (reader.readLine().also { line = it } != null) {
response.append(line) response.append(line)
} }
reader.close() reader.close()
val json = response.toString(); val json = response.toString();
val ipInfoObj = JsonParser.parseString(json) as JsonObject; val ipInfoObj = JsonParser.parseString(json) as JsonObject;
if(ipInfoObj.has("countryCode")) if (ipInfoObj.has("countryCode"))
return ipInfoObj.get("countryCode").asString; return ipInfoObj.get("countryCode").asString;
return null; return null;
}
catch(ex: Throwable) {
if(allowFail)
return null;
throw ex;
}
} }
private fun httpGET(urlStr: String): HttpResp { private fun httpGET(urlStr: String): HttpResp {
......
...@@ -170,7 +170,7 @@ class SlideUpPayment : RelativeLayout { ...@@ -170,7 +170,7 @@ class SlideUpPayment : RelativeLayout {
private fun requestPostalCode(paymentState: PaymentState, overlayContainer: ViewGroup) { private fun requestPostalCode(paymentState: PaymentState, overlayContainer: ViewGroup) {
transitionTo { transitionTo {
SlideUpPayment(paymentState, overlayContainer.context, overlayContainer, "Postal code", null, SlideUpPayment(paymentState, overlayContainer.context, overlayContainer, overlayContainer.context.getString(R.string.postal_code), null,
PaymentPostalCodeView(overlayContainer.context, _country?.id!!) { PaymentPostalCodeView(overlayContainer.context, _country?.id!!) {
_postalCode = it; _postalCode = it;
requestNext(paymentState, overlayContainer); requestNext(paymentState, overlayContainer);
...@@ -181,7 +181,7 @@ class SlideUpPayment : RelativeLayout { ...@@ -181,7 +181,7 @@ class SlideUpPayment : RelativeLayout {
private fun requestPaymentMethod(paymentState: PaymentState, overlayContainer: ViewGroup) { private fun requestPaymentMethod(paymentState: PaymentState, overlayContainer: ViewGroup) {
transitionTo { transitionTo {
SlideUpPayment(paymentState, overlayContainer.context, overlayContainer, "Payment using", null, SlideUpPayment(paymentState, overlayContainer.context, overlayContainer, overlayContainer.context.getString(R.string.payment_using), null,
*PaymentConfigurations.PAYMENT_METHODS.map { method -> *PaymentConfigurations.PAYMENT_METHODS.map { method ->
PaymentMethodView(overlayContainer.context, method) { PaymentMethodView(overlayContainer.context, method) {
_paymentMethod = method; _paymentMethod = method;
...@@ -194,7 +194,7 @@ class SlideUpPayment : RelativeLayout { ...@@ -194,7 +194,7 @@ class SlideUpPayment : RelativeLayout {
private fun requestCountry(paymentState: PaymentState, overlayContainer: ViewGroup) { private fun requestCountry(paymentState: PaymentState, overlayContainer: ViewGroup) {
transitionTo { transitionTo {
SlideUpPayment(paymentState, overlayContainer.context, overlayContainer, "Country of residency", null).apply { SlideUpPayment(paymentState, overlayContainer.context, overlayContainer, overlayContainer.context.getString(R.string.country_of_residency), null).apply {
setItems(PaymentConfigurations.COUNTRIES.sortedBy { i -> i.nameEnglish }, setItems(PaymentConfigurations.COUNTRIES.sortedBy { i -> i.nameEnglish },
createView = { CountryView(overlayContainer.context) }, createView = { CountryView(overlayContainer.context) },
bindView = { view, value -> bindView = { view, value ->
...@@ -230,7 +230,7 @@ class SlideUpPayment : RelativeLayout { ...@@ -230,7 +230,7 @@ class SlideUpPayment : RelativeLayout {
Collections.swap(sortedCurrencies, 4, sortedCurrencies.indexOfFirst { it.id.equals("jpy", ignoreCase = true) }); Collections.swap(sortedCurrencies, 4, sortedCurrencies.indexOfFirst { it.id.equals("jpy", ignoreCase = true) });
transitionTo { transitionTo {
SlideUpPayment(paymentState, overlayContainer.context, overlayContainer, "Currency of payment", null).apply { SlideUpPayment(paymentState, overlayContainer.context, overlayContainer, overlayContainer.context.getString(R.string.currency_of_payment), null).apply {
setItems(sortedCurrencies.filter { _currencies?.contains(it.id) ?: true }, setItems(sortedCurrencies.filter { _currencies?.contains(it.id) ?: true },
createView = { CurrencyView(overlayContainer.context) }, createView = { CurrencyView(overlayContainer.context) },
bindView = { view, value -> bindView = { view, value ->
...@@ -267,7 +267,7 @@ class SlideUpPayment : RelativeLayout { ...@@ -267,7 +267,7 @@ class SlideUpPayment : RelativeLayout {
); );
transitionTo { transitionTo {
SlideUpPayment(paymentState, overlayContainer.context, overlayContainer, "Checkout", null, paymentCheckoutView) SlideUpPayment(paymentState, overlayContainer.context, overlayContainer, overlayContainer.context.getString(R.string.checkout), null, paymentCheckoutView)
} }
CoroutineScope(Dispatchers.IO).launch { CoroutineScope(Dispatchers.IO).launch {
...@@ -280,7 +280,7 @@ class SlideUpPayment : RelativeLayout { ...@@ -280,7 +280,7 @@ class SlideUpPayment : RelativeLayout {
catch (ex: Throwable) { catch (ex: Throwable) {
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
Log.e("SlideUpPayment", "Failed to obtain price breakdown", ex) Log.e("SlideUpPayment", "Failed to obtain price breakdown", ex)
paymentCheckoutView.setError("Failed to obtain price breakdown\n(${ex.message})\n Try again later") paymentCheckoutView.setError(overlayContainer.context.getString(R.string.failed_to_obtain_price_breakdown_exceptionmessage_try_again_later).replace("{exceptionMessage}", ex.message ?: ""))
} }
} }
} }
......
...@@ -53,7 +53,6 @@ class UIDialogs { ...@@ -53,7 +53,6 @@ class UIDialogs {
val buttonView = TextView(context); val buttonView = TextView(context);
val dp10 = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 10f, resources.displayMetrics).toInt(); val dp10 = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 10f, resources.displayMetrics).toInt();
val dp28 = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 28f, resources.displayMetrics).toInt(); val dp28 = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 28f, resources.displayMetrics).toInt();
val dp14 = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 14.0f, resources.displayMetrics);
buttonView.layoutParams = LinearLayout.LayoutParams( buttonView.layoutParams = LinearLayout.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT ViewGroup.LayoutParams.WRAP_CONTENT
......
...@@ -3,8 +3,12 @@ package com.futo.futopay ...@@ -3,8 +3,12 @@ package com.futo.futopay
import android.content.Context import android.content.Context
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.graphics.drawable.PictureDrawable import android.graphics.drawable.PictureDrawable
import android.icu.text.NumberFormat
import android.icu.util.Currency
import androidx.core.graphics.drawable.toBitmap import androidx.core.graphics.drawable.toBitmap
import com.caverock.androidsvg.SVG import com.caverock.androidsvg.SVG
import java.util.Locale
import kotlin.math.pow
private val _assetFlags = HashMap<String, Drawable>(); private val _assetFlags = HashMap<String, Drawable>();
...@@ -38,6 +42,28 @@ fun initFlags(context: Context) ...@@ -38,6 +42,28 @@ fun initFlags(context: Context)
} }
} }
fun formatMoney(countryCode: String, currency: String, amount: Long): String {
fun getStripeDecimalPlaces(c: String): Int {
return when (c.uppercase()) {
"ISK", "HUF", "TWD", "UGX" -> 2
"BIF", "CLP", "DJF", "GNF", "JPY", "KMF", "KRW", "MGA", "PYG", "RWF", "VND", "VUV", "XAF", "XOF", "XPF" -> 0
"BHD", "JOD", "KWD", "OMR", "TND" -> 3
else -> 2 // default case for all other currencies
}
}
val decimalPlaces = getStripeDecimalPlaces(currency)
val adjustedAmount = amount / 10.0.pow(decimalPlaces.toDouble())
val locale = Locale(countryCode, currency)
val currencyInstance = Currency.getInstance(currency.uppercase())
val numberFormat = NumberFormat.getCurrencyInstance(locale).apply {
minimumFractionDigits = decimalPlaces
maximumFractionDigits = decimalPlaces
this.currency = currencyInstance
}
return numberFormat.format(adjustedAmount)
}
fun getCountryDrawable(context: Context, name: String): Drawable? { fun getCountryDrawable(context: Context, name: String): Drawable? {
val code = name.lowercase(); val code = name.lowercase();
initFlags(context); initFlags(context);
......
...@@ -3,6 +3,8 @@ package com.futo.futopay.views ...@@ -3,6 +3,8 @@ package com.futo.futopay.views
import android.content.Context import android.content.Context
import android.graphics.Color import android.graphics.Color
import android.graphics.drawable.Animatable import android.graphics.drawable.Animatable
import android.icu.text.NumberFormat
import android.icu.util.Currency
import android.text.Editable import android.text.Editable
import android.text.TextWatcher import android.text.TextWatcher
import android.view.View import android.view.View
...@@ -15,6 +17,9 @@ import androidx.constraintlayout.widget.ConstraintLayout ...@@ -15,6 +17,9 @@ import androidx.constraintlayout.widget.ConstraintLayout
import com.futo.futopay.PaymentBreakdown import com.futo.futopay.PaymentBreakdown
import com.futo.futopay.PaymentConfigurations import com.futo.futopay.PaymentConfigurations
import com.futo.futopay.R import com.futo.futopay.R
import com.futo.futopay.formatMoney
import java.util.Locale
import kotlin.math.pow
class PaymentCheckoutView : ConstraintLayout { class PaymentCheckoutView : ConstraintLayout {
private var _isValid = false; private var _isValid = false;
...@@ -30,6 +35,7 @@ class PaymentCheckoutView : ConstraintLayout { ...@@ -30,6 +35,7 @@ class PaymentCheckoutView : ConstraintLayout {
private val textTotal: TextView private val textTotal: TextView
private val textCountry: TextView private val textCountry: TextView
private val editEmail: EditText private val editEmail: EditText
private val editEmailConfirm: EditText
private val layoutBreakdown: LinearLayout private val layoutBreakdown: LinearLayout
private val layoutLoader: FrameLayout private val layoutLoader: FrameLayout
private val imageLoader: ImageView private val imageLoader: ImageView
...@@ -37,6 +43,7 @@ class PaymentCheckoutView : ConstraintLayout { ...@@ -37,6 +43,7 @@ class PaymentCheckoutView : ConstraintLayout {
private val textEmailSubtext: TextView private val textEmailSubtext: TextView
private val textPostalCodeHeader: TextView private val textPostalCodeHeader: TextView
private val buttonChangePostalCode: FrameLayout private val buttonChangePostalCode: FrameLayout
private val country: PaymentConfigurations.CountryDescriptor
constructor(context: Context, method: String, currency: PaymentConfigurations.CurrencyDescriptor, country: PaymentConfigurations.CountryDescriptor, postalCode: String?, onBuy: (String)->Unit, onChangeCountry: ()->Unit, onChangeCurrency: ()->Unit, onChangePostalCode: ()->Unit): super(context) { constructor(context: Context, method: String, currency: PaymentConfigurations.CurrencyDescriptor, country: PaymentConfigurations.CountryDescriptor, postalCode: String?, onBuy: (String)->Unit, onChangeCountry: ()->Unit, onChangeCurrency: ()->Unit, onChangePostalCode: ()->Unit): super(context) {
inflate(context, R.layout.payment_checkout, this); inflate(context, R.layout.payment_checkout, this);
...@@ -54,11 +61,13 @@ class PaymentCheckoutView : ConstraintLayout { ...@@ -54,11 +61,13 @@ class PaymentCheckoutView : ConstraintLayout {
textTotal = findViewById(R.id.text_total) textTotal = findViewById(R.id.text_total)
textCountry = findViewById(R.id.text_country) textCountry = findViewById(R.id.text_country)
editEmail = findViewById(R.id.edit_email) editEmail = findViewById(R.id.edit_email)
editEmailConfirm = findViewById(R.id.edit_email_confirm)
layoutBreakdown = findViewById(R.id.layout_breakdown) layoutBreakdown = findViewById(R.id.layout_breakdown)
layoutLoader = findViewById(R.id.layout_loader) layoutLoader = findViewById(R.id.layout_loader)
textError = findViewById(R.id.text_error) textError = findViewById(R.id.text_error)
imageLoader = findViewById(R.id.image_loader) imageLoader = findViewById(R.id.image_loader)
textEmailSubtext = findViewById(R.id.text_email_subtext) textEmailSubtext = findViewById(R.id.text_email_subtext)
this.country = country
//findViewById<ImageView>(R.id.image_method).setImageResource(paymentMethod.image); //findViewById<ImageView>(R.id.image_method).setImageResource(paymentMethod.image);
textMethod.text = method.replaceFirstChar { it.uppercase() } textMethod.text = method.replaceFirstChar { it.uppercase() }
...@@ -77,22 +86,18 @@ class PaymentCheckoutView : ConstraintLayout { ...@@ -77,22 +86,18 @@ class PaymentCheckoutView : ConstraintLayout {
onChangePostalCode(); onChangePostalCode();
}; };
val emailRegex = """(?:[a-z0-9!#${'$'}%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#${'$'}%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])""".toRegex()
editEmail.addTextChangedListener(object : TextWatcher { editEmail.addTextChangedListener(object : TextWatcher {
override fun afterTextChanged(s: Editable?) = Unit override fun afterTextChanged(s: Editable?) = Unit
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) = Unit override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) = Unit
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
val text = editEmail.text.toString(); validateInput()
_isValid = emailRegex.matches(text); }
if (_isValid) { });
textEmailSubtext.setTextColor(Color.rgb(0x99, 0x99, 0x99)); editEmailConfirm.addTextChangedListener(object : TextWatcher {
textEmailSubtext.setText("Required to accurately calculate the applicable sales tax"); override fun afterTextChanged(s: Editable?) = Unit
buttonPay.alpha = 1.0f; override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) = Unit
} else { override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
textEmailSubtext.setTextColor(Color.rgb(0xFF, 0x00, 0x00)); validateInput()
textEmailSubtext.setText("Email is invalid");
buttonPay.alpha = 0.4f;
}
} }
}); });
...@@ -109,6 +114,32 @@ class PaymentCheckoutView : ConstraintLayout { ...@@ -109,6 +114,32 @@ class PaymentCheckoutView : ConstraintLayout {
setPaymentBreakdown(null); setPaymentBreakdown(null);
} }
private fun validateInput() {
val emailRegex = """(?:[a-zA-Z0-9!#${'$'}%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#${'$'}%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])""".toRegex()
val text = editEmail.text.toString()
if (!emailRegex.matches(text)) {
_isValid = false
textEmailSubtext.setTextColor(Color.rgb(0xFF, 0x00, 0x00))
textEmailSubtext.text = context.getString(R.string.email_is_invalid)
buttonPay.alpha = 0.4f;
return
}
if (editEmail.text.toString() != editEmailConfirm.text.toString()) {
_isValid = false
textEmailSubtext.setTextColor(Color.rgb(0xFF, 0x00, 0x00))
textEmailSubtext.text = context.getString(R.string.email_must_match)
buttonPay.alpha = 0.4f;
return
}
textEmailSubtext.setTextColor(Color.rgb(0x99, 0x99, 0x99));
textEmailSubtext.text = context.getString(R.string.required_to_accurately_calculate_the_applicable_sales_tax);
buttonPay.alpha = 1.0f;
_isValid = true
}
private fun setPostalCode(countryId: String, postalCode: String? = null) { private fun setPostalCode(countryId: String, postalCode: String? = null) {
if (countryId.equals("us", ignoreCase = true) || countryId.equals("ca", ignoreCase = true)) { if (countryId.equals("us", ignoreCase = true) || countryId.equals("ca", ignoreCase = true)) {
textPostalCode.visibility = View.VISIBLE textPostalCode.visibility = View.VISIBLE
...@@ -119,7 +150,7 @@ class PaymentCheckoutView : ConstraintLayout { ...@@ -119,7 +150,7 @@ class PaymentCheckoutView : ConstraintLayout {
textPostalCode.text = postalCode.uppercase(); textPostalCode.text = postalCode.uppercase();
textPostalCode.setTextColor(Color.rgb(0xFF, 0xFF, 0xFF)) textPostalCode.setTextColor(Color.rgb(0xFF, 0xFF, 0xFF))
} else { } else {
textPostalCode.text ="Missing"; textPostalCode.text =context.getString(R.string.missing);
textPostalCode.setTextColor(Color.rgb(0xFF, 0, 0)) textPostalCode.setTextColor(Color.rgb(0xFF, 0, 0))
} }
} else { } else {
...@@ -138,19 +169,16 @@ class PaymentCheckoutView : ConstraintLayout { ...@@ -138,19 +169,16 @@ class PaymentCheckoutView : ConstraintLayout {
return; return;
} }
val currency = PaymentConfigurations.CURRENCIES.find { it.id == paymentBreakdown.currency };
val symbol = currency?.symbol ?: "";
(imageLoader.drawable as Animatable?)?.stop() (imageLoader.drawable as Animatable?)?.stop()
layoutLoader.visibility = View.GONE layoutLoader.visibility = View.GONE
textError.visibility = View.GONE textError.visibility = View.GONE
layoutBreakdown.visibility = View.VISIBLE layoutBreakdown.visibility = View.VISIBLE
textProduct.text = paymentBreakdown.productName; textProduct.text = paymentBreakdown.productName;
textProductPrice.text = symbol + "%.2f".format((paymentBreakdown.productPrice).toDouble()/100); textProductPrice.text = formatMoney(country.id, paymentBreakdown.currency, paymentBreakdown.productPrice)
textTaxPercentage.text = "%.2f".format(paymentBreakdown.taxPercentage); textTaxPercentage.text = "%.2f".format(paymentBreakdown.taxPercentage);
textTax.text = symbol + "%.2f".format((paymentBreakdown.taxPrice).toDouble()/100); textTax.text = formatMoney(country.id, paymentBreakdown.currency, paymentBreakdown.taxPrice)
textTotal.text = symbol + "%.2f".format((paymentBreakdown.totalPrice).toDouble()/100); textTotal.text = formatMoney(country.id, paymentBreakdown.currency, paymentBreakdown.totalPrice)
textCurrency.text = paymentBreakdown.currency.uppercase(); textCurrency.text = paymentBreakdown.currency.uppercase()
} }
fun setError(error: String) { fun setError(error: String) {
......
...@@ -29,8 +29,8 @@ class PaymentMethodView: ConstraintLayout { ...@@ -29,8 +29,8 @@ class PaymentMethodView: ConstraintLayout {
fun bind(paymentMethod: PaymentConfigurations.PaymentMethodDescriptor, onClick: (String)->Unit) { fun bind(paymentMethod: PaymentConfigurations.PaymentMethodDescriptor, onClick: (String)->Unit) {
_image.setImageResource(paymentMethod.image); _image.setImageResource(paymentMethod.image);
_name.text = paymentMethod.name; _name.text = context.getString(paymentMethod.name);
_description.text = paymentMethod.description; _description.text = context.getString(paymentMethod.description);
if(!paymentMethod.isDisabled) { if(!paymentMethod.isDisabled) {
setOnClickListener { setOnClickListener {
......
...@@ -34,20 +34,20 @@ class PaymentPostalCodeView : ConstraintLayout { ...@@ -34,20 +34,20 @@ class PaymentPostalCodeView : ConstraintLayout {
_isValid = regex?.matches(text) ?: true; _isValid = regex?.matches(text) ?: true;
if (_isValid) { if (_isValid) {
textSubtext.setTextColor(Color.rgb(0x99, 0x99, 0x99)); textSubtext.setTextColor(Color.rgb(0x99, 0x99, 0x99));
textSubtext.setText("Required to accurately calculate the applicable sales tax"); textSubtext.text = context.getString(R.string.required_to_accurately_calculate_the_applicable_sales_tax);
buttonSubmit.alpha = 1.0f; buttonSubmit.alpha = 1.0f;
} else { } else {
textSubtext.setTextColor(Color.rgb(0xFF, 0x00, 0x00)); textSubtext.setTextColor(Color.rgb(0xFF, 0x00, 0x00));
textSubtext.setText("Value is invalid for ${countryId.uppercase()}"); textSubtext.text = context.getString(R.string.value_is_invalid_for) + countryId.uppercase();
buttonSubmit.alpha = 0.4f; buttonSubmit.alpha = 0.4f;
} }
} }
}); });
editPostalCode.hint = when (countryId) { editPostalCode.hint = when (countryId) {
"us" -> "Enter ZIP code (e.g., 12345 or 12345-6789)" "us" -> context.getString(R.string.enter_zip_code_e_g_12345_or_12345_6789)
"ca" -> "Enter Postal code (e.g., A1A 1A1)" "ca" -> context.getString(R.string.enter_postal_code_e_g_a1a_1a1)
else -> "Enter Postal code" else -> context.getString(R.string.enter_postal_code)
} }
if (regex != null) { if (regex != null) {
......