diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountViewModel.kt index dcfca17eca..48c8e94492 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountViewModel.kt @@ -63,6 +63,7 @@ import com.vitorpamplona.amethyst.model.User import com.vitorpamplona.amethyst.model.privacyOptions.EmptyRoleBasedHttpClientBuilder import com.vitorpamplona.amethyst.model.privacyOptions.IRoleBasedHttpClientBuilder import com.vitorpamplona.amethyst.model.privacyOptions.RoleBasedHttpClientBuilder +import com.vitorpamplona.amethyst.service.ClinkDebitPayer import com.vitorpamplona.amethyst.service.OnlineChecker import com.vitorpamplona.amethyst.service.ZapPaymentHandler import com.vitorpamplona.amethyst.service.cashu.melt.MeltProcessor @@ -87,6 +88,8 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.eventsync.EventSync import com.vitorpamplona.amethyst.ui.screen.loggedIn.wallet.ReloadMintRequest import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.tor.TorSettingsFlow +import com.vitorpamplona.quartz.experimental.clink.debits.DebitResponse +import com.vitorpamplona.quartz.experimental.clink.pointers.NDebit import com.vitorpamplona.quartz.experimental.ephemChat.chat.RoomId import com.vitorpamplona.quartz.experimental.interactiveStories.InteractiveStoryBaseEvent import com.vitorpamplona.quartz.experimental.interactiveStories.InteractiveStoryReadingStateEvent @@ -2027,6 +2030,20 @@ class AccountViewModel( onSent() } + /** + * Pays a single BOLT-11 through a CLINK debit pointer (kind 21002) — the debit-rail + * counterpart of [sendZapPaymentRequestFor]. [onResult] receives the decrypted + * response (`isOk()` with optional preimage, or a GFY failure), or null on timeout. + * Untested end-to-end. + */ + fun payInvoiceViaClinkDebit( + pointer: NDebit, + bolt11: String, + onResult: (DebitResponse?) -> Unit, + ) = launchSigner { + onResult(ClinkDebitPayer.payInvoice(account, pointer, bolt11)) + } + fun getInteractiveStoryReadingState(dATag: String): AddressableNote = LocalCache.getOrCreateAddressableNote(InteractiveStoryReadingStateEvent.createAddress(account.signer.pubKey, dATag)) fun updateInteractiveStoryReadingState( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/dvms/DvmContentDiscoveryScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/dvms/DvmContentDiscoveryScreen.kt index b112e5264a..02b4cdc9ce 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/dvms/DvmContentDiscoveryScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/dvms/DvmContentDiscoveryScreen.kt @@ -49,6 +49,7 @@ import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.viewmodel.compose.viewModel import com.vitorpamplona.amethyst.R +import com.vitorpamplona.amethyst.commons.model.payments.PaymentSource import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.User import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.EventFinderFilterAssemblerSubscription @@ -379,39 +380,57 @@ fun DvmPaymentActions( if (invoice != null) { val context = LocalContext.current Button(onClick = { - if (accountViewModel.account.nip47SignerState.hasWalletConnectSetup()) { - accountViewModel.sendZapPaymentRequestFor( - bolt11 = invoice, - zappedNote = null, - onSent = { - onStatusUpdate(nwcPaymentRequest) - }, - onResponse = { response -> + when (val source = accountViewModel.account.settings.defaultPaymentSource()) { + is PaymentSource.ClinkDebit -> { + onStatusUpdate(nwcPaymentRequest) + accountViewModel.payInvoiceViaClinkDebit(source.wallet.pointer, invoice) { response -> onStatusUpdate( - if (response is PayInvoiceErrorResponse) { - stringRes( - context, - R.string.wallet_connect_pay_invoice_error_error, - response.error?.message - ?: response.error?.code?.toString() ?: "Error parsing error message", - ) - } else { + if (response?.isOk() == true) { thankYou + } else { + response?.error?.takeIf { it.isNotBlank() } + ?: stringRes(context, R.string.clink_debit_no_response) }, ) - }, - ) - } else { - payViaIntent( - invoice, - context, - onPaid = { - onStatusUpdate(thankYou) - }, - onError = { - onStatusUpdate(it) - }, - ) + } + } + + is PaymentSource.Nwc -> { + accountViewModel.sendZapPaymentRequestFor( + bolt11 = invoice, + zappedNote = null, + onSent = { + onStatusUpdate(nwcPaymentRequest) + }, + onResponse = { response -> + onStatusUpdate( + if (response is PayInvoiceErrorResponse) { + stringRes( + context, + R.string.wallet_connect_pay_invoice_error_error, + response.error?.message + ?: response.error?.code?.toString() ?: "Error parsing error message", + ) + } else { + thankYou + }, + ) + }, + ) + } + + null -> { + payViaIntent( + invoice, + context, + onPaid = { + onStatusUpdate(thankYou) + }, + onError = { + onStatusUpdate(it) + }, + ) + } } }) { val amountInInvoice = diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/DisplayLNAddress.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/DisplayLNAddress.kt index 2b2ac59469..38337579a2 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/DisplayLNAddress.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/DisplayLNAddress.kt @@ -34,6 +34,7 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import com.vitorpamplona.amethyst.R +import com.vitorpamplona.amethyst.commons.model.payments.PaymentSource import com.vitorpamplona.amethyst.model.User import com.vitorpamplona.amethyst.ui.actions.InformationDialog import com.vitorpamplona.amethyst.ui.components.util.LongPressCopyText @@ -109,22 +110,38 @@ fun DisplayLNAddress( lud16, user, accountViewModel, - onSuccess = { + onSuccess = { invoice -> zapExpanded = false - // pay directly - if (accountViewModel.account.nip47SignerState.hasWalletConnectSetup()) { - accountViewModel.sendZapPaymentRequestFor(it, null) { response -> - if (response is PayInvoiceSuccessResponse) { - showInfoMessageDialog = stringRes(context, R.string.payment_successful) - } else if (response is PayInvoiceErrorResponse) { - showErrorMessageDialog = - response.error?.message - ?: response.error?.code?.toString() - ?: stringRes(context, R.string.error_parsing_error_message) + // pay directly through the selected default payment source + when (val source = accountViewModel.account.settings.defaultPaymentSource()) { + is PaymentSource.ClinkDebit -> { + accountViewModel.payInvoiceViaClinkDebit(source.wallet.pointer, invoice) { response -> + if (response?.isOk() == true) { + showInfoMessageDialog = stringRes(context, R.string.payment_successful) + } else { + showErrorMessageDialog = + response?.error?.takeIf { it.isNotBlank() } + ?: stringRes(context, R.string.clink_debit_no_response) + } } } - } else { - payViaIntent(it, context, { zapExpanded = false }, { showErrorMessageDialog = it }) + + is PaymentSource.Nwc -> { + accountViewModel.sendZapPaymentRequestFor(invoice, null) { response -> + if (response is PayInvoiceSuccessResponse) { + showInfoMessageDialog = stringRes(context, R.string.payment_successful) + } else if (response is PayInvoiceErrorResponse) { + showErrorMessageDialog = + response.error?.message + ?: response.error?.code?.toString() + ?: stringRes(context, R.string.error_parsing_error_message) + } + } + } + + null -> { + payViaIntent(invoice, context, { zapExpanded = false }, { showErrorMessageDialog = it }) + } } }, onError = { title, message -> accountViewModel.toastManager.toast(title, message) },