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 (19)
Showing
with 163 additions and 589 deletions
# FUTO TEMPORARY LICENSE
This license grants you the rights, and only the rights, set out below in respect of the source code provided. If you take advantage of these rights, you accept this license. If you do not accept the license, do not access the code.
Words used in the Terms of Service have the same meaning in this license. Where there is any inconsistency between this license and those Terms of Service, these terms prevail.
## Section 1: Definitions
- "code" means the source code made available from time, in our sole discretion, for access under this license. Reference to code in this license means the code and any part of it and any derivative of it.
- “compilation” means to compile the code from ‘source code’ to ‘machine code’.
- "defect" means a defect, bug, backdoor, security issue or other deficiency in the code.
- “non-commercial distribution” means distribution of the code or any compilation of the code, or of any other application or program containing the code or any compilation of the code, where such distribution is not intended for or directed towards commercial advantage or monetary compensation.
- "review" means to access, analyse, test and otherwise review the code as a reference, for the sole purpose of analysing it for defects.
- "you" means the licensee of rights set out in this license.
## Section 2: Grant of Rights
1. Subject to the terms of this license, we grant you a non-transferable, non-exclusive, worldwide, royalty-free license to access and use the code solely for the purposes of review, compilation and non-commercial distribution.
2. You may provide the code to anyone else and publish excerpts of it for the purposes of review, compilation and non-commercial distribution, provided that when you do so you make any recipient of the code aware of the terms of this license, they must agree to be bound by the terms of this license and you must attribute the code to the provider.
3. Other than in respect of those parts of the code that were developed by other parties and as specified strictly in accordance with the open source and other licenses under which those parts of the code have been made available, as set out on our website or in those items of code, you are not entitled to use or do anything with the code for any commercial or other purpose, other than review, compilation and non-commercial distribution in accordance with the terms of this license.
4. Subject to the terms of this license, you must at all times comply with and shall be bound by our Terms of Use, Privacy and Data Policy.
## Section 3: Limitations
1. This license does not grant you any rights to use the provider's name, logo, or trademarks and you must not in any way indicate you are authorised to speak on behalf of the provider.
2. If you issue proceedings in any jurisdiction against the provider because you consider the provider has infringed copyright or any patent right in respect of the code (including any joinder or counterclaim), your license to the code is automatically terminated.
3. THE CODE IS MADE AVAILABLE "AS-IS" AND WITHOUT ANY EXPRESS OR IMPLIED GUARANTEES AS TO FITNESS, MERCHANTABILITY, NON-INFRINGEMENT OR OTHERWISE. IT IS NOT BEING PROVIDED IN TRADE BUT ON A VOLUNTARY BASIS ON OUR PART AND IS NOT MADE AVAILABLE FOR ANY USE OUTSIDE THE TERMS OF THIS LICENSE. ANYONE ACCESSING THE CODE MUST ENSURE THEY HAVE THE REQUISITE EXPERTISE TO SECURE THEIR OWN SYSTEM AND DEVICES AND TO ACCESS AND USE THE CODE IN ACCORDANCE WITH THE TERMS OF THIS LICENSE. YOU BEAR THE RISK OF ACCESSING AND USING THE CODE. IN PARTICULAR, THE PROVIDER BEARS NO LIABILITY FOR ANY INTERFERENCE WITH OR ADVERSE EFFECT ON YOUR SYSTEM OR DEVICES AS A RESULT OF YOUR ACCESSING AND USING THE CODE IN ACCORDANCE WITH THE TERMS OF THIS LICENSE OR OTHERWISE.
## Section 4: Termination, suspension and variation
1. We may suspend, terminate or vary the terms of this license and any access to the code at any time, without notice, for any reason or no reason, in respect of any licensee, group of licensees or all licensees including as may be applicable any sub-licensees.
## Section 5: General
1. This license and its interpretation and operation are governed solely by the local law. You agree to submit to the exclusive jurisdiction of the local arbitral tribunals as further described in our Terms of Service and you agree not to raise any jurisdictional issue if we need to enforce an arbitral award or judgment in our jurisdiction or another country.
2. Questions and comments regarding this license are welcomed and should be addressed at https://chat.futo.org/login/.
Last updated 7 June 2023.
# 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(
......
...@@ -8,4 +8,5 @@ class PaymentIntentInfo( ...@@ -8,4 +8,5 @@ class PaymentIntentInfo(
val publishableKey: String, val publishableKey: String,
val customer: String? = null, val customer: String? = null,
val ephemeralKey: String? = null, val ephemeralKey: String? = null,
val purchaseId: String? = null
); );
\ No newline at end of file
...@@ -23,7 +23,9 @@ class PaymentManager { ...@@ -23,7 +23,9 @@ class PaymentManager {
private val _sheet: PaymentSheet; private val _sheet: PaymentSheet;
private val _paymentState: PaymentState; private val _paymentState: PaymentState;
constructor(paymentState: PaymentState, fragment: Fragment, overlayContainer: ViewGroup, onCompleted: (success: Boolean, exception: Throwable?)->Unit) { private var _lastPurchaseId: String? = null;
constructor(paymentState: PaymentState, fragment: Fragment, overlayContainer: ViewGroup, onCompleted: (success: Boolean, purchaseId: String?, exception: Throwable?)->Unit) {
_fragment = fragment; _fragment = fragment;
_paymentState = paymentState; _paymentState = paymentState;
_overlayContainer = overlayContainer; _overlayContainer = overlayContainer;
...@@ -34,10 +36,10 @@ class PaymentManager { ...@@ -34,10 +36,10 @@ class PaymentManager {
} }
is PaymentSheetResult.Failed -> { is PaymentSheetResult.Failed -> {
onCompleted(false, paymentSheetResult.error) onCompleted(false, null, paymentSheetResult.error)
} }
is PaymentSheetResult.Completed -> { is PaymentSheetResult.Completed -> {
onCompleted(true, null); onCompleted(true, _lastPurchaseId,null);
} }
} }
}; };
...@@ -48,7 +50,7 @@ class PaymentManager { ...@@ -48,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) {
...@@ -60,7 +62,7 @@ class PaymentManager { ...@@ -60,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);
} }
} }
} }
...@@ -75,6 +77,7 @@ class PaymentManager { ...@@ -75,6 +77,7 @@ class PaymentManager {
PaymentSheet.CustomerConfiguration(paymentIntentResult.customer, paymentIntentResult.ephemeralKey); PaymentSheet.CustomerConfiguration(paymentIntentResult.customer, paymentIntentResult.ephemeralKey);
else null; else null;
_lastPurchaseId = paymentIntentResult.purchaseId;
PaymentConfiguration.init(_fragment.requireContext(), paymentIntentResult.publishableKey); PaymentConfiguration.init(_fragment.requireContext(), paymentIntentResult.publishableKey);
...@@ -95,7 +98,8 @@ class PaymentManager { ...@@ -95,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);
} }
} }
} }
......
...@@ -12,8 +12,8 @@ import java.net.URL ...@@ -12,8 +12,8 @@ import java.net.URL
abstract class PaymentState { abstract class PaymentState {
val REGEX_KEY_FORMAT = Regex("[a-zA-Z0-9-]{4}-[a-zA-Z0-9-]{4}-[a-zA-Z0-9-]{4}-[a-zA-Z0-9-]{4}-[a-zA-Z0-9-]{4}-[a-zA-Z0-9-]{4}-[a-zA-Z0-9-]{4}-[a-zA-Z0-9-]{4}"); val REGEX_KEY_FORMAT = Regex("[a-zA-Z0-9-]{4}-[a-zA-Z0-9-]{4}-[a-zA-Z0-9-]{4}-[a-zA-Z0-9-]{4}-[a-zA-Z0-9-]{4}-[a-zA-Z0-9-]{4}-[a-zA-Z0-9-]{4}-[a-zA-Z0-9-]{4}");
private val URL_BASE = "https://payment.grayjay.app"; private val URL_BASE = if(!isTesting) "https://payment.grayjay.app" else "https://futopay-test.azurewebsites.net";
private val URL_STATIC_BASE = "https://spayment.grayjay.app"; private val URL_STATIC_BASE = if(!isTesting) "https://spayment.grayjay.app" else "https://futopay-test.azurewebsites.net";
private val URL_PAYMENT_STRIPE_INTENT = "${URL_BASE}/api/v1/stripe/paymentintent/payment"; private val URL_PAYMENT_STRIPE_INTENT = "${URL_BASE}/api/v1/stripe/paymentintent/payment";
private val URL_PAYMENT_BREAKDOWN = "${URL_BASE}/api/v1/payment/breakdown"; private val URL_PAYMENT_BREAKDOWN = "${URL_BASE}/api/v1/payment/breakdown";
private val URL_TIP_INTENT = "${URL_BASE}/api/v1/stripe/paymentintent/tip?amount="; private val URL_TIP_INTENT = "${URL_BASE}/api/v1/stripe/paymentintent/tip?amount=";
...@@ -21,6 +21,7 @@ abstract class PaymentState { ...@@ -21,6 +21,7 @@ abstract class PaymentState {
private val URL_CURRENCIES = "${URL_STATIC_BASE}/api/v1/payment/currencies"; private val URL_CURRENCIES = "${URL_STATIC_BASE}/api/v1/payment/currencies";
private val URL_PRICES = "${URL_STATIC_BASE}/api/v1/payment/prices"; private val URL_PRICES = "${URL_STATIC_BASE}/api/v1/payment/prices";
private val URL_ACTIVATION_URL = "${URL_BASE}/api/v1/activate/"; private val URL_ACTIVATION_URL = "${URL_BASE}/api/v1/activate/";
private val URL_PAYMENT_STATUS = "${URL_BASE}/api/v1/payment/status/";
private val _currencyCache = HashMap<String, List<String>>(); private val _currencyCache = HashMap<String, List<String>>();
private val _priceCache = HashMap<String, HashMap<String, Long>>(); private val _priceCache = HashMap<String, HashMap<String, Long>>();
...@@ -29,6 +30,8 @@ abstract class PaymentState { ...@@ -29,6 +30,8 @@ abstract class PaymentState {
var hasPaid: Boolean = false; var hasPaid: Boolean = false;
var hasPaidChanged = Event1<Boolean>(); var hasPaidChanged = Event1<Boolean>();
protected open val isTesting get() = false;
constructor(validationPublicKey: String) { constructor(validationPublicKey: String) {
_validator = LicenseValidator(validationPublicKey) _validator = LicenseValidator(validationPublicKey)
} }
...@@ -100,7 +103,7 @@ abstract class PaymentState { ...@@ -100,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]!!;
...@@ -118,7 +121,7 @@ abstract class PaymentState { ...@@ -118,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]!!;
...@@ -135,7 +138,7 @@ abstract class PaymentState { ...@@ -135,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 +
...@@ -149,32 +152,47 @@ abstract class PaymentState { ...@@ -149,32 +152,47 @@ 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 {
val result = httpGET(URL_PAYMENT_STATUS + purchaseId);
if(!result.isSuccessful)
throw IllegalStateException("Could not get payment intent:\n" + result.body);
if(result.body == null)
throw IllegalStateException("Could not get payment intent:\nEmpty response");
return _json.decodeFromString(result.body);
}
fun getPaymentCountryFromIP(): String? {
val urlString = "https://freeipapi.com/api/json"
val url = URL(urlString) fun getPaymentCountryFromIP(allowFail: Boolean = false): String? {
val connection = url.openConnection() as HttpURLConnection try {
connection.requestMethod = "GET" val urlString = "https://freeipapi.com/api/json"
val reader = BufferedReader(InputStreamReader(connection.inputStream)) val url = URL(urlString)
val response = StringBuilder() val connection = url.openConnection() as HttpURLConnection
var line: String? connection.requestMethod = "GET"
while (reader.readLine().also { line = it } != null) {
response.append(line)
}
reader.close()
val json = response.toString();
val ipInfoObj = JsonParser.parseString(json) as JsonObject; val reader = BufferedReader(InputStreamReader(connection.inputStream))
if(ipInfoObj.has("countryCode")) val response = StringBuilder()
return ipInfoObj.get("countryCode").asString; var line: String?
return null; while (reader.readLine().also { line = it } != null) {
} response.append(line)
}
reader.close()
val json = response.toString();
val ipInfoObj = JsonParser.parseString(json) as JsonObject;
if (ipInfoObj.has("countryCode"))
return ipInfoObj.get("countryCode").asString;
return null;
}
catch(ex: Throwable) {
if(allowFail)
return null;
throw ex;
}
}
private fun httpGET(urlStr: String): HttpResp { private fun httpGET(urlStr: String): HttpResp {
val url = URL(urlStr); val url = URL(urlStr);
......
package com.futo.futopay
import kotlinx.serialization.Serializable
@Serializable
class PaymentStatus(
val status: Int,
val purchaseId: String? = null
);
\ No newline at end of file
...@@ -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);
......