diff --git a/.gitignore b/.gitignore index ea83a5e..dcdc776 100644 --- a/.gitignore +++ b/.gitignore @@ -1,44 +1,83 @@ -# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm -# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 +# Built application files +*.apk +*.ap_ +*.aab -# User-specific stuff: +# Files for the ART/Dalvik VM +*.dex + +# Java class files +*.class + +# Generated files +bin/ +gen/ +out/ +# Uncomment the following line in case you need and you don't have the release build type files in your app +# release/ + +# Gradle files +.gradle/ +build/ + +# Local configuration file (sdk path, etc) +local.properties + +# Proguard folder generated by Eclipse +proguard/ + +# Log Files +*.log + +# Android Studio Navigation editor temp files +.navigation/ + +# Android Studio captures folder +captures/ + +# IntelliJ +*.iml .idea/workspace.xml .idea/tasks.xml -.idea/dictionaries -.idea/vcs.xml -.idea/jsLibraryMappings.xml - -# Sensitive or high-churn files: -.idea/dataSources.ids -.idea/dataSources.xml -.idea/dataSources.local.xml -.idea/sqlDataSources.xml -.idea/dynamic.xml -.idea/uiDesigner.xml - -# Gradle: .idea/gradle.xml +.idea/assetWizardSettings.xml +.idea/dictionaries .idea/libraries +# Android Studio 3 in .gitignore file. +.idea/caches +.idea/modules.xml +# Comment next line if keeping position of elements in Navigation Editor is relevant for you +.idea/navEditor.xml -# Mongo Explorer plugin: -.idea/mongoSettings.xml +# Keystore files +# Uncomment the following lines if you do not want to check your keystore files in. +#*.jks +#*.keystore -## File-based project format: -*.iws +# External native build folder generated in Android Studio 2.2 and later +.externalNativeBuild -## Plugin-specific files: +# Google Services (e.g. APIs or Firebase) +# google-services.json -# IntelliJ -/out/ +# Freeline +freeline.py +freeline/ +freeline_project_description.json -# mpeltonen/sbt-idea plugin -.idea_modules/ +# fastlane +fastlane/report.xml +fastlane/Preview.html +fastlane/screenshots +fastlane/test_output +fastlane/readme.md -# JIRA plugin -atlassian-ide-plugin.xml +# Version control +vcs.xml -# Crashlytics plugin (for Android Studio and IntelliJ) -com_crashlytics_export_strings.xml -crashlytics.properties -crashlytics-build.properties -fabric.properties +# lint +lint/intermediates/ +lint/generated/ +lint/outputs/ +lint/tmp/ +# lint/reports/ diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml new file mode 100644 index 0000000..681f41a --- /dev/null +++ b/.idea/codeStyles/Project.xml @@ -0,0 +1,116 @@ + + + + + + + +
+ + + + xmlns:android + + ^$ + + + +
+
+ + + + xmlns:.* + + ^$ + + + BY_NAME + +
+
+ + + + .*:id + + http://schemas.android.com/apk/res/android + + + +
+
+ + + + .*:name + + http://schemas.android.com/apk/res/android + + + +
+
+ + + + name + + ^$ + + + +
+
+ + + + style + + ^$ + + + +
+
+ + + + .* + + ^$ + + + BY_NAME + +
+
+ + + + .* + + http://schemas.android.com/apk/res/android + + + ANDROID_ATTRIBUTE_ORDER + +
+
+ + + + .* + + .* + + + BY_NAME + +
+
+
+
+
+
\ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml index 5d19981..f63a89a 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,43 +1,45 @@ - - - - - - - - - - - - - - + diff --git a/README.md b/README.md index 52749b2..7a94f5e 100644 --- a/README.md +++ b/README.md @@ -14,24 +14,20 @@ Una vez que haya recopilado la información de un cliente, tendrá que intercamb ## Creación de Token desde un formulario personalizado -Puede crear tokens utilizando el método utilizando el método de instancia Culqi createToken +Puede crear tokens utilizando el método utilizando el método getToken() del API enviando un objeto Card Pasando el número de la tarjeta, cvv, la fecha de vencimiento y un correo -```java -Card card = new Card(“411111111111111”, “123”, 9, 2020, “wm@wm.com”); - -Token token = new Token("{CODIGO COMERCIO}"); - -token.createToken(getApplicationContext(), card, new TokenCallback() { - @Override - public void onSuccess(JSONObject token) { - // get Token - token.get("id").toString() - } - @Override - public void onError(Exception error) { - } -}); +```Kotlin +mTokenRestDataStore.getToken(Card( + card_number = etMainCardNumber.text.toString(), + cvv = etMainCVV.text.toString(), + expiration_month = "09", + expiration_year = 2020, + email = etMainEmail.text.toString() + ) + +//El CODIGO DEL COMERCIO se ingresa en build.gradle dentro de la carpeta app (Se reemplaza el por el CODIGO DEL COMERCIO +//buildConfigField('String', 'AUTHORIZATION', '"Bearer "') ``` ## Usando Tokens diff --git a/app/app.iml b/app/app.iml deleted file mode 100644 index 3bdf759..0000000 --- a/app/app.iml +++ /dev/null @@ -1,130 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index a7f4ed3..9391055 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,15 +1,27 @@ apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-android-extensions' +apply plugin: 'kotlin-kapt' android { - compileSdkVersion 25 - buildToolsVersion "25.0.2" + compileSdkVersion 29 defaultConfig { applicationId "com.android.culqi.culqi_android" - minSdkVersion 15 - targetSdkVersion 25 + minSdkVersion 16 + targetSdkVersion 29 versionCode 1 versionName "1.0" - testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + multiDexEnabled true + + + buildConfigField('String', 'URL_SERVER', '"https://secure.culqi.com/v2/"') + buildConfigField('String', 'AUTHORIZATION', '"Bearer "') + buildConfigField('String', 'CONTENT_TYPE', '"application/json; charset=utf-8"') + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 } buildTypes { release { @@ -20,11 +32,33 @@ android { } dependencies { - compile fileTree(dir: 'libs', include: ['*.jar']) - androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { - exclude group: 'com.android.support', module: 'support-annotations' - }) - compile 'com.android.support:appcompat-v7:25.1.0' - compile 'com.android.volley:volley:1.0.0' - testCompile 'junit:junit:4.12' + implementation fileTree(include: ['*.jar'], dir: 'libs') + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" + + //AndroidX + implementation 'androidx.appcompat:appcompat:1.1.0' + implementation 'androidx.constraintlayout:constraintlayout:1.1.3' + + //Retrofit + implementation 'com.squareup.retrofit2:retrofit:2.6.1' + implementation 'com.squareup.retrofit2:converter-moshi:2.6.1' + implementation 'com.squareup.retrofit2:adapter-rxjava2:2.6.1' + implementation 'com.squareup.retrofit2:converter-gson:2.4.0' + implementation "com.squareup.okhttp3:logging-interceptor:4.2.0" + + //Moshi + kapt "com.squareup.moshi:moshi-kotlin-codegen:1.8.0" + + //Rx + implementation 'io.reactivex.rxjava2:rxjava:2.2.10' + implementation 'io.reactivex.rxjava2:rxkotlin:2.4.0' + implementation 'io.reactivex.rxjava2:rxandroid:2.1.1' + + //Koin + implementation 'org.koin:koin-android:2.0.1' + + //Test + testImplementation 'junit:junit:4.12' + androidTestImplementation 'androidx.test:runner:1.2.0' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index cfe6302..184d4e5 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,5 +1,6 @@ @@ -9,8 +10,10 @@ android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:supportsRtl="true" - android:theme="@style/AppTheme"> - + android:theme="@style/AppTheme" + tools:ignore="GoogleAppIndexingWarning" + android:name=".CulqiApplication"> + diff --git a/app/src/main/java/com/android/culqi/culqi_android/Culqi/Card.java b/app/src/main/java/com/android/culqi/culqi_android/Culqi/Card.java deleted file mode 100644 index bcff8d8..0000000 --- a/app/src/main/java/com/android/culqi/culqi_android/Culqi/Card.java +++ /dev/null @@ -1,66 +0,0 @@ -package com.android.culqi.culqi_android.Culqi; - -/** - * Created by culqi on 2/7/17. - */ - -public class Card { - - public Card(String card_number, String cvv, int expiration_month, int expiration_year, String email) { - this.card_number = card_number; - this.cvv = cvv; - this.expiration_month = expiration_month; - this.expiration_year = expiration_year; - this.email = email; - } - - private String card_number; - - private String cvv; - - private int expiration_month; - - private int expiration_year; - - private String email; - - public String getCard_number() { - return card_number; - } - - public void setCard_number(String card_number) { - this.card_number = card_number; - } - - public String getCvv() { - return cvv; - } - - public void setCvv(String cvv) { - this.cvv = cvv; - } - - public int getExpiration_month() { - return expiration_month; - } - - public void setExpiration_month(int expiration_month) { - this.expiration_month = expiration_month; - } - - public int getExpiration_year() { - return expiration_year; - } - - public void setExpiration_year(int expiration_year) { - this.expiration_year = expiration_year; - } - - public String getEmail() { - return email; - } - - public void setEmail(String email) { - this.email = email; - } -} diff --git a/app/src/main/java/com/android/culqi/culqi_android/Culqi/Config.java b/app/src/main/java/com/android/culqi/culqi_android/Culqi/Config.java deleted file mode 100644 index e3ba45d..0000000 --- a/app/src/main/java/com/android/culqi/culqi_android/Culqi/Config.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.android.culqi.culqi_android.Culqi; - -/** - * Created by culqi on 1/19/17. - */ - -public class Config { - - public String url_base = "https://api.culqi.com/v2"; - public String url_base_secure = "https://secure.culqi.com/v2"; - -} diff --git a/app/src/main/java/com/android/culqi/culqi_android/Culqi/Token.java b/app/src/main/java/com/android/culqi/culqi_android/Culqi/Token.java deleted file mode 100644 index b80dc4e..0000000 --- a/app/src/main/java/com/android/culqi/culqi_android/Culqi/Token.java +++ /dev/null @@ -1,91 +0,0 @@ -package com.android.culqi.culqi_android.Culqi; - -import android.content.Context; -import android.util.Log; - -import com.android.volley.AuthFailureError; -import com.android.volley.DefaultRetryPolicy; -import com.android.volley.Request; -import com.android.volley.RequestQueue; -import com.android.volley.Response; -import com.android.volley.VolleyError; -import com.android.volley.toolbox.JsonObjectRequest; -import com.android.volley.toolbox.Volley; - -import org.json.JSONObject; - -import java.util.HashMap; -import java.util.Map; - -/** - * Created by culqi on 1/19/17. - */ - -public class Token { - - Config config = new Config(); - - private static final String URL = "/tokens/"; - - private String api_key; - - private TokenCallback listener; - - public Token(String api_key){ - this.api_key = api_key; - this.listener = null; - } - - public void createToken(Context context, Card card, final TokenCallback listener) { - - this.listener = listener; - - RequestQueue requestQueue = Volley.newRequestQueue(context); - - JSONObject jsonBody = new JSONObject(); - try { - jsonBody = new JSONObject(); - jsonBody.put("card_number", card.getCard_number()); - jsonBody.put("cvv", card.getCvv()); - jsonBody.put("expiration_month", card.getExpiration_month()); - jsonBody.put("expiration_year", card.getExpiration_year()); - jsonBody.put("email", card.getEmail()); - } catch (Exception ex){ - Log.v("", "ERROR: "+ex.getMessage()); - } - - JsonObjectRequest jsonObjectRequest = new JsonObjectRequest(Request.Method.POST, config.url_base_secure+URL,jsonBody, new Response.Listener(){ - @Override - public void onResponse(JSONObject response) { - try { - listener.onSuccess(response); - } catch (Exception ex){ - listener.onError(ex); - } - } - }, new Response.ErrorListener() { - @Override - public void onErrorResponse(VolleyError error) { - listener.onError(error); - } - }) { - @Override - public Map getHeaders() throws AuthFailureError { - Map headers = new HashMap<>(); - headers.put("Content-Type", "application/json; charset=utf-8"); - headers.put("Authorization", "Bearer " + Token.this.api_key); - return headers; - } - - }; - - jsonObjectRequest.setRetryPolicy(new DefaultRetryPolicy( - 30000, - DefaultRetryPolicy.DEFAULT_MAX_RETRIES, - DefaultRetryPolicy.DEFAULT_BACKOFF_MULT)); - - requestQueue.add(jsonObjectRequest); - - } - -} diff --git a/app/src/main/java/com/android/culqi/culqi_android/Culqi/TokenCallback.java b/app/src/main/java/com/android/culqi/culqi_android/Culqi/TokenCallback.java deleted file mode 100644 index 9c4509e..0000000 --- a/app/src/main/java/com/android/culqi/culqi_android/Culqi/TokenCallback.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.android.culqi.culqi_android.Culqi; - -import org.json.JSONObject; - -/** - * Created by culqi on 2/7/17. - */ - -public interface TokenCallback { - - public void onSuccess(JSONObject token); - - public void onError(Exception error); - -} diff --git a/app/src/main/java/com/android/culqi/culqi_android/CulqiApplication.kt b/app/src/main/java/com/android/culqi/culqi_android/CulqiApplication.kt new file mode 100644 index 0000000..670127e --- /dev/null +++ b/app/src/main/java/com/android/culqi/culqi_android/CulqiApplication.kt @@ -0,0 +1,21 @@ +package com.android.culqi.culqi_android + +import android.app.Application +import com.android.culqi.culqi_android.di.modules +import org.koin.android.ext.koin.androidContext +import org.koin.android.ext.koin.androidLogger +import org.koin.core.context.startKoin + +class CulqiApplication : Application() { + + + override fun onCreate() { + super.onCreate() + + startKoin { + androidContext(this@CulqiApplication) + androidLogger() + modules(modules) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/android/culqi/culqi_android/MainActivity.java b/app/src/main/java/com/android/culqi/culqi_android/MainActivity.java deleted file mode 100644 index 3bc2092..0000000 --- a/app/src/main/java/com/android/culqi/culqi_android/MainActivity.java +++ /dev/null @@ -1,165 +0,0 @@ -package com.android.culqi.culqi_android; - -import android.app.ProgressDialog; -import android.os.Bundle; -import android.support.v7.app.AppCompatActivity; -import android.text.Editable; -import android.text.InputFilter; -import android.text.TextWatcher; -import android.view.View; -import android.widget.Button; -import android.widget.TextView; - -import com.android.culqi.culqi_android.Culqi.Card; -import com.android.culqi.culqi_android.Culqi.Token; -import com.android.culqi.culqi_android.Culqi.TokenCallback; -import com.android.culqi.culqi_android.Validation.Validation; - -import org.json.JSONObject; - -public class MainActivity extends AppCompatActivity { - - Validation validation; - - ProgressDialog progress; - - TextView txtcardnumber, txtcvv, txtmonth, txtyear, txtemail, kind_card, result; - Button btnPay; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_main); - - validation = new Validation(); - - progress = new ProgressDialog(this); - progress.setMessage("Validando informacion de la tarjeta"); - progress.setCancelable(false); - progress.setProgressStyle(ProgressDialog.STYLE_SPINNER); - - txtcardnumber = (TextView) findViewById(R.id.txt_cardnumber); - - txtcvv = (TextView) findViewById(R.id.txt_cvv); - - txtmonth = (TextView) findViewById(R.id.txt_month); - - txtyear = (TextView) findViewById(R.id.txt_year); - - txtemail = (TextView) findViewById(R.id.txt_email); - - kind_card = (TextView) findViewById(R.id.kind_card); - - result = (TextView) findViewById(R.id.token_id); - - btnPay = (Button) findViewById(R.id.btn_pay); - - txtcvv.setEnabled(false); - - txtcardnumber.addTextChangedListener(new TextWatcher() { - @Override - public void beforeTextChanged(CharSequence s, int start, int count, int after) { - } - - @Override - public void onTextChanged(CharSequence s, int start, int before, int count) { - if(s.length() == 0){ - txtcvv.setEnabled(true); - } - } - - @Override - public void afterTextChanged(Editable s) { - String text = txtcardnumber.getText().toString(); - if(s.length() == 0) { - txtcardnumber.setBackgroundResource(R.drawable.border_error); - } - - if(validation.luhn(text)) { - txtcardnumber.setBackgroundResource(R.drawable.border_sucess); - } else { - txtcardnumber.setBackgroundResource(R.drawable.border_error); - } - - int cvv = validation.bin(text, kind_card); - if(cvv > 0) { - txtcvv.setFilters(new InputFilter[]{new InputFilter.LengthFilter(cvv)}); - txtcvv.setEnabled(true); - } else { - txtcvv.setEnabled(false); - txtcvv.setText(""); - } - } - }); - - txtyear.addTextChangedListener(new TextWatcher() { - @Override - public void beforeTextChanged(CharSequence s, int start, int count, int after) { - } - - @Override - public void onTextChanged(CharSequence s, int start, int before, int count) { - } - - @Override - public void afterTextChanged(Editable s) { - String text = txtyear.getText().toString(); - if(validation.year(text)){ - txtyear.setBackgroundResource(R.drawable.border_error); - } else { - txtyear.setBackgroundResource(R.drawable.border_sucess); - } - } - }); - - txtmonth.addTextChangedListener(new TextWatcher() { - @Override - public void beforeTextChanged(CharSequence s, int start, int count, int after) { - } - - @Override - public void onTextChanged(CharSequence s, int start, int before, int count) { - } - - @Override - public void afterTextChanged(Editable s) { - String text = txtmonth.getText().toString(); - if(validation.month(text)){ - txtmonth.setBackgroundResource(R.drawable.border_error); - } else { - txtmonth.setBackgroundResource(R.drawable.border_sucess); - } - } - }); - - btnPay.setOnClickListener(new View.OnClickListener(){ - public void onClick(View v){ - progress.show(); - - Card card = new Card(txtcardnumber.getText().toString(), txtcvv.getText().toString(), 9, 2020, txtemail.getText().toString()); - - Token token = new Token("pk_test_vzMuTHoueOMlgUPj"); - - token.createToken(getApplicationContext(), card, new TokenCallback() { - @Override - public void onSuccess(JSONObject token) { - try { - result.setText(token.get("id").toString()); - } catch (Exception ex){ - progress.hide(); - } - progress.hide(); - } - - @Override - public void onError(Exception error) { - progress.hide(); - } - }); - - } - }); - - } - -} diff --git a/app/src/main/java/com/android/culqi/culqi_android/Validation/Validation.java b/app/src/main/java/com/android/culqi/culqi_android/Validation/Validation.java deleted file mode 100644 index 83b23f5..0000000 --- a/app/src/main/java/com/android/culqi/culqi_android/Validation/Validation.java +++ /dev/null @@ -1,94 +0,0 @@ -package com.android.culqi.culqi_android.Validation; - -import android.widget.TextView; - -import java.util.Calendar; -import java.util.Date; - -/** - * Created by culqi on 1/19/17. - */ - -public class Validation { - - public static boolean luhn(String number){ - int s1 = 0, s2 = 0; - String reverse = new StringBuffer(number).reverse().toString(); - for(int i = 0 ;i < reverse.length();i++){ - int digit = Character.digit(reverse.charAt(i), 10); - if(i % 2 == 0){//this is for odd digits, they are 1-indexed in the algorithm - s1 += digit; - }else{//add 2 * digit for 0-4, add 2 * digit - 9 for 5-9 - s2 += 2 * digit; - if(digit >= 5){ - s2 -= 9; - } - } - } - return (s1 + s2) % 10 == 0; - } - - public int bin(String bin, final TextView kind_card) { - - if(bin.length() > 1) { - if(Integer.valueOf(bin.substring(0,2)) == 41) { - kind_card.setText("VISA"); - return 3; - } else if (Integer.valueOf(bin.substring(0,2)) == 51) { - kind_card.setText("MasterCard"); - return 3; - } else { - } - } else { - kind_card.setText(""); - } - - if(bin.length() > 1) { - if(Integer.valueOf(bin.substring(0,2)) == 36){ - kind_card.setText("Diners Club"); - return 3; - } else if(Integer.valueOf(bin.substring(0,2)) == 38){ - kind_card.setText("Diners Club"); - return 3; - } else if(Integer.valueOf(bin.substring(0,2)) == 37){ - kind_card.setText("AMEX"); - return 3; - } else { - } - } - - if(bin.length() > 2) { - if(Integer.valueOf(bin.substring(0,3)) == 300){ - kind_card.setText("Diners Club"); - return 3; - } else if(Integer.valueOf(bin.substring(0,3)) == 305){ - kind_card.setText("Diners Club"); - return 3; - } else { - } - } - return 0; - } - - public boolean month(String month) { - if(!month.equals("")){ - if(Integer.valueOf(""+month) > 12) { - return true; - } - } - return false; - } - - public boolean year(String year){ - Date today = new Date(); - Calendar calendar = Calendar.getInstance(); - calendar.setTime(today); - if(!year.equals("")){ - if(Integer.valueOf("20"+year) < calendar.get(Calendar.YEAR)) { - return true; - } - } - return false; - } - -} diff --git a/app/src/main/java/com/android/culqi/culqi_android/activities/MainActivity.kt b/app/src/main/java/com/android/culqi/culqi_android/activities/MainActivity.kt new file mode 100644 index 0000000..d49a100 --- /dev/null +++ b/app/src/main/java/com/android/culqi/culqi_android/activities/MainActivity.kt @@ -0,0 +1,129 @@ +package com.android.culqi.culqi_android.activities + +import android.app.ProgressDialog +import android.os.Bundle +import android.text.Editable +import android.text.InputFilter +import android.text.TextWatcher +import android.util.Log +import androidx.appcompat.app.AppCompatActivity +import com.android.culqi.culqi_android.R +import com.android.culqi.culqi_android.rest.TokenRestDataStore +import com.android.culqi.culqi_android.model.Card +import com.android.culqi.culqi_android.utils.Validation +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.disposables.CompositeDisposable +import io.reactivex.schedulers.Schedulers +import kotlinx.android.synthetic.main.activity_main.* + +class MainActivity : AppCompatActivity() { + + private lateinit var progress:ProgressDialog + + protected val disposables = CompositeDisposable() + + private val mTokenRestDataStore by lazy { TokenRestDataStore() } + + internal lateinit var validation: Validation + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_main) + validation = Validation() + + progress = ProgressDialog(this) + progress.setMessage("Validando informacion de la tarjeta") + progress.setCancelable(false) + progress.setProgressStyle(ProgressDialog.STYLE_SPINNER) + + etMainCardNumber.addTextChangedListener(object : TextWatcher { + override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {} + + override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) { + if (s.isEmpty()) { + etMainCVV.isEnabled = true + } + } + + override fun afterTextChanged(s: Editable) { + val text = etMainCardNumber.text.toString() + if (s.length == 0) { + etMainCardNumber.setBackgroundResource(R.drawable.border_error) + } + + if (validation.luhn(text)) { + etMainCardNumber.setBackgroundResource(R.drawable.border_sucess) + } else { + etMainCardNumber.setBackgroundResource(R.drawable.border_error) + } + + val cvv = validation.bin(text) + tvMainKindCard.text = cvv.first + if (cvv.second > 0) { + etMainCVV.filters = arrayOf(InputFilter.LengthFilter(cvv.second)) + etMainCVV.isEnabled = true + } else { + etMainCVV.isEnabled = false + etMainCVV.setText("") + } + } + }) + + etMainYear.addTextChangedListener(object : TextWatcher { + override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {} + + override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {} + + override fun afterTextChanged(s: Editable) { + val text = etMainYear.text.toString() + if (validation.year(text)) { + etMainYear.setBackgroundResource(R.drawable.border_error) + } else { + etMainYear.setBackgroundResource(R.drawable.border_sucess) + } + } + }) + + etMainMonth.addTextChangedListener(object : TextWatcher { + override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {} + + override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {} + + override fun afterTextChanged(s: Editable) { + val text = etMainMonth.text.toString() + if (validation.month(text)) { + etMainMonth.setBackgroundResource(R.drawable.border_error) + } else { + etMainMonth.setBackgroundResource(R.drawable.border_sucess) + } + } + }) + + btMainCreateToken.setOnClickListener { + progress.show() + disposables.add(mTokenRestDataStore.getToken(Card( + card_number = etMainCardNumber.text.toString(), + cvv = etMainCVV.text.toString(), + expiration_month = "09", + expiration_year = 2020, + email = etMainEmail.text.toString() + )) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe({ + progress.hide() + tvMainTokenId.text = it.id + }, { + progress.hide() + Log.d("Error", "${it.message}") + }) + ) + } + + } + + override fun onDestroy() { + super.onDestroy() + disposables.clear() + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/android/culqi/culqi_android/di/modules.kt b/app/src/main/java/com/android/culqi/culqi_android/di/modules.kt new file mode 100644 index 0000000..4a81156 --- /dev/null +++ b/app/src/main/java/com/android/culqi/culqi_android/di/modules.kt @@ -0,0 +1,17 @@ +package com.android.culqi.culqi_android.di + +import com.android.culqi.culqi_android.rest.TokenRestDataStore +import org.koin.dsl.module + +private val mainModule = module { + //region Datastore + single { + TokenRestDataStore() + } + //endregion +} + + +val modules = listOf( + mainModule +) \ No newline at end of file diff --git a/app/src/main/java/com/android/culqi/culqi_android/model/Card.kt b/app/src/main/java/com/android/culqi/culqi_android/model/Card.kt new file mode 100644 index 0000000..491a2fc --- /dev/null +++ b/app/src/main/java/com/android/culqi/culqi_android/model/Card.kt @@ -0,0 +1,10 @@ +package com.android.culqi.culqi_android.model + +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +data class Card(val card_number: String, + val cvv: String, + val expiration_month: String, + val expiration_year: Int, + val email: String) \ No newline at end of file diff --git a/app/src/main/java/com/android/culqi/culqi_android/model/Token.kt b/app/src/main/java/com/android/culqi/culqi_android/model/Token.kt new file mode 100644 index 0000000..55f3c35 --- /dev/null +++ b/app/src/main/java/com/android/culqi/culqi_android/model/Token.kt @@ -0,0 +1,14 @@ +package com.android.culqi.culqi_android.model + +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +data class Token( + val id: String?, + val type: String?, + val creation_date: Long?, + val email: String?, + val card_number: String?, + val last_four: String?, + val active: Boolean? +) \ No newline at end of file diff --git a/app/src/main/java/com/android/culqi/culqi_android/rest/ApiClient.kt b/app/src/main/java/com/android/culqi/culqi_android/rest/ApiClient.kt new file mode 100644 index 0000000..73489af --- /dev/null +++ b/app/src/main/java/com/android/culqi/culqi_android/rest/ApiClient.kt @@ -0,0 +1,5 @@ +package com.android.culqi.culqi_android.rest + +import com.android.culqi.culqi_android.rest.interfaces.ITokenApiClient + +object TokenApiClient: BaseApiClient(ITokenApiClient::class.java) \ No newline at end of file diff --git a/app/src/main/java/com/android/culqi/culqi_android/rest/BaseApiClient.kt b/app/src/main/java/com/android/culqi/culqi_android/rest/BaseApiClient.kt new file mode 100644 index 0000000..012de0d --- /dev/null +++ b/app/src/main/java/com/android/culqi/culqi_android/rest/BaseApiClient.kt @@ -0,0 +1,45 @@ +package com.android.culqi.culqi_android.rest + +import android.content.Context +import com.android.culqi.culqi_android.BuildConfig +import okhttp3.OkHttpClient +import okhttp3.logging.HttpLoggingInterceptor +import org.koin.core.KoinComponent +import org.koin.core.inject +import retrofit2.Retrofit +import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory +import retrofit2.converter.moshi.MoshiConverterFactory +import java.util.concurrent.TimeUnit + +open class BaseApiClient(private val classT: Class) : KoinComponent { + private val HEADER_AUTHORIZATION = "Authorization" + private val HEADER_AUTHORIZATION_TYPE = "Bearer " + private val context: Context by inject() + + open fun getApiClient( + CONNECTION_TIMEOUT: Long = 180L, + READ_TIMEOUT: Long = 180L, + WRITE_TIMEOUT: Long = 180L + ): T { + + val httpLoggingInterceptor = HttpLoggingInterceptor() + httpLoggingInterceptor.level= HttpLoggingInterceptor.Level.BODY + + val okHttpClient = OkHttpClient().newBuilder() + .connectTimeout(CONNECTION_TIMEOUT, TimeUnit.SECONDS) + .readTimeout(READ_TIMEOUT, TimeUnit.SECONDS) + .writeTimeout(WRITE_TIMEOUT, TimeUnit.SECONDS) + .addInterceptor(httpLoggingInterceptor) + .build() + + val retrofitBuilder = Retrofit.Builder().apply { + baseUrl(BuildConfig.URL_SERVER) + client(okHttpClient) + addConverterFactory(MoshiConverterFactory.create()) + addCallAdapterFactory(RxJava2CallAdapterFactory.create()) + } + + val retrofit = retrofitBuilder.build() + return retrofit.create(classT) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/android/culqi/culqi_android/rest/TokenRestDataStore.kt b/app/src/main/java/com/android/culqi/culqi_android/rest/TokenRestDataStore.kt new file mode 100644 index 0000000..56e0ce8 --- /dev/null +++ b/app/src/main/java/com/android/culqi/culqi_android/rest/TokenRestDataStore.kt @@ -0,0 +1,12 @@ +package com.android.culqi.culqi_android.rest + +import com.android.culqi.culqi_android.model.Card +import com.android.culqi.culqi_android.model.Token +import io.reactivex.Single + +class TokenRestDataStore { + + fun getToken(card: Card): Single { + return TokenApiClient.getApiClient().getToken(card = card) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/android/culqi/culqi_android/rest/interfaces/ITokenApiClient.kt b/app/src/main/java/com/android/culqi/culqi_android/rest/interfaces/ITokenApiClient.kt new file mode 100644 index 0000000..99e9794 --- /dev/null +++ b/app/src/main/java/com/android/culqi/culqi_android/rest/interfaces/ITokenApiClient.kt @@ -0,0 +1,18 @@ +package com.android.culqi.culqi_android.rest.interfaces + +import com.android.culqi.culqi_android.BuildConfig +import com.android.culqi.culqi_android.model.Card +import com.android.culqi.culqi_android.model.Token +import io.reactivex.Single +import retrofit2.http.Body +import retrofit2.http.Header +import retrofit2.http.POST + +interface ITokenApiClient { + + @POST("tokens/") + fun getToken(@Header("Authorization") authorization: String = BuildConfig.AUTHORIZATION, + @Header("Content-Type") contentType:String = BuildConfig.CONTENT_TYPE, + @Body card: Card): Single + +} \ No newline at end of file diff --git a/app/src/main/java/com/android/culqi/culqi_android/utils/Validation.kt b/app/src/main/java/com/android/culqi/culqi_android/utils/Validation.kt new file mode 100644 index 0000000..ca0beee --- /dev/null +++ b/app/src/main/java/com/android/culqi/culqi_android/utils/Validation.kt @@ -0,0 +1,81 @@ +package com.android.culqi.culqi_android.utils + +import java.util.* + +class Validation { + + fun bin(bin: String): Pair { + + if (bin.length > 1) { + when (Integer.valueOf(bin.substring(0, 2))) { + 36 -> { + return Pair("Diners Club",3) + } + 37 -> { + return Pair("Diners Club",3) + } + 38 -> { + return Pair("Diners Club",3) + } + 41 -> { + return Pair("VISA",3) + } + 51 -> { + return Pair("MasterCard",3) + } + } + } + + if (bin.length > 2) { + when (Integer.valueOf(bin.substring(0, 3))) { + 300 -> { + return Pair("Diners Club",3) + } + 305 -> { + return Pair("Diners Club",3) + } + } + } + return Pair("",0) + } + + fun month(month: String): Boolean { + if (month != "") { + if (month.toInt() > 12) { + return true + } + } + return false + } + + fun year(year: String): Boolean { + val today = Date() + val calendar = Calendar.getInstance() + calendar.time = today + if (year != "") { + if (Integer.valueOf("20$year") < calendar.get(Calendar.YEAR)) { + return true + } + } + return false + } + + fun luhn(number: String): Boolean { + var s1 = 0 + var s2 = 0 + val reverse = StringBuffer(number).reverse().toString() + for (i in 0 until reverse.length) { + val digit = Character.digit(reverse[i], 10) + if (i % 2 == 0) {//this is for odd digits, they are 1-indexed in the algorithm + s1 += digit + } else {//add 2 * digit for 0-4, add 2 * digit - 9 for 5-9 + s2 += 2 * digit + if (digit >= 5) { + s2 -= 9 + } + } + } + return (s1 + s2) % 10 == 0 + } + +} \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index cb7f5c1..9388eba 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -1,123 +1,123 @@ - + android:selectAllOnFocus="false" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" /> + android:layout_marginTop="19dp" + android:background="@drawable/border" + android:ems="10" + android:hint="@string/main_month" + android:inputType="number" + android:maxLength="2" + app:layout_constraintEnd_toStartOf="@+id/etMainYear" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/etMainCardNumber" /> + android:background="@drawable/border" + android:ems="10" + android:hint="@string/main_year" + android:inputType="number" + android:maxLength="2" + app:layout_constraintBaseline_toBaselineOf="@+id/etMainMonth" + app:layout_constraintEnd_toStartOf="@+id/etMainCVV" + app:layout_constraintStart_toEndOf="@+id/etMainMonth" /> + + + android:ems="10" + android:hint="@string/main_email" + android:inputType="textEmailAddress" />