-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathQuickPayViewModel.kt
More file actions
136 lines (120 loc) · 5.01 KB
/
QuickPayViewModel.kt
File metadata and controls
136 lines (120 loc) · 5.01 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
package to.bitkit.viewmodels
import android.content.Context
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import org.lightningdevkit.ldknode.Event
import org.lightningdevkit.ldknode.PaymentId
import to.bitkit.ext.WatchResult
import to.bitkit.ext.toUserMessage
import to.bitkit.ext.watchUntil
import to.bitkit.repositories.LightningRepo
import to.bitkit.repositories.PaymentPendingException
import to.bitkit.repositories.PendingPaymentRepo
import to.bitkit.utils.AppError
import to.bitkit.utils.Logger
import javax.inject.Inject
@HiltViewModel
class QuickPayViewModel @Inject constructor(
@ApplicationContext private val context: Context,
private val lightningRepo: LightningRepo,
private val pendingPaymentRepo: PendingPaymentRepo,
) : ViewModel() {
companion object {
private const val TAG = "QuickPayViewModel"
}
private val _uiState = MutableStateFlow(QuickPayUiState())
val uiState = _uiState.asStateFlow()
val lightningState = lightningRepo.lightningState
fun pay(data: QuickPayData) {
viewModelScope.launch {
val (bolt11, amount, displaySats) = when (data) {
is QuickPayData.Bolt11 -> {
Logger.info("QuickPay: processing bolt11 invoice")
Triple(data.bolt11, null, data.sats)
}
is QuickPayData.LnurlPay -> {
Logger.info("QuickPay: fetching LNURL Pay invoice from callback")
val invoice = lightningRepo.fetchLnurlInvoice(callbackUrl = data.callback, amountSats = data.sats)
.getOrElse { error ->
_uiState.update {
it.copy(result = QuickPayResult.Error(error.message.orEmpty()))
}
return@launch
}
Triple(invoice.bolt11, data.sats, data.sats)
}
}
sendLightning(bolt11, amount)
.onSuccess { paymentHash ->
Logger.info("QuickPay lightning payment successful")
_uiState.update {
it.copy(
result = QuickPayResult.Success(
paymentHash = paymentHash,
amountWithFee = displaySats.toLong() // TODO GET FEE WHEN AVAILABLE
)
)
}
}.onFailure { error ->
if (error is PaymentPendingException) {
Logger.info("QuickPay lightning payment pending", context = TAG)
pendingPaymentRepo.track(error.paymentHash)
_uiState.update {
it.copy(
result = QuickPayResult.Pending(
paymentHash = error.paymentHash,
amount = displaySats.toLong(),
)
)
}
return@onFailure
}
Logger.error("QuickPay lightning payment failed", error, context = TAG)
_uiState.update {
it.copy(result = QuickPayResult.Error(error.message.orEmpty()))
}
}
}
}
private suspend fun sendLightning(
bolt11: String,
amount: ULong? = null,
): Result<PaymentId> {
val hash = lightningRepo.payInvoice(bolt11 = bolt11, sats = amount)
.onFailure { exception ->
return Result.failure(exception)
}
.getOrDefault("")
// Wait until matching payment event is received (with timeout for hold invoices)
val result = lightningRepo.nodeEvents.watchUntil(LightningRepo.SEND_LN_TIMEOUT) {
when (it) {
is Event.PaymentSuccessful if it.paymentHash == hash -> WatchResult.Complete(Result.success(hash))
is Event.PaymentFailed if it.paymentHash == hash -> WatchResult.Complete(
Result.failure(AppError(it.reason.toUserMessage(context)))
)
else -> WatchResult.Continue()
}
}
return result ?: Result.failure(PaymentPendingException(hash))
}
}
sealed class QuickPayResult {
data class Success(
val paymentHash: String,
val amountWithFee: Long,
) : QuickPayResult()
data class Pending(
val paymentHash: String,
val amount: Long,
) : QuickPayResult()
data class Error(val message: String) : QuickPayResult()
}
data class QuickPayUiState(
val result: QuickPayResult? = null,
)