Skip to content

Commit

Permalink
Feature: Payment completion
Browse files Browse the repository at this point in the history
  • Loading branch information
jdsdhp committed Jan 28, 2024
1 parent 12f0737 commit 71e6546
Show file tree
Hide file tree
Showing 12 changed files with 196 additions and 86 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -66,49 +66,6 @@ internal fun MainScreen(
.verticalScroll(rememberScrollState()),
horizontalAlignment = Alignment.CenterHorizontally,
) {
Spacer(modifier = Modifier.height(16.dp))

Text(
text = stringResource(R.string.payment_details),
style = MaterialTheme.typography.titleLarge,
)

Spacer(modifier = Modifier.height(16.dp))

Card(modifier = Modifier.fillMaxWidth()) {
Column(modifier = Modifier.padding(8.dp)) {
uiState.payment?.let { payment ->
Text(
text = stringResource(
R.string.transaction_uuid_dots,
payment.transactionUuid
)
)
Text(text = stringResource(R.string.status_code_dots, payment.statusCode))
Text(text = stringResource(R.string.status_name_dots, payment.statusName))
Text(text = stringResource(R.string.description_dots, payment.description))
Text(text = stringResource(R.string.currency_dots, payment.currency))
Text(text = stringResource(R.string.created_at_dots, payment.createdAt))
Text(text = stringResource(R.string.updated_at_dots, payment.updatedAt))
Text(text = stringResource(R.string.total_price_dots, payment.totalPrice))
payment.links.forEach {
Spacer(modifier = Modifier.height(4.dp))
Text(text = "${it.rel} - ${it.method} - ${it.href}")
}
Spacer(modifier = Modifier.height(4.dp))
}
}
}

Spacer(modifier = Modifier.height(16.dp))

Button(onClick = { viewModel.onGetPaymentDetailClick() }) {
Text(text = stringResource(R.string.get_payment_details))
}

Spacer(modifier = Modifier.height(16.dp))

HorizontalDivider()

Spacer(modifier = Modifier.height(16.dp))

Expand Down Expand Up @@ -284,6 +241,52 @@ internal fun MainScreen(

Spacer(modifier = Modifier.height(16.dp))

HorizontalDivider()

Spacer(modifier = Modifier.height(16.dp))

Text(
text = stringResource(R.string.payment_details),
style = MaterialTheme.typography.titleLarge,
)

Spacer(modifier = Modifier.height(16.dp))

Card(modifier = Modifier.fillMaxWidth()) {
Column(modifier = Modifier.padding(8.dp)) {
uiState.payment?.let { payment ->
Text(
text = stringResource(
R.string.transaction_uuid_dots,
payment.transactionUuid
)
)
Text(text = stringResource(R.string.status_code_dots, payment.statusCode))
Text(text = stringResource(R.string.status_name_dots, payment.statusName))
Text(text = stringResource(R.string.description_dots, payment.description))
Text(text = stringResource(R.string.currency_dots, payment.currency))
Text(text = stringResource(R.string.created_at_dots, payment.createdAt))
Text(text = stringResource(R.string.updated_at_dots, payment.updatedAt))
Text(text = stringResource(R.string.total_price_dots, payment.totalPrice))
payment.links.forEach {
Spacer(modifier = Modifier.height(4.dp))
Text(text = "${it.rel} - ${it.method} - ${it.href}")
}
Spacer(modifier = Modifier.height(4.dp))
}
}
}

Spacer(modifier = Modifier.height(16.dp))

Button(onClick = { viewModel.onGetPaymentDetailClick() }) {
Text(text = stringResource(R.string.get_payment_details))
}

Spacer(modifier = Modifier.height(16.dp))



HorizontalDivider()

Spacer(modifier = Modifier.height(16.dp))
Expand All @@ -301,6 +304,23 @@ internal fun MainScreen(

Spacer(modifier = Modifier.height(16.dp))

HorizontalDivider()

Spacer(modifier = Modifier.height(16.dp))

Text(
text = stringResource(R.string.complete_payment),
style = MaterialTheme.typography.titleLarge,
)

Spacer(modifier = Modifier.height(16.dp))

Button(onClick = { viewModel.onCompletePaymentClick() }) {
Text(text = stringResource(R.string.complete_payment))
}

Spacer(modifier = Modifier.height(16.dp))

}

if (uiState.isLoading) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@ class MainViewModel @Inject constructor(private val enzona: Enzona) : ViewModel(
val isLoading: Boolean = false,
val error: String? = null,
val textMessage: String? = null,
val consumerKey: String = "81go6n9Vz1gcSL2nK5mfZxJzDRwa",
val consumerSecret: String = "MLZucMGr3z9k5kqp49jvwJCrWU4a",
val merchantUUID: String = "2a5d8dfd49794387b408d2168a461da5",
val consumerKey: String = "",
val consumerSecret: String = "",
val merchantUUID: String = "",
val items: List<Item> = emptyList(),
val createPayment: CreatePayment = CreatePayment(),
val payment: Payment? = null,
Expand Down Expand Up @@ -56,7 +56,7 @@ class MainViewModel @Inject constructor(private val enzona: Enzona) : ViewModel(
_uiState.update {
it.copy(
items = listOf(
Item(
/*Item(
quantity = 2,
name = "Mango",
description = "Product One Description",
Expand All @@ -69,7 +69,7 @@ class MainViewModel @Inject constructor(private val enzona: Enzona) : ViewModel(
description = "Product Two Description",
price = 2.0,
tax = 2.0,
),
),*/
Item(
quantity = 1,
name = "Guava",
Expand Down Expand Up @@ -308,18 +308,20 @@ class MainViewModel @Inject constructor(private val enzona: Enzona) : ViewModel(
transactionUuid = _uiState.value.payment?.transactionUuid ?: "",
)) {
is ResultValue.Success -> {
Log.d(TAG, "onGePaymentDetailClick: Success = $res")
Log.d(TAG, "onGetPaymentDetailClick: Success = $res")
_uiState.update {
it.copy(
payment = res.data.copy(links = res.data.links),
payment = res.data.copy(
links = _uiState.value.payment?.links ?: emptyList()
),
isLoading = false,
textMessage = "Payment details updated!",
)
}
}

is ResultValue.Error -> {
Log.d(TAG, "onGePaymentDetailClick: Error = $res")
Log.d(TAG, "onGetPaymentDetailClick: Error = $res")
_uiState.update {
it.copy(
isLoading = false,
Expand Down Expand Up @@ -365,4 +367,36 @@ class MainViewModel @Inject constructor(private val enzona: Enzona) : ViewModel(
}
}

fun onCompletePaymentClick() {
viewModelScope.launch {
_uiState.update { it.copy(isLoading = true, textMessage = null, error = null) }
when (val res = enzona.completePayment(
transactionUuid = _uiState.value.payment?.transactionUuid ?: "",
)) {
is ResultValue.Success -> {
Log.d(TAG, "onCompletePaymentClick: Success = $res")
_uiState.update {
it.copy(
payment = res.data.copy(
links = _uiState.value.payment?.links ?: emptyList()
),
isLoading = false,
textMessage = "Payment completed successfully!",
)
}
}

is ResultValue.Error -> {
Log.d(TAG, "onCompletePaymentClick: Error = $res")
_uiState.update {
it.copy(
isLoading = false,
error = res.exception.toString(),
)
}
}
}
}
}

}
1 change: 1 addition & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,5 @@
<string name="buyer_identity_code_optional">Buyer Identity Code (Optional)</string>
<string name="terminal_id_optional">Terminal Id (Optional)</string>
<string name="tax_dots">Tax: %1$s</string>
<string name="complete_payment">Complete Payment</string>
</resources>
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ interface Enzona {
enum class ApiUrl(val url: String) {
OFFICIAL(url = "https://api.enzona.net"),
SANDBOX(url = "https://apisandbox.enzona.net"),
OFFICIAL_PAYMENT_ENDPOINT("payment/v1.0.0/payments")
}

/**
Expand Down Expand Up @@ -85,4 +84,11 @@ interface Enzona {
*/
suspend fun cancelPayment(transactionUuid: String): ResultValue<CancelStatus>

/**
* Suspend function to complete a payment using transaction UUID.
* @param transactionUuid The UUID of the transaction to be completed.
* @return ResultValue containing the payment information after completion.
*/
suspend fun completePayment(transactionUuid: String): ResultValue<Payment>

}
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ internal class EnzonaImpl @Inject constructor(

override suspend fun authenticate(): ResultValue<Token> =
authRemoteDatasource.authenticate(
apiUrl = apiUrl,
consumerKey = consumerKey,
consumerSecret = consumerSecret,
).also {
Expand All @@ -59,6 +60,7 @@ internal class EnzonaImpl @Inject constructor(
terminalId: String,
items: List<Item>,
): ResultValue<Payment> = paymentRemoteDatasource.createPayment(
apiUrl = apiUrl,
token = token?.accessToken ?: "",
discount = discount,
shipping = shipping,
Expand All @@ -77,12 +79,21 @@ internal class EnzonaImpl @Inject constructor(

override suspend fun getPaymentDetails(transactionUuid: String): ResultValue<Payment> =
paymentRemoteDatasource.getPaymentDetails(
apiUrl = apiUrl,
token = token?.accessToken ?: "",
transactionUuid = transactionUuid,
)

override suspend fun cancelPayment(transactionUuid: String): ResultValue<CancelStatus> =
paymentRemoteDatasource.cancelPayment(
apiUrl = apiUrl,
token = token?.accessToken ?: "",
transactionUuid = transactionUuid,
)

override suspend fun completePayment(transactionUuid: String): ResultValue<Payment> =
paymentRemoteDatasource.completePayment(
apiUrl = apiUrl,
token = token?.accessToken ?: "",
transactionUuid = transactionUuid,
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,14 @@ internal class AuthRemoteDatasourceImpl @Inject constructor(

@OptIn(ExperimentalEncodingApi::class)
override suspend fun authenticate(
apiUrl: Enzona.ApiUrl,
consumerKey: String,
consumerSecret: String,
): ResultValue<Token> = withContext(dispatcher) {
remoteDatasource.call {
val authHeader = "Basic ${Base64.encode("$consumerKey:$consumerSecret".toByteArray())}"
val res = okHttpClient.post(
fullUrl = "${Enzona.ApiUrl.OFFICIAL.url}/token",
fullUrl = "${apiUrl.url}/token",
mediaTypeContent = "application/x-www-form-urlencoded",
content = "grant_type=client_credentials&scope=${Scope.ENZONA_BUSINESS_PAYMENT.label}",
headers = mapOf("Authorization" to authHeader),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import com.github.jdsdhp.enzona.payment.embedded.data.datasource.remote.datasour
import com.github.jdsdhp.enzona.payment.embedded.data.datasource.remote.dto.request.AmountDto
import com.github.jdsdhp.enzona.payment.embedded.data.datasource.remote.dto.request.DetailsDto
import com.github.jdsdhp.enzona.payment.embedded.data.datasource.remote.dto.request.create.CreatePaymentDto
import com.github.jdsdhp.enzona.payment.embedded.data.datasource.remote.dto.response.PaymentResponseDto
import com.github.jdsdhp.enzona.payment.embedded.data.datasource.remote.dto.response.cancel.CancelResponseDto
import com.github.jdsdhp.enzona.payment.embedded.data.datasource.remote.dto.response.create.PaymentResponseDto
import com.github.jdsdhp.enzona.payment.embedded.data.datasource.remote.mapper.asData
import com.github.jdsdhp.enzona.payment.embedded.data.datasource.remote.mapper.asDomain
import com.github.jdsdhp.enzona.payment.embedded.di.IoDispatcher
Expand All @@ -29,9 +29,20 @@ internal class PaymentRemoteDatasourceImpl @Inject constructor(
private val remoteDatasource: RemoteDatasource,
) : PaymentRemoteDatasource {

/**
* Enumeration representing different API endpoints.
*/
private enum class Endpoint(val path: String) {
PAYMENTS("payment/v1.0.0/payments")
}

/**
* Gson instance for JSON serialization and deserialization.
*/
private val gson: Gson = Gson()

override suspend fun createPayment(
apiUrl: Enzona.ApiUrl,
token: String,
discount: Double,
shipping: Double,
Expand Down Expand Up @@ -79,7 +90,7 @@ internal class PaymentRemoteDatasourceImpl @Inject constructor(
terminalId = terminalId,
)
val res = okHttpClient.post(
fullUrl = "${Enzona.ApiUrl.OFFICIAL.url}/${Enzona.ApiUrl.OFFICIAL_PAYMENT_ENDPOINT.url}",
fullUrl = "${apiUrl.url}/${Endpoint.PAYMENTS.path}",
mediaTypeContent = "application/json",
content = gson.toJson(createPayment),
headers = mapOf("Authorization" to "Bearer $token"),
Expand All @@ -90,25 +101,27 @@ internal class PaymentRemoteDatasourceImpl @Inject constructor(
}

override suspend fun getPaymentDetails(
apiUrl: Enzona.ApiUrl,
token: String,
transactionUuid: String,
): ResultValue<Payment> = withContext(dispatcher) {
remoteDatasource.call {
val res = okHttpClient.get(
fullUrl = "${Enzona.ApiUrl.OFFICIAL.url}/${Enzona.ApiUrl.OFFICIAL_PAYMENT_ENDPOINT.url}/$transactionUuid",
fullUrl = "${apiUrl.url}/${Endpoint.PAYMENTS.path}/$transactionUuid",
headers = mapOf("Authorization" to "Bearer $token"),
)
gson.fromJson(res.body?.string(), PaymentResponseDto::class.java).asDomain()
}
}

override suspend fun cancelPayment(
apiUrl: Enzona.ApiUrl,
token: String,
transactionUuid: String,
): ResultValue<CancelStatus> = withContext(dispatcher) {
remoteDatasource.call {
val res = okHttpClient.post(
fullUrl = "${Enzona.ApiUrl.OFFICIAL.url}/${Enzona.ApiUrl.OFFICIAL_PAYMENT_ENDPOINT.url}/$transactionUuid/cancel",
fullUrl = "${apiUrl.url}/${Endpoint.PAYMENTS.path}/$transactionUuid/cancel",
mediaTypeContent = "application/json",
content = "",
headers = mapOf("Authorization" to "Bearer $token"),
Expand All @@ -117,4 +130,20 @@ internal class PaymentRemoteDatasourceImpl @Inject constructor(
}
}

override suspend fun completePayment(
apiUrl: Enzona.ApiUrl,
token: String,
transactionUuid: String,
): ResultValue<Payment> = withContext(dispatcher) {
remoteDatasource.call {
val res = okHttpClient.post(
fullUrl = "${apiUrl.url}/${Endpoint.PAYMENTS.path}/$transactionUuid/complete",
mediaTypeContent = "application/json",
content = "",
headers = mapOf("Authorization" to "Bearer $token"),
)
gson.fromJson(res.body?.string(), PaymentResponseDto::class.java).asDomain()
}
}

}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.github.jdsdhp.enzona.payment.embedded.data.datasource.remote.dto.response.create
package com.github.jdsdhp.enzona.payment.embedded.data.datasource.remote.dto.response

import androidx.annotation.Keep
import com.github.jdsdhp.enzona.payment.embedded.data.datasource.remote.dto.response.AmountResponseDto
Expand Down
Loading

0 comments on commit 71e6546

Please sign in to comment.