jean
03/23/2023, 11:51 AMfun processPayment(price: Int) = flow {
emit(PaymentStatus.Working)
either {
val qrCode = getQrCode(price).bind()
emit(PaymentStatus.ShowQrCode(qrCode.url))
try {
withTimeout(twoMinutes()) {
var paymentIsApproved = false
while (!paymentIsApproved) {
val paymentStatus = getPaymentFromApi(qrCode.reference).bind()
paymentIsApproved = paymentStatus.aggregate.authorizedAmount.value == price.toLong()
delay(2000)
}
emit(PaymentStatus.Success)
}
} catch (e: Exception) {
emit(paymentError(PaymentError.CouldNotVerifyPaymentBeforeTimeout))
}
}.mapLeft(::paymentError)
}
Alejandro Serrano Mena
03/23/2023, 1:31 PMfun processPayment(price: Int) = flow {
emit(PaymentStatus.Working)
either {
val qrCode = getQrCode(price).bind()
emit(PaymentStatus.ShowQrCode(qrCode.url))
Schedule.spaced(2.seconds).whileOutput { it < 2.minutes }.retryOrElse {
fa = {
val paymentStatus = getPaymentFromApi(qrCode.reference).bind()
val paymentIsApproved = paymentStatus.aggregate.authorizedAmount.value == price.toLong()
if (paymentIsApproved) emit(PaymentStatus.Success)
},
orElse = {
emit(paymentError(PaymentError.CouldNotVerifyPaymentBeforeTimeout))
}
}
}.mapLeft(::paymentError)
}
Francis Reynders
03/23/2023, 3:26 PMflow
lambda argument type returns Unit
so I don't think the Either value is returned. Makes me wonder what is the best way to capture the getQrCode logic failure. Maybe define processPayment with Raise context or extension?jean
03/27/2023, 1:02 PMsimon.vergauwen
03/27/2023, 1:03 PMSchedule
is indeed nicer instead of manually implementing the Schedule
, but Francis is also correct in that the `Eitherem`` value is never returned or rather emitted.jean
03/27/2023, 1:05 PMelse
branch after if(paymentApproved)
to throw a Throwable
? Otherwise it would not retry, right?retryOrElse
is Any
not Either<PaymentError, PaymentStatus>
simon.vergauwen
03/27/2023, 1:15 PMPaymentError
and PaymentStatus
share a common parent?jean
03/27/2023, 1:22 PMPaymentStatus
is a sealed class with Working
, Success
and Error
as sub-elements. Error
takes a PaymentError
as parameter. It can be Timeout
or whatever comes from the bind()
simon.vergauwen
03/27/2023, 1:23 PMFlow<PaymentStatus>
?jean
03/27/2023, 1:24 PMflow
{ ... } That contains the schedule logicsimon.vergauwen
03/27/2023, 1:38 PMFlow
?jean
03/27/2023, 5:35 PMflow {
emit(PaymentStatus.Working) // the process start, I emit "Working", the ui can show a spinner for example
val result = Schedule... // Here I want to poll my endpoint until I get the expected response, then I emit that final "Success" or "Error" if the IO requests return 4xx, 5xx or if I get a timeout
emit(result)
}
simon.vergauwen
03/27/2023, 6:00 PMThrowable
there. So basically repeat polling until you reach Either.Right
with the desired value.
Schedule
.spaced<Either<VippsError, PaymentStatus>>(twoSeconds)
.whileOutput { it < twoMinutes }
.whileInput {
it.map { it.aggregate.authorizedAmount.value != price.toLong() }
.getOrElse { true }
}
.zipRight(Schedule.identity())
.repeatOrElseEither(
fa = { getPayment(qrCode)) },
orElse = { VippsError.from(throwable).left() }
).fold(
{ emit(PaymentStatus.Success) },
{ emit(it) }
)
jean
03/27/2023, 6:40 PM