adicionando SDK novo e mudando aditum module para paymentViewModel
This commit is contained in:
parent
d931dfeff9
commit
0abcf81d79
@ -47,7 +47,7 @@ android {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(files("libs/AditumSdkIntegration-2.3.6.67824-release.aar"))
|
implementation(files("libs/AditumSdkIntegration-2.3.4.860743-release.aar"))
|
||||||
implementation("com.google.code.gson:gson:2.13.1")
|
implementation("com.google.code.gson:gson:2.13.1")
|
||||||
implementation("androidx.datastore:datastore-preferences:1.1.7")
|
implementation("androidx.datastore:datastore-preferences:1.1.7")
|
||||||
implementation(libs.androidx.core.ktx)
|
implementation(libs.androidx.core.ktx)
|
||||||
|
|||||||
BIN
app/libs/AditumSdkIntegration-2.3.4.860743-release.aar
Normal file
BIN
app/libs/AditumSdkIntegration-2.3.4.860743-release.aar
Normal file
Binary file not shown.
@ -4,16 +4,11 @@ import android.content.Context
|
|||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.activity.compose.setContent
|
import androidx.activity.compose.setContent
|
||||||
import androidx.activity.enableEdgeToEdge
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.platform.LocalContext
|
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.lifecycle.ViewModel
|
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
import androidx.lifecycle.viewmodel.viewModelFactory
|
|
||||||
import com.example.mypos.models.PaymentViewModel
|
import com.example.mypos.models.PaymentViewModel
|
||||||
import com.example.mypos.screen.PaymentScreen
|
import com.example.mypos.screen.PaymentScreen
|
||||||
import com.example.mypos.services.AditumSdkService
|
|
||||||
import com.example.mypos.services.PaymentApplication
|
import com.example.mypos.services.PaymentApplication
|
||||||
import com.example.mypos.ui.theme.POSTheme
|
import com.example.mypos.ui.theme.POSTheme
|
||||||
|
|
||||||
@ -22,14 +17,13 @@ class MainActivity : ComponentActivity() {
|
|||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
val context = this
|
val context = this
|
||||||
var paymentApplication = application as PaymentApplication
|
var paymentApplication = application as PaymentApplication
|
||||||
var aditumSdkService = AditumSdkService(paymentApplication)
|
|
||||||
var paymentViewModel = ViewModelProvider(
|
var paymentViewModel = ViewModelProvider(
|
||||||
this,
|
this,
|
||||||
PaymentViewModel.provideFactory(paymentApplication, context, aditumSdkService)
|
PaymentViewModel.provideFactory(paymentApplication, context)
|
||||||
)[PaymentViewModel::class.java]
|
)[PaymentViewModel::class.java]
|
||||||
setContent {
|
setContent {
|
||||||
POSTheme {
|
POSTheme {
|
||||||
PaymentScreen(paymentViewModel, aditumSdkService)
|
PaymentScreen(paymentViewModel)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,11 +6,36 @@ import android.util.Log
|
|||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import br.com.aditum.data.v2.IPaymentCallback
|
||||||
import br.com.aditum.data.v2.enums.AbecsCommands
|
import br.com.aditum.data.v2.enums.AbecsCommands
|
||||||
|
import br.com.aditum.data.v2.enums.InstallmentType
|
||||||
|
import br.com.aditum.data.v2.enums.PayOperationType
|
||||||
|
import br.com.aditum.data.v2.enums.PaymentType
|
||||||
import br.com.aditum.data.v2.enums.TransactionStatus
|
import br.com.aditum.data.v2.enums.TransactionStatus
|
||||||
|
import br.com.aditum.data.v2.model.Charge
|
||||||
|
import br.com.aditum.data.v2.model.MerchantData
|
||||||
|
import br.com.aditum.data.v2.model.PinpadMessages
|
||||||
|
import br.com.aditum.data.v2.model.callbacks.GetClearDataFinishedCallback
|
||||||
|
import br.com.aditum.data.v2.model.callbacks.GetClearDataRequest
|
||||||
|
import br.com.aditum.data.v2.model.callbacks.GetMenuSelectionFinishedCallback
|
||||||
|
import br.com.aditum.data.v2.model.callbacks.GetMenuSelectionRequest
|
||||||
|
import br.com.aditum.data.v2.model.cancelation.CancelationRequest
|
||||||
|
import br.com.aditum.data.v2.model.cancelation.CancelationResponse
|
||||||
|
import br.com.aditum.data.v2.model.cancelation.CancelationResponseCallback
|
||||||
|
import br.com.aditum.data.v2.model.deactivation.DeactivationResponseCallback
|
||||||
|
import br.com.aditum.data.v2.model.init.InitRequest
|
||||||
|
import br.com.aditum.data.v2.model.init.InitResponse
|
||||||
|
import br.com.aditum.data.v2.model.init.InitResponseCallback
|
||||||
|
import br.com.aditum.data.v2.model.payment.PaymentRequest
|
||||||
|
import br.com.aditum.data.v2.model.payment.PaymentResponse
|
||||||
|
import br.com.aditum.data.v2.model.payment.PaymentResponseCallback
|
||||||
|
import br.com.aditum.data.v2.model.transactions.ConfirmTransactionCallback
|
||||||
|
import br.com.aditum.data.v2.model.transactions.PendingTransactionsCallback
|
||||||
|
import com.example.mypos.BuildConfig
|
||||||
|
import com.example.mypos.data.AditumError
|
||||||
import com.example.mypos.data.PaymentRegister
|
import com.example.mypos.data.PaymentRegister
|
||||||
import com.example.mypos.services.AditumSdkService
|
|
||||||
import com.example.mypos.services.PaymentApplication
|
import com.example.mypos.services.PaymentApplication
|
||||||
|
import com.google.gson.Gson
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
@ -18,35 +43,31 @@ import kotlinx.coroutines.launch
|
|||||||
|
|
||||||
class PaymentViewModel(
|
class PaymentViewModel(
|
||||||
private val application: PaymentApplication,
|
private val application: PaymentApplication,
|
||||||
private val context: Context,
|
private val context: Context
|
||||||
private val aditumSdkService: AditumSdkService
|
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
private val paymentApplication = application
|
private val paymentApplication = application
|
||||||
|
private val gson = Gson()
|
||||||
private val TAG = "PaymentViewModel"
|
private val TAG = "PaymentViewModel"
|
||||||
private val _isServiceConnected = MutableStateFlow(false)
|
private val _isServiceConnected = MutableStateFlow(false)
|
||||||
val isServiceConnected: StateFlow<Boolean> = _isServiceConnected.asStateFlow()
|
val isServiceConnected: StateFlow<Boolean> = _isServiceConnected.asStateFlow()
|
||||||
|
private val _messageFlow = MutableStateFlow("")
|
||||||
|
val messageFlow: StateFlow<String> = _messageFlow.asStateFlow()
|
||||||
|
private val _pinLengthFlow = MutableStateFlow(0)
|
||||||
|
val pinLengthFlow: StateFlow<Int> = _pinLengthFlow.asStateFlow()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
paymentApplication.isServiceConnectedFlow.collect { isConnected ->
|
paymentApplication.isServiceConnectedFlow.collect { isConnected ->
|
||||||
_isServiceConnected.value = isConnected
|
_isServiceConnected.value = isConnected
|
||||||
}
|
if (isConnected) {
|
||||||
aditumSdkService.register(
|
try {
|
||||||
listener = object : PaymentRegister {
|
paymentApplication.communicationService?.registerPaymentCallback(paymentCallback)
|
||||||
override fun notification(
|
?: Log.e(TAG, "Failed to register payment callback: communicationService is null")
|
||||||
message: String?,
|
} catch (e: Exception) {
|
||||||
transactionStatus: TransactionStatus?,
|
Log.e(TAG, "Error registering payment callback: ${e.message}")
|
||||||
command: AbecsCommands?
|
}
|
||||||
) {
|
|
||||||
val notificationMessage = when (command) {
|
|
||||||
AbecsCommands.Display -> "Exibindo mensagem: $message"
|
|
||||||
AbecsCommands.GetPin -> "Aguardando digitação do PIN"
|
|
||||||
else -> message ?: "Processando..."
|
|
||||||
}
|
|
||||||
Log.d(TAG, notificationMessage)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -54,16 +75,285 @@ class PaymentViewModel(
|
|||||||
paymentApplication.startAditumSdkService()
|
paymentApplication.startAditumSdkService()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val paymentCallback = object : IPaymentCallback.Stub() {
|
||||||
|
override fun notification(
|
||||||
|
message: String?,
|
||||||
|
transactionStatus: TransactionStatus?,
|
||||||
|
command: AbecsCommands?
|
||||||
|
) {
|
||||||
|
Log.i(TAG, "\nnotification - ${ message ?: "" }")
|
||||||
|
if (message != null) {
|
||||||
|
_messageFlow.value = message.replace("\\s+".toRegex(), " ").trim()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun pinNotification(message: String?, length: Int) {
|
||||||
|
_pinLengthFlow.value = length
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun startGetClearData(
|
||||||
|
clearDataRequest: GetClearDataRequest?,
|
||||||
|
finished: GetClearDataFinishedCallback?
|
||||||
|
) {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun startGetMenuSelection(
|
||||||
|
menuSelectionRequest: GetMenuSelectionRequest?,
|
||||||
|
finished: GetMenuSelectionFinishedCallback?
|
||||||
|
) {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun qrCodeGenerated(qrCode: String?, expirationTime: Int) {
|
||||||
|
Log.d(TAG, "\nqrCode - ${ qrCode ?: "" }\nexpirationTime - $expirationTime")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun initAditumSdk(
|
||||||
|
applicationName: String,
|
||||||
|
applicationVersion: String,
|
||||||
|
activationCode: String?,
|
||||||
|
resolve: (Boolean) -> Unit = {},
|
||||||
|
reject: (String, String?) -> Unit = { _, _ -> }
|
||||||
|
) {
|
||||||
|
viewModelScope.launch {
|
||||||
|
try {
|
||||||
|
if (!paymentApplication.ensureServiceConnected()) {
|
||||||
|
reject(AditumError.SERVICE_NOT_AVAILABLE, AditumError.SERVICE_NOT_AVAILABLE_MESSAGE)
|
||||||
|
return@launch
|
||||||
|
}
|
||||||
|
|
||||||
|
val pinpadMessages = PinpadMessages().apply {
|
||||||
|
mainMessage = applicationName
|
||||||
|
}
|
||||||
|
|
||||||
|
val initRequest = InitRequest().apply {
|
||||||
|
this.pinpadMessages = pinpadMessages
|
||||||
|
this.activationCode = if (BuildConfig.DEBUG) BuildConfig.ACTIVATION_CODE else activationCode
|
||||||
|
this.applicationName = applicationName
|
||||||
|
this.applicationVersion = applicationVersion
|
||||||
|
this.useOnlySdk = true
|
||||||
|
this.applicationToken = BuildConfig.APPLICATION_TOKEN
|
||||||
|
}
|
||||||
|
|
||||||
|
val callback = object : InitResponseCallback.Stub() {
|
||||||
|
override fun onResponse(initResponse: InitResponse?) {
|
||||||
|
if (initResponse != null) {
|
||||||
|
resolve(initResponse.initialized)
|
||||||
|
} else {
|
||||||
|
Log.e(TAG, "onResponse - initResponse is null")
|
||||||
|
reject(AditumError.INIT_RESPONSE_NULL, AditumError.INIT_RESPONSE_NULL_MESSAGE)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
paymentApplication.communicationService?.init(initRequest, callback)
|
||||||
|
?: reject(AditumError.SERVICE_NULL, AditumError.SERVICE_NULL_MESSAGE)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
reject(AditumError.INIT_ERROR, "${AditumError.INIT_ERROR_MESSAGE}: ${e.message}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getMerchantData(resolve: (MerchantData) -> Unit = {}, reject: (String, String?) -> Unit = { _, _ -> }) {
|
||||||
|
viewModelScope.launch {
|
||||||
|
try {
|
||||||
|
if (!paymentApplication.ensureServiceConnected()) {
|
||||||
|
reject(AditumError.SERVICE_NOT_AVAILABLE, AditumError.SERVICE_NOT_AVAILABLE_MESSAGE)
|
||||||
|
return@launch
|
||||||
|
}
|
||||||
|
|
||||||
|
val merchantData: MerchantData? = paymentApplication.communicationService?.merchantData
|
||||||
|
if (merchantData != null) {
|
||||||
|
resolve(merchantData)
|
||||||
|
} else {
|
||||||
|
reject(AditumError.MERCHANT_DATA_NULL, AditumError.MERCHANT_DATA_NULL_MESSAGE)
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
reject(AditumError.MERCHANT_DATA_ERROR, "${AditumError.MERCHANT_DATA_ERROR_MESSAGE}: ${e.message}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun pay(
|
||||||
|
amount: Long,
|
||||||
|
installments: Int? = null,
|
||||||
|
paymentType: PaymentType,
|
||||||
|
allowContactless: Boolean = true,
|
||||||
|
amountSeasoning: ((Long) -> Long)? = null,
|
||||||
|
resolve: (PaymentResponse) -> Unit = {},
|
||||||
|
reject: (String, String?) -> Unit = { _, _ -> }
|
||||||
|
) {
|
||||||
|
viewModelScope.launch {
|
||||||
|
try {
|
||||||
|
if (!paymentApplication.ensureServiceConnected()) {
|
||||||
|
reject(AditumError.SERVICE_NOT_AVAILABLE, AditumError.SERVICE_NOT_AVAILABLE_MESSAGE)
|
||||||
|
return@launch
|
||||||
|
}
|
||||||
|
|
||||||
|
val adjustedAmount = amountSeasoning?.invoke(amount) ?: amount
|
||||||
|
if (adjustedAmount <= 0) {
|
||||||
|
reject(AditumError.INVALID_AMOUNT, AditumError.INVALID_AMOUNT_MESSAGE)
|
||||||
|
return@launch
|
||||||
|
}
|
||||||
|
|
||||||
|
val paymentRequest = PaymentRequest().apply {
|
||||||
|
currency = 986
|
||||||
|
operationType = PayOperationType.Authorization
|
||||||
|
this.paymentType = paymentType
|
||||||
|
this.amount = amount
|
||||||
|
this.allowContactless = allowContactless
|
||||||
|
manualEntry = false
|
||||||
|
installmentType = if (paymentType == PaymentType.Debit) InstallmentType.Undefined else InstallmentType.Merchant
|
||||||
|
this.installmentNumber = when {
|
||||||
|
installments == null -> if (paymentType == PaymentType.Credit) 1 else 0
|
||||||
|
installments > 1 -> installments
|
||||||
|
paymentType == PaymentType.Debit -> 0
|
||||||
|
else -> 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val callback = object : PaymentResponseCallback.Stub() {
|
||||||
|
override fun onResponse(paymentResponse: PaymentResponse?) {
|
||||||
|
if (paymentResponse != null) {
|
||||||
|
val json = gson.toJson(paymentResponse)
|
||||||
|
Log.d(TAG, "onResponse - $json")
|
||||||
|
resolve(paymentResponse)
|
||||||
|
} else {
|
||||||
|
Log.e(TAG, "onResponse - paymentResponse is null")
|
||||||
|
reject(AditumError.PAYMENT_RESPONSE_NULL, AditumError.PAYMENT_RESPONSE_NULL_MESSAGE)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
paymentApplication.communicationService?.pay(paymentRequest, callback)
|
||||||
|
?: reject(AditumError.SERVICE_NULL, AditumError.SERVICE_NULL_MESSAGE)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
reject(AditumError.PAYMENT_ERROR, "${AditumError.PAYMENT_ERROR_MESSAGE}: ${e.message}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun confirm(nsu: String, resolve: (Boolean) -> Unit = {}, reject: (String, String?) -> Unit = { _, _ -> }) {
|
||||||
|
viewModelScope.launch {
|
||||||
|
try {
|
||||||
|
if (!paymentApplication.ensureServiceConnected()) {
|
||||||
|
reject(AditumError.SERVICE_NOT_AVAILABLE, AditumError.SERVICE_NOT_AVAILABLE_MESSAGE)
|
||||||
|
return@launch
|
||||||
|
}
|
||||||
|
|
||||||
|
val callback = object : ConfirmTransactionCallback.Stub() {
|
||||||
|
override fun onResponse(confirmed: Boolean) {
|
||||||
|
resolve(confirmed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
paymentApplication.communicationService?.confirmTransaction(nsu, callback)
|
||||||
|
?: reject(AditumError.SERVICE_NULL, AditumError.SERVICE_NULL_MESSAGE)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
reject(AditumError.CONFIRM_ERROR, "${AditumError.CONFIRM_ERROR_MESSAGE}: ${e.message}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun cancel(nsu: String, isReversal: Boolean, resolve: (Boolean) -> Unit = {}, reject: (String, String?) -> Unit = { _, _ -> }) {
|
||||||
|
viewModelScope.launch {
|
||||||
|
try {
|
||||||
|
if (!paymentApplication.ensureServiceConnected()) {
|
||||||
|
reject(AditumError.SERVICE_NOT_AVAILABLE, AditumError.SERVICE_NOT_AVAILABLE_MESSAGE)
|
||||||
|
return@launch
|
||||||
|
}
|
||||||
|
|
||||||
|
val cancelationRequest = CancelationRequest(nsu, isReversal)
|
||||||
|
|
||||||
|
val callback = object : CancelationResponseCallback.Stub() {
|
||||||
|
override fun onResponse(cancelationResponse: CancelationResponse?) {
|
||||||
|
if (cancelationResponse != null) {
|
||||||
|
resolve(cancelationResponse.canceled)
|
||||||
|
} else {
|
||||||
|
Log.e(TAG, "onResponse - cancelationResponse is null")
|
||||||
|
reject(AditumError.CANCEL_RESPONSE_NULL, AditumError.CANCEL_RESPONSE_NULL_MESSAGE)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
paymentApplication.communicationService?.cancel(cancelationRequest, callback)
|
||||||
|
?: reject(AditumError.SERVICE_NULL, AditumError.SERVICE_NULL_MESSAGE)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
reject(AditumError.CANCEL_ERROR, "${AditumError.CANCEL_ERROR_MESSAGE}: ${e.message}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun aboutOperation(resolve: (Boolean) -> Unit = {}, reject: (String, String?) -> Unit = { _, _ -> }) {
|
||||||
|
viewModelScope.launch {
|
||||||
|
if (!paymentApplication.ensureServiceConnected()) {
|
||||||
|
reject(AditumError.SERVICE_NOT_AVAILABLE, AditumError.SERVICE_NOT_AVAILABLE_MESSAGE)
|
||||||
|
return@launch
|
||||||
|
}
|
||||||
|
|
||||||
|
paymentApplication.communicationService?.abortOperation()
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun pendingTransactions(resolve: (List<Charge?>?) -> Unit = {}, reject: (String, String?) -> Unit = { _, _ ->}) {
|
||||||
|
viewModelScope.launch {
|
||||||
|
try {
|
||||||
|
if (!paymentApplication.ensureServiceConnected()) {
|
||||||
|
reject(AditumError.SERVICE_NOT_AVAILABLE, AditumError.SERVICE_NOT_AVAILABLE_MESSAGE)
|
||||||
|
return@launch
|
||||||
|
}
|
||||||
|
|
||||||
|
val callback = object : PendingTransactionsCallback.Stub() {
|
||||||
|
override fun onResponse(results: List<Charge?>?) {
|
||||||
|
val json = gson.toJson(results)
|
||||||
|
Log.e(TAG, "onResponse - $json")
|
||||||
|
resolve(results)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
paymentApplication.communicationService?.pendingTransactions(callback)
|
||||||
|
?: reject(AditumError.SERVICE_NULL, AditumError.SERVICE_NULL_MESSAGE)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
reject(AditumError.CONFIRM_ERROR, "${AditumError.CONFIRM_ERROR_MESSAGE}: ${e.message}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun deactivate(resolve: (Boolean) -> Unit = {}, reject: (String, String?) -> Unit = { _, _ -> }) {
|
||||||
|
viewModelScope.launch {
|
||||||
|
try {
|
||||||
|
if (!paymentApplication.ensureServiceConnected()) {
|
||||||
|
reject(AditumError.SERVICE_NOT_AVAILABLE, AditumError.SERVICE_NOT_AVAILABLE_MESSAGE)
|
||||||
|
return@launch
|
||||||
|
}
|
||||||
|
|
||||||
|
val callback = object : DeactivationResponseCallback.Stub() {
|
||||||
|
override fun onResponse(status: Boolean) {
|
||||||
|
Log.d(TAG, "onDeactivationResponse - deactivationResponse: $status")
|
||||||
|
resolve(status)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
paymentApplication.communicationService?.deactivate(callback)
|
||||||
|
?: reject(AditumError.SERVICE_NULL, AditumError.SERVICE_NULL_MESSAGE)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
reject(AditumError.DEACTIVATE_ERROR, "${AditumError.DEACTIVATE_ERROR_MESSAGE}: ${e.message}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun provideFactory(
|
fun provideFactory(
|
||||||
paymentApplication: PaymentApplication,
|
paymentApplication: PaymentApplication,
|
||||||
context: Context,
|
context: Context
|
||||||
aditumSdkService: AditumSdkService
|
|
||||||
): ViewModelProvider.Factory = object : ViewModelProvider.Factory {
|
): ViewModelProvider.Factory = object : ViewModelProvider.Factory {
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
override fun <T : ViewModel> create(modelClass: Class<T>): T {
|
override fun <T : ViewModel> create(modelClass: Class<T>): T {
|
||||||
if (modelClass.isAssignableFrom(PaymentViewModel::class.java)) {
|
if (modelClass.isAssignableFrom(PaymentViewModel::class.java)) {
|
||||||
return PaymentViewModel(paymentApplication, context, aditumSdkService) as T
|
return PaymentViewModel(paymentApplication, context) as T
|
||||||
}
|
}
|
||||||
throw IllegalArgumentException("Unknown ViewModel class")
|
throw IllegalArgumentException("Unknown ViewModel class")
|
||||||
}
|
}
|
||||||
|
|||||||
@ -15,18 +15,17 @@ import androidx.compose.ui.unit.dp
|
|||||||
import br.com.aditum.data.v2.enums.PaymentType
|
import br.com.aditum.data.v2.enums.PaymentType
|
||||||
import com.example.mypos.BuildConfig
|
import com.example.mypos.BuildConfig
|
||||||
import com.example.mypos.models.PaymentViewModel
|
import com.example.mypos.models.PaymentViewModel
|
||||||
import com.example.mypos.services.AditumSdkService
|
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun PaymentScreen(viewModel: PaymentViewModel, aditumSdkService: AditumSdkService) {
|
fun PaymentScreen(viewModel: PaymentViewModel) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
var amount by remember { mutableStateOf("") }
|
var amount by remember { mutableStateOf("") }
|
||||||
var installments by remember { mutableStateOf("1") }
|
var installments by remember { mutableStateOf("1") }
|
||||||
var paymentType by remember { mutableStateOf(PaymentType.Credit) }
|
var paymentType by remember { mutableStateOf(PaymentType.Credit) }
|
||||||
val isServiceConnected by viewModel.isServiceConnected.collectAsState()
|
val isServiceConnected by viewModel.isServiceConnected.collectAsState()
|
||||||
val message by aditumSdkService.messageFlow.collectAsState()
|
val message by viewModel.messageFlow.collectAsState()
|
||||||
val pinLength by aditumSdkService.pinLengthFlow.collectAsState()
|
val pinLength by viewModel.pinLengthFlow.collectAsState()
|
||||||
var isInitializing by remember { mutableStateOf(false) }
|
var isInitializing by remember { mutableStateOf(false) }
|
||||||
var isPaying by remember { mutableStateOf(false) }
|
var isPaying by remember { mutableStateOf(false) }
|
||||||
var showLoading by remember { mutableStateOf(false) }
|
var showLoading by remember { mutableStateOf(false) }
|
||||||
@ -69,7 +68,7 @@ fun PaymentScreen(viewModel: PaymentViewModel, aditumSdkService: AditumSdkServic
|
|||||||
if (!isInitializing) {
|
if (!isInitializing) {
|
||||||
isInitializing = true
|
isInitializing = true
|
||||||
showLoading = true
|
showLoading = true
|
||||||
aditumSdkService.initAditumSdk(
|
viewModel.initAditumSdk(
|
||||||
applicationName = "MyPOS",
|
applicationName = "MyPOS",
|
||||||
applicationVersion = "1.0.0",
|
applicationVersion = "1.0.0",
|
||||||
activationCode = BuildConfig.ACTIVATION_CODE,
|
activationCode = BuildConfig.ACTIVATION_CODE,
|
||||||
@ -156,7 +155,7 @@ fun PaymentScreen(viewModel: PaymentViewModel, aditumSdkService: AditumSdkServic
|
|||||||
if (!isPaying) {
|
if (!isPaying) {
|
||||||
isPaying = true
|
isPaying = true
|
||||||
showLoading = true
|
showLoading = true
|
||||||
aditumSdkService.pay(
|
viewModel.pay(
|
||||||
amount = amountValue,
|
amount = amountValue,
|
||||||
installments = if (paymentType == PaymentType.Credit) installmentCount else null,
|
installments = if (paymentType == PaymentType.Credit) installmentCount else null,
|
||||||
paymentType = paymentType,
|
paymentType = paymentType,
|
||||||
|
|||||||
@ -1,331 +0,0 @@
|
|||||||
package com.example.mypos.services
|
|
||||||
|
|
||||||
import android.util.Log
|
|
||||||
import br.com.aditum.data.v2.IPaymentCallback
|
|
||||||
import br.com.aditum.data.v2.enums.AbecsCommands
|
|
||||||
import br.com.aditum.data.v2.enums.InstallmentType
|
|
||||||
import br.com.aditum.data.v2.enums.PayOperationType
|
|
||||||
import br.com.aditum.data.v2.enums.PaymentType
|
|
||||||
import br.com.aditum.data.v2.enums.TransactionStatus
|
|
||||||
import br.com.aditum.data.v2.model.Charge
|
|
||||||
import br.com.aditum.data.v2.model.MerchantData
|
|
||||||
import br.com.aditum.data.v2.model.PinpadMessages
|
|
||||||
import br.com.aditum.data.v2.model.callbacks.GetClearDataFinishedCallback
|
|
||||||
import br.com.aditum.data.v2.model.callbacks.GetClearDataRequest
|
|
||||||
import br.com.aditum.data.v2.model.callbacks.GetMenuSelectionFinishedCallback
|
|
||||||
import br.com.aditum.data.v2.model.callbacks.GetMenuSelectionRequest
|
|
||||||
import br.com.aditum.data.v2.model.cancelation.CancelationRequest
|
|
||||||
import br.com.aditum.data.v2.model.cancelation.CancelationResponse
|
|
||||||
import br.com.aditum.data.v2.model.cancelation.CancelationResponseCallback
|
|
||||||
import br.com.aditum.data.v2.model.deactivation.DeactivationResponseCallback
|
|
||||||
import br.com.aditum.data.v2.model.init.InitRequest
|
|
||||||
import br.com.aditum.data.v2.model.init.InitResponse
|
|
||||||
import br.com.aditum.data.v2.model.init.InitResponseCallback
|
|
||||||
import br.com.aditum.data.v2.model.payment.PaymentRequest
|
|
||||||
import br.com.aditum.data.v2.model.payment.PaymentResponse
|
|
||||||
import br.com.aditum.data.v2.model.payment.PaymentResponseCallback
|
|
||||||
import br.com.aditum.data.v2.model.transactions.ConfirmTransactionCallback
|
|
||||||
import br.com.aditum.data.v2.model.transactions.PendingTransactionsCallback
|
|
||||||
import com.example.mypos.BuildConfig
|
|
||||||
import com.example.mypos.data.AditumError
|
|
||||||
import com.example.mypos.data.PaymentRegister
|
|
||||||
import com.google.gson.Gson
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
|
|
||||||
class AditumSdkService(private val paymentApplication: PaymentApplication) {
|
|
||||||
companion object {
|
|
||||||
private const val TAG = "AditumSdkModule"
|
|
||||||
}
|
|
||||||
|
|
||||||
private val gson = Gson()
|
|
||||||
private val coroutineScope = CoroutineScope(Dispatchers.IO)
|
|
||||||
|
|
||||||
fun getName(): String = "AditumSdkModule"
|
|
||||||
|
|
||||||
private val _messageFlow = MutableStateFlow("")
|
|
||||||
val messageFlow: StateFlow<String> = _messageFlow.asStateFlow()
|
|
||||||
|
|
||||||
private val _pinLengthFlow = MutableStateFlow(0)
|
|
||||||
val pinLengthFlow: StateFlow<Int> = _pinLengthFlow.asStateFlow()
|
|
||||||
|
|
||||||
init {
|
|
||||||
coroutineScope.launch {
|
|
||||||
paymentApplication.communicationService?.registerPaymentCallback(paymentCallback)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val paymentCallback = object : IPaymentCallback.Stub() {
|
|
||||||
override fun notification(
|
|
||||||
message: String?,
|
|
||||||
transactionStatus: TransactionStatus?,
|
|
||||||
command: AbecsCommands?
|
|
||||||
) {
|
|
||||||
Log.i(TAG, "\nnotification - ${ message ?: "" }")
|
|
||||||
if (message != null) {
|
|
||||||
_messageFlow.value = message.replace("\\s+".toRegex(), " ").trim()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun pinNotification(message: String?, length: Int) {
|
|
||||||
_pinLengthFlow.value = length
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun startGetClearData(
|
|
||||||
clearDataRequest: GetClearDataRequest?,
|
|
||||||
finished: GetClearDataFinishedCallback?
|
|
||||||
) {
|
|
||||||
TODO("Not yet implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun startGetMenuSelection(
|
|
||||||
menuSelectionRequest: GetMenuSelectionRequest?,
|
|
||||||
finished: GetMenuSelectionFinishedCallback?
|
|
||||||
) {
|
|
||||||
TODO("Not yet implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun qrCodeGenerated(qrCode: String?, expirationTime: Int) {
|
|
||||||
Log.d(TAG, "\nqrCode - ${ qrCode ?: "" }\nexpirationTime - $expirationTime")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun initAditumSdk(
|
|
||||||
applicationName: String,
|
|
||||||
applicationVersion: String,
|
|
||||||
activationCode: String?,
|
|
||||||
resolve: (Boolean) -> Unit = {},
|
|
||||||
reject: (String, String?) -> Unit = { _, _ -> }
|
|
||||||
) {
|
|
||||||
coroutineScope.launch {
|
|
||||||
try {
|
|
||||||
if (!paymentApplication.ensureServiceConnected()) {
|
|
||||||
reject(AditumError.SERVICE_NOT_AVAILABLE, AditumError.SERVICE_NOT_AVAILABLE_MESSAGE)
|
|
||||||
return@launch
|
|
||||||
}
|
|
||||||
|
|
||||||
val pinpadMessages = PinpadMessages().apply {
|
|
||||||
mainMessage = applicationName
|
|
||||||
}
|
|
||||||
|
|
||||||
val initRequest = InitRequest().apply {
|
|
||||||
this.pinpadMessages = pinpadMessages
|
|
||||||
this.activationCode = if (BuildConfig.DEBUG) BuildConfig.ACTIVATION_CODE else activationCode
|
|
||||||
this.applicationName = applicationName
|
|
||||||
this.applicationVersion = applicationVersion
|
|
||||||
this.useOnlySdk = true
|
|
||||||
this.applicationToken = BuildConfig.APPLICATION_TOKEN
|
|
||||||
}
|
|
||||||
|
|
||||||
val callback = object : InitResponseCallback.Stub() {
|
|
||||||
override fun onResponse(initResponse: InitResponse?) {
|
|
||||||
if (initResponse != null) {
|
|
||||||
resolve(initResponse.initialized)
|
|
||||||
} else {
|
|
||||||
Log.e(TAG, "onResponse - initResponse is null")
|
|
||||||
reject(AditumError.INIT_RESPONSE_NULL, AditumError.INIT_RESPONSE_NULL_MESSAGE)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
paymentApplication.communicationService?.init(initRequest, callback)
|
|
||||||
?: reject(AditumError.SERVICE_NULL, AditumError.SERVICE_NULL_MESSAGE)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
reject(AditumError.INIT_ERROR, "${AditumError.INIT_ERROR_MESSAGE}: ${e.message}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getMerchantData(resolve: (MerchantData) -> Unit = {}, reject: (String, String?) -> Unit = { _, _ -> }) {
|
|
||||||
coroutineScope.launch {
|
|
||||||
try {
|
|
||||||
if (!paymentApplication.ensureServiceConnected()) {
|
|
||||||
reject(AditumError.SERVICE_NOT_AVAILABLE, AditumError.SERVICE_NOT_AVAILABLE_MESSAGE)
|
|
||||||
return@launch
|
|
||||||
}
|
|
||||||
|
|
||||||
val merchantData: MerchantData? = paymentApplication.communicationService?.merchantData
|
|
||||||
if (merchantData != null) {
|
|
||||||
resolve(merchantData)
|
|
||||||
} else {
|
|
||||||
reject(AditumError.MERCHANT_DATA_NULL, AditumError.MERCHANT_DATA_NULL_MESSAGE)
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
reject(AditumError.MERCHANT_DATA_ERROR, "${AditumError.MERCHANT_DATA_ERROR_MESSAGE}: ${e.message}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun pay(
|
|
||||||
amount: Long,
|
|
||||||
installments: Int? = null,
|
|
||||||
paymentType: PaymentType,
|
|
||||||
allowContactless: Boolean = true,
|
|
||||||
amountSeasoning: ((Long) -> Long)? = null,
|
|
||||||
resolve: (PaymentResponse) -> Unit = {},
|
|
||||||
reject: (String, String?) -> Unit = { _, _ -> }
|
|
||||||
) {
|
|
||||||
coroutineScope.launch {
|
|
||||||
try {
|
|
||||||
if (!paymentApplication.ensureServiceConnected()) {
|
|
||||||
reject(AditumError.SERVICE_NOT_AVAILABLE, AditumError.SERVICE_NOT_AVAILABLE_MESSAGE)
|
|
||||||
return@launch
|
|
||||||
}
|
|
||||||
|
|
||||||
val adjustedAmount = amountSeasoning?.invoke(amount) ?: amount
|
|
||||||
if (adjustedAmount <= 0) {
|
|
||||||
reject(AditumError.INVALID_AMOUNT, AditumError.INVALID_AMOUNT_MESSAGE)
|
|
||||||
return@launch
|
|
||||||
}
|
|
||||||
|
|
||||||
val paymentRequest = PaymentRequest().apply {
|
|
||||||
currency = 986
|
|
||||||
operationType = PayOperationType.Authorization
|
|
||||||
this.paymentType = paymentType
|
|
||||||
this.amount = amount
|
|
||||||
this.allowContactless = allowContactless
|
|
||||||
manualEntry = false
|
|
||||||
installmentType = if (paymentType == PaymentType.Debit) InstallmentType.Undefined else InstallmentType.Merchant
|
|
||||||
this.installmentNumber = when {
|
|
||||||
installments == null -> if (paymentType == PaymentType.Credit) 1 else 0
|
|
||||||
installments > 1 -> installments
|
|
||||||
paymentType == PaymentType.Debit -> 0
|
|
||||||
else -> 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val callback = object : PaymentResponseCallback.Stub() {
|
|
||||||
override fun onResponse(paymentResponse: PaymentResponse?) {
|
|
||||||
if (paymentResponse != null) {
|
|
||||||
val json = gson.toJson(paymentResponse)
|
|
||||||
Log.d(TAG, "onResponse - $json")
|
|
||||||
resolve(paymentResponse)
|
|
||||||
} else {
|
|
||||||
Log.e(TAG, "onResponse - paymentResponse is null")
|
|
||||||
reject(AditumError.PAYMENT_RESPONSE_NULL, AditumError.PAYMENT_RESPONSE_NULL_MESSAGE)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
paymentApplication.communicationService?.pay(paymentRequest, callback)
|
|
||||||
?: reject(AditumError.SERVICE_NULL, AditumError.SERVICE_NULL_MESSAGE)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
reject(AditumError.PAYMENT_ERROR, "${AditumError.PAYMENT_ERROR_MESSAGE}: ${e.message}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun confirm(nsu: String, resolve: (Boolean) -> Unit = {}, reject: (String, String?) -> Unit = { _, _ -> }) {
|
|
||||||
coroutineScope.launch {
|
|
||||||
try {
|
|
||||||
if (!paymentApplication.ensureServiceConnected()) {
|
|
||||||
reject(AditumError.SERVICE_NOT_AVAILABLE, AditumError.SERVICE_NOT_AVAILABLE_MESSAGE)
|
|
||||||
return@launch
|
|
||||||
}
|
|
||||||
|
|
||||||
val callback = object : ConfirmTransactionCallback.Stub() {
|
|
||||||
override fun onResponse(confirmed: Boolean) {
|
|
||||||
resolve(confirmed)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
paymentApplication.communicationService?.confirmTransaction(nsu, callback)
|
|
||||||
?: reject(AditumError.SERVICE_NULL, AditumError.SERVICE_NULL_MESSAGE)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
reject(AditumError.CONFIRM_ERROR, "${AditumError.CONFIRM_ERROR_MESSAGE}: ${e.message}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun cancel(nsu: String, isReversal: Boolean, resolve: (Boolean) -> Unit = {}, reject: (String, String?) -> Unit = { _, _ -> }) {
|
|
||||||
coroutineScope.launch {
|
|
||||||
try {
|
|
||||||
if (!paymentApplication.ensureServiceConnected()) {
|
|
||||||
reject(AditumError.SERVICE_NOT_AVAILABLE, AditumError.SERVICE_NOT_AVAILABLE_MESSAGE)
|
|
||||||
return@launch
|
|
||||||
}
|
|
||||||
|
|
||||||
val cancelationRequest = CancelationRequest(nsu, isReversal)
|
|
||||||
|
|
||||||
val callback = object : CancelationResponseCallback.Stub() {
|
|
||||||
override fun onResponse(cancelationResponse: CancelationResponse?) {
|
|
||||||
if (cancelationResponse != null) {
|
|
||||||
resolve(cancelationResponse.canceled)
|
|
||||||
} else {
|
|
||||||
Log.e(TAG, "onResponse - cancelationResponse is null")
|
|
||||||
reject(AditumError.CANCEL_RESPONSE_NULL, AditumError.CANCEL_RESPONSE_NULL_MESSAGE)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
paymentApplication.communicationService?.cancel(cancelationRequest, callback)
|
|
||||||
?: reject(AditumError.SERVICE_NULL, AditumError.SERVICE_NULL_MESSAGE)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
reject(AditumError.CANCEL_ERROR, "${AditumError.CANCEL_ERROR_MESSAGE}: ${e.message}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun aboutOperation(resolve: (Boolean) -> Unit = {}, reject: (String, String?) -> Unit = { _, _ -> }) {
|
|
||||||
coroutineScope.launch {
|
|
||||||
if (!paymentApplication.ensureServiceConnected()) {
|
|
||||||
reject(AditumError.SERVICE_NOT_AVAILABLE, AditumError.SERVICE_NOT_AVAILABLE_MESSAGE)
|
|
||||||
return@launch
|
|
||||||
}
|
|
||||||
|
|
||||||
paymentApplication.communicationService?.abortOperation()
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun pendingTransactions(resolve: (List<Charge?>?) -> Unit = {}, reject: (String, String?) -> Unit = { _, _ ->}) {
|
|
||||||
coroutineScope.launch {
|
|
||||||
try {
|
|
||||||
if (!paymentApplication.ensureServiceConnected()) {
|
|
||||||
reject(AditumError.SERVICE_NOT_AVAILABLE, AditumError.SERVICE_NOT_AVAILABLE_MESSAGE)
|
|
||||||
return@launch
|
|
||||||
}
|
|
||||||
|
|
||||||
val callback = object : PendingTransactionsCallback.Stub() {
|
|
||||||
override fun onResponse(results: List<Charge?>?) {
|
|
||||||
val json = gson.toJson(results)
|
|
||||||
Log.e(TAG, "onResponse - $json")
|
|
||||||
resolve(results)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
paymentApplication.communicationService?.pendingTransactions(callback)
|
|
||||||
?: reject(AditumError.SERVICE_NULL, AditumError.SERVICE_NULL_MESSAGE)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
reject(AditumError.CONFIRM_ERROR, "${AditumError.CONFIRM_ERROR_MESSAGE}: ${e.message}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun deactivate(resolve: (Boolean) -> Unit = {}, reject: (String, String?) -> Unit = { _, _ -> }) {
|
|
||||||
coroutineScope.launch {
|
|
||||||
try {
|
|
||||||
if (!paymentApplication.ensureServiceConnected()) {
|
|
||||||
reject(AditumError.SERVICE_NOT_AVAILABLE, AditumError.SERVICE_NOT_AVAILABLE_MESSAGE)
|
|
||||||
return@launch
|
|
||||||
}
|
|
||||||
|
|
||||||
val callback = object : DeactivationResponseCallback.Stub() {
|
|
||||||
override fun onResponse(status: Boolean) {
|
|
||||||
Log.d(TAG, "onDeactivationResponse - deactivationResponse: $status")
|
|
||||||
resolve(status)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
paymentApplication.communicationService?.deactivate(callback)
|
|
||||||
?: reject(AditumError.SERVICE_NULL, AditumError.SERVICE_NULL_MESSAGE)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
reject(AditumError.DEACTIVATE_ERROR, "${AditumError.DEACTIVATE_ERROR_MESSAGE}: ${e.message}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loading…
x
Reference in New Issue
Block a user