Santosh Bhandary
3 years ago
24 changed files with 725 additions and 58 deletions
-
9app/src/main/java/com/swifttech/remit/jmecustomer/base/PrefKeys.java
-
27app/src/main/java/com/swifttech/remit/jmecustomer/base/PrivilegedGateway.java
-
4app/src/main/java/com/swifttech/remit/jmecustomer/base/PrivilegedGatewayInterface.java
-
6app/src/main/java/com/swifttech/remit/jmecustomer/features/kyc/existingCustomer/presenter/ExistingKYCV3ViewModel.java
-
5app/src/main/java/com/swifttech/remit/jmecustomer/features/kyc/newCustomer/presenter/KYCV3ViewModel.java
-
8app/src/main/java/com/swifttech/remit/jmecustomer/features/login/gateway/LoginV2Gateway.java
-
5app/src/main/java/com/swifttech/remit/jmecustomer/features/login/presenter/LoginV2InteractorInterface.java
-
6app/src/main/java/com/swifttech/remit/jmecustomer/features/login/presenter/LoginV2PresenterInterface.java
-
28app/src/main/java/com/swifttech/remit/jmecustomer/features/login/presenter/LoginV2ViewModel.java
-
70app/src/main/java/com/swifttech/remit/jmecustomer/features/login/view/LoginV2Activity.java
-
5app/src/main/java/com/swifttech/remit/jmecustomer/features/register/presenter/RegisterV2Presenter.java
-
5app/src/main/java/com/swifttech/remit/jmecustomer/features/registerv2/existingcustomer/presenter/ExistingCustomerRegisterViewModel.java
-
5app/src/main/java/com/swifttech/remit/jmecustomer/features/registerv2/newcustomer/presenter/NewRegisterV2Presenter.java
-
2app/src/main/java/com/swifttech/remit/jmecustomer/features/security/RemitAuthManager.java
-
455app/src/main/java/com/swifttech/remit/jmecustomer/features/security/RemitAuthManager1.java
-
11app/src/main/java/com/swifttech/remit/jmecustomer/features/settings/view/SettingsView.java
-
35app/src/main/java/com/swifttech/remit/jmecustomer/utils/Constants.java
-
59app/src/main/java/com/swifttech/remit/jmecustomer/utils/Utils.java
-
8app/src/main/res/layout/fragment_existing_customer_registration_submit.xml
-
6app/src/main/res/values-bn/strings.xml
-
6app/src/main/res/values-ja/strings.xml
-
6app/src/main/res/values-ne/strings.xml
-
6app/src/main/res/values-vi/strings.xml
-
6app/src/main/res/values/strings.xml
@ -0,0 +1,455 @@ |
|||
package com.swifttech.remit.jmecustomer.features.security; |
|||
|
|||
import android.content.SharedPreferences; |
|||
import android.os.Build; |
|||
import android.security.keystore.KeyGenParameterSpec; |
|||
import android.security.keystore.KeyProperties; |
|||
import android.util.Base64; |
|||
|
|||
import androidx.annotation.NonNull; |
|||
import androidx.annotation.RequiresApi; |
|||
import androidx.appcompat.app.AppCompatActivity; |
|||
import androidx.biometric.BiometricManager; |
|||
import androidx.biometric.BiometricPrompt; |
|||
|
|||
import com.swifttech.remit.jmecustomer.R; |
|||
import com.swifttech.remit.jmecustomer.RemitApplication; |
|||
import com.swifttech.remit.jmecustomer.features.security.model.RemitAuthFailedResult; |
|||
import com.swifttech.remit.jmecustomer.features.security.model.RemitAuthSuccessResult; |
|||
|
|||
import java.security.Key; |
|||
import java.security.KeyStore; |
|||
import java.util.Arrays; |
|||
import java.util.List; |
|||
import java.util.concurrent.ExecutorService; |
|||
import java.util.concurrent.Executors; |
|||
|
|||
import javax.crypto.BadPaddingException; |
|||
import javax.crypto.Cipher; |
|||
import javax.crypto.IllegalBlockSizeException; |
|||
import javax.crypto.KeyGenerator; |
|||
import javax.crypto.SecretKey; |
|||
import javax.crypto.spec.IvParameterSpec; |
|||
|
|||
import static androidx.biometric.BiometricManager.Authenticators.BIOMETRIC_WEAK; |
|||
import static com.swifttech.remit.jmecustomer.base.PrefKeys.PREF_DEFAULT_ENCRYPTED; |
|||
import static com.swifttech.remit.jmecustomer.base.PrefKeys.PREF_DEFAULT_INITIALIZATION_VECTOR; |
|||
import static com.swifttech.remit.jmecustomer.base.PrefKeys.PREF_LOGIN_CREDENTIALS_DATA_ENCRYPTED; |
|||
import static com.swifttech.remit.jmecustomer.base.PrefKeys.PREF_LOGIN_CREDENTIALS_INITIALIZATION_VECTOR; |
|||
import static com.swifttech.remit.jmecustomer.base.PrefKeys.PREF_PIN_DATA_ENCRYPTED; |
|||
import static com.swifttech.remit.jmecustomer.base.PrefKeys.PREF_PIN_INITIALIZATION_VECTOR; |
|||
import static com.swifttech.remit.jmecustomer.utils.Constants.ACTION_DECRYPT; |
|||
import static com.swifttech.remit.jmecustomer.utils.Constants.ACTION_ENCRYPT; |
|||
import static com.swifttech.remit.jmecustomer.utils.Constants.ALGORITHM; |
|||
import static com.swifttech.remit.jmecustomer.utils.Constants.BIOMETRIC_ERROR_HW_UNAVAILABLE; |
|||
import static com.swifttech.remit.jmecustomer.utils.Constants.BIOMETRIC_ERROR_NONE_ENROLLED; |
|||
import static com.swifttech.remit.jmecustomer.utils.Constants.BIOMETRIC_ERROR_NO_HARDWARE; |
|||
import static com.swifttech.remit.jmecustomer.utils.Constants.BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED; |
|||
import static com.swifttech.remit.jmecustomer.utils.Constants.BIOMETRIC_SUCCESS; |
|||
import static com.swifttech.remit.jmecustomer.utils.Constants.BLOCK_MODE; |
|||
import static com.swifttech.remit.jmecustomer.utils.Constants.DEFAULT; |
|||
import static com.swifttech.remit.jmecustomer.utils.Constants.KEYS_TORE; |
|||
import static com.swifttech.remit.jmecustomer.utils.Constants.KEY_DEFAULT_NAME; |
|||
import static com.swifttech.remit.jmecustomer.utils.Constants.KEY_LOGIN_CREDENTIALS_NAME; |
|||
import static com.swifttech.remit.jmecustomer.utils.Constants.KEY_PIN_NAME; |
|||
import static com.swifttech.remit.jmecustomer.utils.Constants.LOGIN; |
|||
import static com.swifttech.remit.jmecustomer.utils.Constants.PADDING; |
|||
import static com.swifttech.remit.jmecustomer.utils.Constants.PIN; |
|||
import static com.swifttech.remit.jmecustomer.utils.https.HTTPConstants.INVALID_REQUEST; |
|||
|
|||
public class RemitAuthManager1 { |
|||
private AppCompatActivity activity; |
|||
private RemitAuthListener1 listener; |
|||
private KeyStore keyStore; |
|||
|
|||
public RemitAuthManager1(AppCompatActivity activity) { |
|||
this.activity = activity; |
|||
} |
|||
|
|||
public void setListener(RemitAuthListener1 listener) { |
|||
this.listener = listener; |
|||
} |
|||
|
|||
private void resetParamToDefault() { |
|||
listener = null; |
|||
} |
|||
|
|||
@RequiresApi(Build.VERSION_CODES.M) |
|||
public SecretKey createKey(String type) throws Exception { |
|||
KeyGenerator keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, KEYS_TORE); |
|||
String KEY_NAME; |
|||
switch (type) { |
|||
case PIN: |
|||
KEY_NAME = KEY_PIN_NAME; |
|||
break; |
|||
case LOGIN: |
|||
KEY_NAME = KEY_LOGIN_CREDENTIALS_NAME; |
|||
break; |
|||
default: |
|||
KEY_NAME = KEY_DEFAULT_NAME; |
|||
break; |
|||
} |
|||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { |
|||
keyGenerator.init(new KeyGenParameterSpec.Builder(KEY_NAME, |
|||
KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT) |
|||
.setBlockModes(KeyProperties.BLOCK_MODE_CBC) |
|||
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7) |
|||
.setUserAuthenticationRequired(false) |
|||
.setInvalidatedByBiometricEnrollment(true) |
|||
.build()); |
|||
} else { |
|||
keyGenerator.init(new KeyGenParameterSpec.Builder(KEY_NAME, |
|||
KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT) |
|||
.setBlockModes(KeyProperties.BLOCK_MODE_CBC) |
|||
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7) |
|||
.setUserAuthenticationRequired(false) |
|||
.build()); |
|||
} |
|||
SecretKey secretKey = keyGenerator.generateKey(); |
|||
return secretKey; |
|||
} |
|||
|
|||
private Key getKey(String type) throws Exception { |
|||
keyStore = KeyStore.getInstance(KEYS_TORE); |
|||
keyStore.load(null); |
|||
switch (type) { |
|||
case PIN: |
|||
return keyStore.getKey(KEY_PIN_NAME, null); |
|||
case LOGIN: |
|||
return keyStore.getKey(KEY_LOGIN_CREDENTIALS_NAME, null); |
|||
default: |
|||
return keyStore.getKey(KEY_DEFAULT_NAME, null); |
|||
} |
|||
} |
|||
|
|||
void encryptPrompt( |
|||
String type, |
|||
byte[] data |
|||
) throws Exception { |
|||
try { |
|||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { |
|||
SecretKey secretKey = createKey(type); |
|||
Cipher cipher = getEncryptCipher(secretKey); |
|||
handleEncrypt(type, cipher, data); |
|||
|
|||
} |
|||
|
|||
} catch (Exception e) { |
|||
listener.onRemitAuthFailed(new RemitAuthFailedResult(activity.getResources().getString(R.string.unknownAuthReq_text) + "\n" + INVALID_REQUEST)); |
|||
} |
|||
} |
|||
|
|||
@RequiresApi(api = Build.VERSION_CODES.M) |
|||
private String combineAndReturnString() { |
|||
|
|||
List<String> alphabets = Arrays.asList(ALGORITHM, BLOCK_MODE, PADDING); |
|||
String delimiter = "/"; |
|||
|
|||
String result = "", prefix = ""; |
|||
for (String s : alphabets) { |
|||
result += prefix + s; |
|||
prefix = delimiter; |
|||
} |
|||
return result; |
|||
} |
|||
|
|||
@RequiresApi(Build.VERSION_CODES.M) |
|||
private Cipher getEncryptCipher(Key key) throws Exception { |
|||
Cipher cipher = Cipher.getInstance(combineAndReturnString()); |
|||
cipher.init(Cipher.ENCRYPT_MODE, key); |
|||
return cipher; |
|||
} |
|||
|
|||
private void handleEncrypt( |
|||
String type, |
|||
Cipher cipher, |
|||
byte[] data |
|||
) { |
|||
|
|||
ExecutorService executor = Executors.newSingleThreadExecutor(); |
|||
BiometricPrompt biometricPrompt = new BiometricPrompt(activity, |
|||
executor, new BiometricPrompt.AuthenticationCallback() { |
|||
@Override |
|||
public void onAuthenticationError(int errorCode, |
|||
@NonNull CharSequence errString) { |
|||
super.onAuthenticationError(errorCode, errString); |
|||
listener.onRemitAuthFailed(new RemitAuthFailedResult(activity.getResources().getString(R.string.unknownAuthReq_text) + "\n" + INVALID_REQUEST)); |
|||
} |
|||
|
|||
@Override |
|||
public void onAuthenticationSucceeded( |
|||
@NonNull BiometricPrompt.AuthenticationResult result) { |
|||
super.onAuthenticationSucceeded(result); |
|||
try { |
|||
Cipher resultCipher = result.getCryptoObject().getCipher(); |
|||
byte[] iv = resultCipher.getIV(); |
|||
byte[] encryptedData = resultCipher.doFinal(data); |
|||
saveEncryptedData(type, encryptedData, iv); |
|||
listener.onRemitAuthSuccess(new RemitAuthSuccessResult(activity.getString(R.string.fingerPrintEnrolledSucessfully_text)), encryptedData); |
|||
} catch (BadPaddingException e) { |
|||
e.printStackTrace(); |
|||
} catch (IllegalBlockSizeException e) { |
|||
e.printStackTrace(); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public void onAuthenticationFailed() { |
|||
super.onAuthenticationFailed(); |
|||
listener.onRemitAuthFailed(new RemitAuthFailedResult(activity.getResources().getString(R.string.unknownAuthReq_text) + "\n" + INVALID_REQUEST)); |
|||
} |
|||
}); |
|||
BiometricPrompt.PromptInfo promptInfo = biometricPromptInfo(type, ACTION_ENCRYPT); |
|||
biometricPrompt.authenticate(promptInfo, new BiometricPrompt.CryptoObject(cipher)); |
|||
} |
|||
|
|||
private void saveEncryptedData( |
|||
String type, |
|||
byte[] dataEncrypted, |
|||
byte[] initializationVector |
|||
) { |
|||
SharedPreferences.Editor sharedPreferenceEditor = RemitApplication.getStorage().edit(); |
|||
switch (type) { |
|||
case LOGIN: |
|||
sharedPreferenceEditor.putString(PREF_LOGIN_CREDENTIALS_DATA_ENCRYPTED, Base64.encodeToString( |
|||
dataEncrypted, |
|||
Base64.DEFAULT |
|||
)); |
|||
sharedPreferenceEditor.putString(PREF_LOGIN_CREDENTIALS_INITIALIZATION_VECTOR, Base64.encodeToString( |
|||
initializationVector, |
|||
Base64.DEFAULT |
|||
)); |
|||
break; |
|||
case PIN: |
|||
sharedPreferenceEditor.putString(PREF_PIN_DATA_ENCRYPTED, Base64.encodeToString( |
|||
dataEncrypted, |
|||
Base64.DEFAULT |
|||
)); |
|||
sharedPreferenceEditor.putString(PREF_PIN_INITIALIZATION_VECTOR, Base64.encodeToString( |
|||
initializationVector, |
|||
Base64.DEFAULT |
|||
)); |
|||
break; |
|||
default: |
|||
sharedPreferenceEditor.putString(PREF_DEFAULT_ENCRYPTED, Base64.encodeToString( |
|||
dataEncrypted, |
|||
Base64.DEFAULT |
|||
)); |
|||
sharedPreferenceEditor.putString(PREF_DEFAULT_INITIALIZATION_VECTOR, Base64.encodeToString( |
|||
initializationVector, |
|||
Base64.DEFAULT |
|||
)); |
|||
break; |
|||
|
|||
} |
|||
} |
|||
|
|||
public void decryptPrompt( |
|||
String type |
|||
) throws Exception { |
|||
try { |
|||
Key secretKey = getKey(type); |
|||
byte[] initializationVector = getInitializationVector(type); |
|||
if (secretKey != null && initializationVector != null) { |
|||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { |
|||
Cipher cipher = getDecryptCipher(secretKey, initializationVector); |
|||
handleDecrypt(type, cipher); |
|||
} |
|||
} else { |
|||
listener.onRemitAuthFailed(new RemitAuthFailedResult(activity.getResources().getString(R.string.unknownAuthReq_text) + "\n" + INVALID_REQUEST)); |
|||
} |
|||
} catch (Exception e) { |
|||
listener.onRemitAuthFailed(new RemitAuthFailedResult(activity.getResources().getString(R.string.unknownAuthReq_text) + "\n" + INVALID_REQUEST)); |
|||
} |
|||
} |
|||
|
|||
private void handleDecrypt( |
|||
String type, |
|||
Cipher cipher |
|||
) { |
|||
ExecutorService executor = Executors.newSingleThreadExecutor(); |
|||
BiometricPrompt biometricPrompt = new BiometricPrompt(activity, |
|||
executor, new BiometricPrompt.AuthenticationCallback() { |
|||
@Override |
|||
public void onAuthenticationError(int errorCode, |
|||
@NonNull CharSequence errString) { |
|||
super.onAuthenticationError(errorCode, errString); |
|||
listener.onRemitAuthFailed(new RemitAuthFailedResult(activity.getResources().getString(R.string.unknownAuthReq_text) + "\n" + INVALID_REQUEST)); |
|||
} |
|||
|
|||
@Override |
|||
public void onAuthenticationSucceeded( |
|||
@NonNull BiometricPrompt.AuthenticationResult result) { |
|||
super.onAuthenticationSucceeded(result); |
|||
try { |
|||
Cipher resultCipher = result.getCryptoObject().getCipher(); |
|||
byte[] encrypted = getEncryptedData(type); |
|||
byte[] data = resultCipher.doFinal(encrypted); |
|||
listener.onRemitAuthSuccess(new RemitAuthSuccessResult(activity.getString(R.string.fingerPrintEnrolledSucessfully_text)), data); |
|||
} catch (BadPaddingException e) { |
|||
e.printStackTrace(); |
|||
} catch (IllegalBlockSizeException e) { |
|||
e.printStackTrace(); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public void onAuthenticationFailed() { |
|||
super.onAuthenticationFailed(); |
|||
listener.onRemitAuthFailed(new RemitAuthFailedResult(activity.getResources().getString(R.string.unknownAuthReq_text) + "\n" + INVALID_REQUEST)); |
|||
} |
|||
}); |
|||
BiometricPrompt.PromptInfo promptInfo = biometricPromptInfo(type, ACTION_DECRYPT); |
|||
biometricPrompt.authenticate(promptInfo, new BiometricPrompt.CryptoObject(cipher)); |
|||
} |
|||
|
|||
private byte[] getEncryptedData(String type) { |
|||
String iv = null; |
|||
switch (type) { |
|||
case PIN: |
|||
iv = RemitApplication.getStorage().getString(PREF_PIN_DATA_ENCRYPTED, null); |
|||
break; |
|||
case LOGIN: |
|||
iv = RemitApplication.getStorage().getString(PREF_LOGIN_CREDENTIALS_DATA_ENCRYPTED, null); |
|||
break; |
|||
default: |
|||
iv = RemitApplication.getStorage().getString(PREF_DEFAULT_ENCRYPTED, null); |
|||
break; |
|||
} |
|||
if (iv != null) { |
|||
return Base64.decode(iv, Base64.DEFAULT); |
|||
} else { |
|||
return null; |
|||
} |
|||
|
|||
} |
|||
|
|||
private byte[] getInitializationVector(String type) { |
|||
String iv = null; |
|||
switch (type) { |
|||
case PIN: |
|||
iv = RemitApplication.getStorage().getString(PREF_PIN_INITIALIZATION_VECTOR, null); |
|||
break; |
|||
case LOGIN: |
|||
iv = RemitApplication.getStorage().getString(PREF_LOGIN_CREDENTIALS_INITIALIZATION_VECTOR, null); |
|||
break; |
|||
default: |
|||
iv = RemitApplication.getStorage().getString(PREF_DEFAULT_INITIALIZATION_VECTOR, null); |
|||
break; |
|||
} |
|||
|
|||
if (iv != null) { |
|||
return Base64.decode(iv, Base64.DEFAULT); |
|||
} else { |
|||
return null; |
|||
} |
|||
} |
|||
|
|||
@RequiresApi(Build.VERSION_CODES.M) |
|||
private Cipher getDecryptCipher(Key key, byte[] iv) throws Exception { |
|||
Cipher cipher = Cipher.getInstance(combineAndReturnString()); |
|||
cipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(iv)); |
|||
return cipher; |
|||
} |
|||
|
|||
public void updateCredential( |
|||
String type, |
|||
byte[] data |
|||
) throws Exception { |
|||
try { |
|||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { |
|||
Key secretKey = createKey(type); |
|||
Cipher cipher = getEncryptCipher(secretKey); |
|||
byte[] iv = cipher.getIV(); |
|||
byte[] encryptedData = cipher.doFinal(data); |
|||
saveEncryptedData(type, encryptedData, iv); |
|||
} |
|||
|
|||
} catch (Exception e) { |
|||
|
|||
} |
|||
} |
|||
|
|||
public void getUpdatedCredential(String type) throws Exception { |
|||
try { |
|||
Key secretKey = getKey(type); |
|||
byte[] initializationVector = getInitializationVector(type); |
|||
if (secretKey != null && initializationVector != null) { |
|||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { |
|||
Cipher cipher = getDecryptCipher(secretKey, initializationVector); |
|||
byte[] encrypted = getEncryptedData(type); |
|||
byte[] data = cipher.doFinal(encrypted); |
|||
} |
|||
} |
|||
} catch (Exception e) { |
|||
} |
|||
} |
|||
|
|||
private BiometricPrompt.PromptInfo biometricPromptInfo(String type, String actionType) { |
|||
String negativeButtonText = null; |
|||
switch (type) { |
|||
case LOGIN: |
|||
negativeButtonText = activity.getString(R.string.prompt_info_use_app_password); |
|||
break; |
|||
|
|||
case PIN: |
|||
negativeButtonText = activity.getString(R.string.label_use_pin); |
|||
break; |
|||
default: |
|||
negativeButtonText = DEFAULT; |
|||
break; |
|||
} |
|||
switch (actionType) { |
|||
case ACTION_ENCRYPT: |
|||
return new BiometricPrompt.PromptInfo.Builder() |
|||
.setTitle(activity.getString(R.string.app_name)) |
|||
.setDescription(activity.getString(R.string.biometric_prompt_description)) |
|||
.setConfirmationRequired(false) |
|||
.setNegativeButtonText(activity.getString(R.string.prompt_info_dismiss)) |
|||
.build(); |
|||
case ACTION_DECRYPT: |
|||
return new BiometricPrompt.PromptInfo.Builder() |
|||
.setTitle(activity.getString(R.string.app_name)) |
|||
.setDescription(activity.getString(R.string.biometric_prompt_description)) |
|||
.setNegativeButtonText(negativeButtonText.toUpperCase()) |
|||
.setConfirmationRequired(false) |
|||
.build(); |
|||
default: |
|||
return new BiometricPrompt.PromptInfo.Builder() |
|||
.setTitle(activity.getString(R.string.app_name)) |
|||
.setDescription(activity.getString(R.string.biometric_prompt_description)) |
|||
.setConfirmationRequired(false) |
|||
.build(); |
|||
} |
|||
} |
|||
|
|||
public String bioMetricCanAuthenticates() { |
|||
BiometricManager biometricManager = BiometricManager.from(activity); |
|||
switch (biometricManager.canAuthenticate(BIOMETRIC_WEAK)) { |
|||
case BiometricManager.BIOMETRIC_SUCCESS: |
|||
return BIOMETRIC_SUCCESS; |
|||
case BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED: |
|||
return BIOMETRIC_ERROR_NONE_ENROLLED; |
|||
|
|||
case BiometricManager.BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED: |
|||
return BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED; |
|||
|
|||
case BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE: |
|||
return BIOMETRIC_ERROR_NO_HARDWARE; |
|||
|
|||
case BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE: |
|||
return BIOMETRIC_ERROR_HW_UNAVAILABLE; |
|||
default: |
|||
return null; |
|||
} |
|||
|
|||
} |
|||
|
|||
public interface RemitAuthListener1 { |
|||
void onRemitAuthSuccess(RemitAuthSuccessResult successResult, byte[] data); |
|||
|
|||
void onRemitAuthFailed(RemitAuthFailedResult failedResult); |
|||
|
|||
void onRemitAuthCancelled(); |
|||
} |
|||
} |
Write
Preview
Loading…
Cancel
Save
Reference in new issue