Skip to content

Commit

Permalink
PX-926 Release 3.0.1 (#13)
Browse files Browse the repository at this point in the history
  • Loading branch information
tl-keenen-charles authored Feb 23, 2024
1 parent b4d98ef commit cf6167c
Show file tree
Hide file tree
Showing 15 changed files with 73 additions and 263 deletions.
4 changes: 2 additions & 2 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -66,10 +66,10 @@ dependencies {
implementation "androidx.compose.material3:material3:$material3_compose_version"

implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.okhttp3:logging-interceptor:4.11.0'
implementation 'com.squareup.okhttp3:logging-interceptor:4.12.0'

implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:$json_serialization_version"
implementation "com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter:$retrofit2_kotlinx_serialization"

implementation "com.truelayer.payments:ui:2.6.0"
implementation "com.truelayer.payments:ui:3.0.1"
}
1 change: 1 addition & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
</activity>
<activity
android:name=".payments.PaymentStatusActivity"
android:theme="@style/Theme.SDKDemo.NoActionBar"
android:parentActivityName=".MainActivity"
android:exported="true">
<intent-filter android:label="TrueLayer Payment Status">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ class ActivityIntegrationActivity : Activity() {
private suspend fun launchPaymentFlow() {
val paymentType = PrefUtils.getPaymentType(this)
// Create a payment context
when (val processorContext = processorContextProvider.getProcessorContext(paymentType)) {
when (val processorContext = processorContextProvider.getProcessorContext(paymentType, this)) {
is Ok -> {
// Create an intent with the payment context to start the payment flow
val intent = ProcessorActivityContract().createIntent(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ class ActivityXIntegrationActivity : AppCompatActivity() {
private suspend fun launchFlow(flow: ActivityResultLauncher<ProcessorContext>) {
val paymentType = PrefUtils.getPaymentType(this)
// Create a payment context
when (val processorContext = processorContextProvider.getProcessorContext(paymentType)) {
when (val processorContext = processorContextProvider.getProcessorContext(paymentType, this)) {
is Ok -> {
// Start the payment flow
flow.launch(processorContext.value)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ class ComposeIntegrationActivity : AppCompatActivity() {
var processorContext by remember { mutableStateOf<ProcessorContext?>(null) }
var error by remember { mutableStateOf<String?>(null) }
LaunchedEffect(true) {
processorContextProvider.getProcessorContext(paymentType)
processorContextProvider.getProcessorContext(paymentType, this@ComposeIntegrationActivity)
.onOk { processorContext = it }
.onError { error = it.localizedMessage }
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,16 +63,16 @@ protected void onCreate(Bundle savedInstanceState) {
private void launchFlow(ActivityResultLauncher<ProcessorContext> flow) {
PaymentType paymentType = PrefUtils.getPaymentType(this);
// Create a payment context
processorContextProvider.getProcessorContext(paymentType, contextOutcome -> {
if(contextOutcome instanceof Ok) {
processorContextProvider.getProcessorContext(paymentType, this, outcome -> {
if(outcome instanceof Ok) {
// Start the payment flow
flow.launch(((Ok<ProcessorContext>) contextOutcome).getValue());
flow.launch(((Ok<ProcessorContext>) outcome).getValue());
}
else if(contextOutcome instanceof Fail) {
else if(outcome instanceof Fail) {
// Display error if payment context creation failed
Toast.makeText(
this,
getString(R.string.processor_context_error, ((Fail<?>) contextOutcome).getError()),
getString(R.string.processor_context_error, ((Fail<?>) outcome).getError()),
Toast.LENGTH_LONG
).show();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ import androidx.compose.ui.unit.dp
/**
* UI component to represent an integration of the SDK and launch it's respective Activity
*/
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun ImplementationItem(
name: String,
Expand Down

This file was deleted.

110 changes: 20 additions & 90 deletions app/src/main/java/com/truelayer/demo/payments/PaymentStatusActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,34 +3,13 @@ package com.truelayer.demo.payments
import android.os.Bundle
import androidx.activity.compose.setContent
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.CheckCircle
import androidx.compose.material.icons.filled.Error
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import com.truelayer.demo.R
import com.truelayer.demo.payments.api.PaymentStatus
import androidx.compose.material3.TextButton
import com.truelayer.demo.utils.PrefUtils
import com.truelayer.payments.core.utils.extractTrueLayerRedirectParams
import com.truelayer.payments.ui.screens.processor.ResultProcessor
import com.truelayer.payments.ui.screens.processor.ResultProcessorContext
import com.truelayer.payments.ui.screens.processor.Processor
import com.truelayer.payments.ui.theme.Theme
import com.truelayer.payments.ui.theme.stackNavigation

Expand All @@ -44,10 +23,10 @@ class PaymentStatusActivity : AppCompatActivity() {

// Extract the payment/mandate parameters from the URL
val params = intent.data.extractTrueLayerRedirectParams()
val processorContext = PrefUtils.getProcessorContext(this)

setContent {
val paymentId = params["payment_id"]
val mandateId = params["mandate_id"]
val resourceId = params["payment_id"] ?: params["mandate_id"]

val activity = this

Expand All @@ -56,79 +35,30 @@ class PaymentStatusActivity : AppCompatActivity() {
stackNavigation(current, transition, direction)
}
) {
if (paymentId != null) {
// If this activity is launched at the end of a payment creation flow then you can use
// the payment result screen to fetch and display the result
ResultProcessor(
resultContext = ResultProcessorContext(paymentId),
if(resourceId != null && processorContext != null && processorContext.id == resourceId) {
// Display the payment result screen or handle any subsequent actions
Processor(
context = processorContext,
onSuccess = {
activity.finish()
},
onFailure = {
activity.finish()
}
)
} else if (mandateId != null) {
// If this activity is launched at the end of a mandate flow then you will need to manually
// fetch and display the status
MandateStatus(mandateId)
}
}
}
}

@Composable
fun MandateStatus(mandateId: String) {
val viewModel = viewModel<MandateStatusViewModel>(
factory = mandateStatusViewModel(mandateId, PrefUtils.getQuickstartUrl(this))
)
val status by viewModel.status.collectAsState()
val error by viewModel.error.collectAsState()

// Start polling for mandate status updates
viewModel.pollMandateStatus()

Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(16.dp),
modifier = Modifier.padding(horizontal = 16.dp)
) {
Text(text = stringResource(id = R.string.status_title), style = MaterialTheme.typography.titleMedium)
when (status) {
PaymentStatus.Status.AUTHORIZING -> {
// If the SDK has done it's work already, it will be ok
// to wait for status change
CircularProgressIndicator()
}
PaymentStatus.Status.AUTHORIZATION_REQUIRED -> {
// If you encounter this state, it's most likely that the SDK didn't
// get chance to do its work yet. Start the CoordinatorFlow.
// If the SDK has done its work, then this state would be considered
// an error.
// Because we are using this view to query the state of the payment
// after redirect from the bank this should never happen.
Image(imageVector = Icons.Filled.Error, contentDescription = null)
}
PaymentStatus.Status.AUTHORIZED,
PaymentStatus.Status.SETTLED,
PaymentStatus.Status.EXECUTED -> {
Image(
imageVector = Icons.Filled.CheckCircle,
colorFilter = ColorFilter.tint(Color.Green),
contentDescription = null
)
}
PaymentStatus.Status.FAILED -> {
Image(imageVector = Icons.Filled.Error, contentDescription = null)
}
else {
AlertDialog(
title = { Text(text = "Whoops", style = MaterialTheme.typography.titleMedium) },
text = { Text(text = "Error getting payment result") },
confirmButton = {
TextButton(onClick = { activity.finish() }) {
Text("Close")
}
},
onDismissRequest = { activity.finish() }
)
}

Text(text = status.toString(), style = MaterialTheme.typography.bodyLarge)
Text(text = error, color = Color.Red)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
package com.truelayer.demo.payments

import android.content.Context
import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory
import com.truelayer.demo.payments.api.PaymentRequest
import com.truelayer.demo.payments.api.PaymentService
import com.truelayer.demo.payments.api.PaymentStatus
import com.truelayer.demo.utils.PrefUtils
import com.truelayer.payments.core.domain.utils.Fail
import com.truelayer.payments.core.domain.utils.Ok
import com.truelayer.payments.core.domain.utils.Outcome
Expand All @@ -14,7 +15,6 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.json.Json
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
Expand Down Expand Up @@ -42,7 +42,7 @@ class ProcessorContextProvider(
}

// Generates a payment context to be used for testing integrations
suspend fun getProcessorContext(paymentType: PaymentType): Outcome<ProcessorContext, Throwable> {
suspend fun getProcessorContext(paymentType: PaymentType, context: Context): Outcome<ProcessorContext, Throwable> {
val service = createPaymentService()
val paymentRequest = createRequest()

Expand All @@ -56,54 +56,31 @@ class ProcessorContextProvider(
PaymentType.MANDATE -> service.createMandate(paymentRequest)
}

val context = if (paymentType == PaymentType.MANDATE) {
val processorContext = if (paymentType == PaymentType.MANDATE) {
ProcessorContext.MandateContext(payment.id, payment.resourceToken, redirectUri)
} else {
PaymentContext(payment.id, payment.resourceToken, redirectUri)
}

Ok(context)
PrefUtils.setProcessorContext(processorContext, context)

Ok(processorContext)
} catch (e: Exception) {
Fail(e)
}
}

// Generates a payment context to be used for testing integrations and returns results with lambda
@OptIn(DelicateCoroutinesApi::class)
fun getProcessorContext(paymentType: PaymentType, callback: (Outcome<ProcessorContext, Throwable>) -> Unit) {
fun getProcessorContext(paymentType: PaymentType, context: Context, callback: (Outcome<ProcessorContext, Throwable>) -> Unit) {
GlobalScope.launch {
withContext(Dispatchers.IO) {
callback(getProcessorContext(paymentType))
callback(getProcessorContext(paymentType, context))
}
}
}

// Gets the status of the payment specified by the ID
suspend fun getPaymentStatus(paymentId: String): Outcome<PaymentStatus, Throwable> {
val service = createPaymentService()

return try {
val paymentStatus = service.getPaymentStatus(paymentId)
Ok(paymentStatus)
} catch (e: Exception) {
Fail(e)
}
}

// Gets the status of the mandate specified by the ID
suspend fun getMandateStatus(mandateId: String): Outcome<PaymentStatus, Throwable> {
val service = createPaymentService()

return try {
val paymentStatus = service.getMandateStatus(mandateId)
Ok(paymentStatus)
} catch (e: Exception) {
Fail(e)
}
}

// Creates a Retrofit service for the Payments Quickstart API
@OptIn(ExperimentalSerializationApi::class)
private fun createPaymentService(): PaymentService {
val interceptor = HttpLoggingInterceptor()
.setLevel(HttpLoggingInterceptor.Level.BODY)
Expand Down
Loading

0 comments on commit cf6167c

Please sign in to comment.