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 PMAlejandro Serrano Mena
03/23/2023, 1:36 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)
}Alejandro Serrano Mena
03/23/2023, 1:37 PMFrancis 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?jean
03/27/2023, 1:06 PMretryOrElse 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)
}jean
03/27/2023, 5:52 PMsimon.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