diff --git a/app/src/main/java/com/electricdreams/numo/feature/items/ItemSelectionActivity.kt b/app/src/main/java/com/electricdreams/numo/feature/items/ItemSelectionActivity.kt index ac3909ea..f9eea9a3 100644 --- a/app/src/main/java/com/electricdreams/numo/feature/items/ItemSelectionActivity.kt +++ b/app/src/main/java/com/electricdreams/numo/feature/items/ItemSelectionActivity.kt @@ -1,3 +1,4 @@ + package com.electricdreams.numo.feature.items import android.animation.AnimatorSet @@ -13,6 +14,7 @@ import android.widget.ImageButton import android.widget.ImageView import android.widget.LinearLayout import android.widget.TextView +import android.widget.Toast import com.google.android.flexbox.FlexboxLayout import androidx.activity.result.contract.ActivityResultContracts import androidx.appcompat.app.AlertDialog @@ -312,8 +314,10 @@ class ItemSelectionActivity : AppCompatActivity() { val basketId = ensureBasketSaved() if (basketId != null) { checkoutHandler.savedBasketId = basketId + checkoutHandler.proceedToCheckout() + } else { + Toast.makeText(this, R.string.item_selection_toast_save_basket_first, Toast.LENGTH_SHORT).show() } - checkoutHandler.proceedToCheckout() } saveButton.setOnClickListener { diff --git a/app/src/main/java/com/electricdreams/numo/feature/items/handlers/CheckoutHandler.kt b/app/src/main/java/com/electricdreams/numo/feature/items/handlers/CheckoutHandler.kt index d8da7631..f0fff0d9 100644 --- a/app/src/main/java/com/electricdreams/numo/feature/items/handlers/CheckoutHandler.kt +++ b/app/src/main/java/com/electricdreams/numo/feature/items/handlers/CheckoutHandler.kt @@ -51,45 +51,17 @@ class CheckoutHandler( // Determine how to format the amount for PaymentRequestActivity val formattedAmount = formatPaymentAmount(fiatTotal, satsTotal) - // Create a snapshot of the basket BEFORE clearing it - // This preserves the checkout data for receipt generation - val checkoutBasket = CheckoutBasket.fromBasketManager( - basketManager = basketManager, - currency = currencyManager.getCurrentCurrency(), - bitcoinPrice = if (btcPrice > 0) btcPrice else null, - totalSatoshis = totalSatoshis, - ) - val checkoutBasketJson = checkoutBasket.toJson() - - android.util.Log.d("CheckoutHandler", "Captured basket with ${checkoutBasket.items.size} items, total: $totalSatoshis sats") - android.util.Log.d("CheckoutHandler", "Basket JSON size: ${checkoutBasketJson.length} chars") - // Clear basket before navigating away so UI state is clean when we return basketManager.clearBasket() - // Decide whether to show tip selection first or go directly to payment request val tipsManager = TipsManager.getInstance(activity) - if (tipsManager.tipsEnabled) { - // Route through beautiful tip selection screen first - val intent = Intent(activity, TipSelectionActivity::class.java).apply { - putExtra(TipSelectionActivity.EXTRA_PAYMENT_AMOUNT, totalSatoshis) - putExtra(TipSelectionActivity.EXTRA_FORMATTED_AMOUNT, formattedAmount) - putExtra(TipSelectionActivity.EXTRA_CHECKOUT_BASKET_JSON, checkoutBasketJson) - // Preserve saved basket association all the way through to PaymentRequestActivity - savedBasketId?.let { putExtra(PaymentRequestActivity.EXTRA_SAVED_BASKET_ID, it) } - } - activity.startActivity(intent) - } else { - // Go directly to payment request without tips - val intent = Intent(activity, PaymentRequestActivity::class.java).apply { - putExtra(PaymentRequestActivity.EXTRA_PAYMENT_AMOUNT, totalSatoshis) - putExtra(PaymentRequestActivity.EXTRA_FORMATTED_AMOUNT, formattedAmount) - putExtra(PaymentRequestActivity.EXTRA_CHECKOUT_BASKET_JSON, checkoutBasketJson) - savedBasketId?.let { putExtra(PaymentRequestActivity.EXTRA_SAVED_BASKET_ID, it) } - } - activity.startActivity(intent) + val intent = Intent(activity, if (tipsManager.tipsEnabled) TipSelectionActivity::class.java else PaymentRequestActivity::class.java).apply { + putExtra(PaymentRequestActivity.EXTRA_PAYMENT_AMOUNT, totalSatoshis) + putExtra(PaymentRequestActivity.EXTRA_FORMATTED_AMOUNT, formattedAmount) + savedBasketId?.let { putExtra(PaymentRequestActivity.EXTRA_SAVED_BASKET_ID, it) } } + activity.startActivity(intent) activity.finish() } diff --git a/app/src/main/java/com/electricdreams/numo/feature/tips/TipSelectionActivity.kt b/app/src/main/java/com/electricdreams/numo/feature/tips/TipSelectionActivity.kt index 72bf81f3..feb10cfc 100644 --- a/app/src/main/java/com/electricdreams/numo/feature/tips/TipSelectionActivity.kt +++ b/app/src/main/java/com/electricdreams/numo/feature/tips/TipSelectionActivity.kt @@ -42,7 +42,7 @@ class TipSelectionActivity : AppCompatActivity() { private var formattedAmount: String = "" private var entryCurrency: Currency = Currency.USD private var enteredAmountFiat: Long = 0 - private var checkoutBasketJson: String? = null + private var basketId: String? = null // State private var selectedTipSats: Long = 0 @@ -123,7 +123,7 @@ class TipSelectionActivity : AppCompatActivity() { private fun initializeFromIntent() { paymentAmountSats = intent.getLongExtra(EXTRA_PAYMENT_AMOUNT, 0) formattedAmount = intent.getStringExtra(EXTRA_FORMATTED_AMOUNT) ?: "" - checkoutBasketJson = intent.getStringExtra(EXTRA_CHECKOUT_BASKET_JSON) + basketId = intent.getStringExtra(EXTRA_BASKET_ID) // Parse entry currency val parsedAmount = Amount.parse(formattedAmount) @@ -852,9 +852,7 @@ class TipSelectionActivity : AppCompatActivity() { putExtra(EXTRA_TIP_PERCENTAGE, selectedTipPercentage) putExtra(EXTRA_BASE_AMOUNT_SATS, paymentAmountSats) putExtra(EXTRA_BASE_FORMATTED_AMOUNT, formattedAmount) - checkoutBasketJson?.let { - putExtra(PaymentRequestActivity.EXTRA_CHECKOUT_BASKET_JSON, it) - } + basketId?.let { putExtra(PaymentRequestActivity.EXTRA_SAVED_BASKET_ID, it) } } startActivityForResult(intent, REQUEST_CODE_PAYMENT) @@ -902,7 +900,7 @@ class TipSelectionActivity : AppCompatActivity() { companion object { const val EXTRA_PAYMENT_AMOUNT = "payment_amount" const val EXTRA_FORMATTED_AMOUNT = "formatted_amount" - const val EXTRA_CHECKOUT_BASKET_JSON = "checkout_basket_json" + const val EXTRA_BASKET_ID = "basket_id" const val EXTRA_TIP_AMOUNT_SATS = "tip_amount_sats" const val EXTRA_TIP_PERCENTAGE = "tip_percentage" const val EXTRA_BASE_AMOUNT_SATS = "base_amount_sats" diff --git a/app/src/main/java/com/electricdreams/numo/payment/PaymentMethodHandler.kt b/app/src/main/java/com/electricdreams/numo/payment/PaymentMethodHandler.kt index 899cb5a0..8ed9d9d0 100644 --- a/app/src/main/java/com/electricdreams/numo/payment/PaymentMethodHandler.kt +++ b/app/src/main/java/com/electricdreams/numo/payment/PaymentMethodHandler.kt @@ -19,11 +19,11 @@ class PaymentMethodHandler( ) { /** Show payment method dialog for the specified amount */ - fun showPaymentMethodDialog(amount: Long, formattedAmount: String, checkoutBasketJson: String? = null) { + fun showPaymentMethodDialog(amount: Long, formattedAmount: String, basketId: String? = null) { val tipsManager = TipsManager.getInstance(activity) val routing = PaymentRoutingCore.determinePaymentRoute(tipsManager.tipsEnabled) - val intent = routing.buildIntent(activity, amount, formattedAmount, checkoutBasketJson) + val intent = routing.buildIntent(activity, amount, formattedAmount, basketId) activity.startActivityForResult(intent, REQUEST_CODE_PAYMENT) } diff --git a/app/src/main/java/com/electricdreams/numo/payment/PaymentRoutingCore.kt b/app/src/main/java/com/electricdreams/numo/payment/PaymentRoutingCore.kt index 46ba4622..c659bba9 100644 --- a/app/src/main/java/com/electricdreams/numo/payment/PaymentRoutingCore.kt +++ b/app/src/main/java/com/electricdreams/numo/payment/PaymentRoutingCore.kt @@ -19,7 +19,7 @@ object PaymentRoutingCore { context: Context, amount: Long, formattedAmount: String, - checkoutBasketJson: String? + basketId: String? ): Intent { val targetClass = when (targetActivity) { TargetActivity.TIP_SELECTION -> TipSelectionActivity::class.java @@ -28,9 +28,7 @@ object PaymentRoutingCore { return Intent(context, targetClass).apply { putExtra(PaymentRequestActivity.EXTRA_PAYMENT_AMOUNT, amount) putExtra(PaymentRequestActivity.EXTRA_FORMATTED_AMOUNT, formattedAmount) - checkoutBasketJson?.let { - putExtra(PaymentRequestActivity.EXTRA_CHECKOUT_BASKET_JSON, it) - } + basketId?.let { putExtra(PaymentRequestActivity.EXTRA_SAVED_BASKET_ID, it) } } } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 23aac4cf..e93c1760 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,17 +1,18 @@ + Numo Open navigation drawer Close navigation drawer Manage Catalog Items - + Cashu Payment Service NDEF Tag Type 4 - + Cashu payment request QR code - + Back NFC Balance Check Check Balance @@ -26,7 +27,7 @@ Card does not support IsoDep Invalid NFC tag - + Back Tips Allow customers to add a tip when paying. Tip presets will appear as quick-select buttons on the payment screen. @@ -36,7 +37,7 @@ Add Preset Reset to Defaults - + %1$d%% Add Tip Preset Enter percentage (1-100) @@ -57,7 +58,7 @@ Reset Presets reset to defaults - + Close Order Total Would you like to add a tip? @@ -70,7 +71,7 @@ Back Confirm - + Close Share Pay @@ -94,7 +95,7 @@ Share Payment Request - + %1$s received. View Details Close @@ -108,13 +109,13 @@ No apps available to share this token Transaction details not available - + Bitcoin POS with Cashu ecash Accept Bitcoin payments instantly with zero fees using Cashu ecash technology. By continuing, you agree to our Terms of Service and Privacy Policy Get Started - + Set Up Your Wallet Choose how you would like to get started Create New Wallet @@ -122,7 +123,7 @@ Restore from Backup Use your existing seed phrase - + Back Restore Wallet Enter your 12-word seed phrase @@ -136,7 +137,7 @@ Please paste a valid 12-word seed phrase Seed phrase pasted - + Creating your wallet... Generating seed phrase... Setting up default mints... @@ -145,11 +146,11 @@ Fetching mint information... This will only take a moment - + Searching for backup... Checking Nostr relays for your mint configuration - + Back Your Mints Backup Found @@ -163,11 +164,11 @@ Restore Wallet Continue - + Restoring Wallet Initializing restore... - + Wallet Restored Recovered %1$d sats Total balance: %1$d sats @@ -176,11 +177,11 @@ BALANCE BY MINT Enter Wallet - + Error creating wallet: %1$s Error initializing wallet: %1$s - + Important Warning Restoring from a seed phrase will replace your current wallet. Make sure you have backed up your current seed phrase before proceeding. Enter your 12-word seed phrase @@ -211,7 +212,7 @@ Restore failed: %1$s - + Back Seed Phrase Keep this secret! @@ -221,12 +222,38 @@ Copy to Clipboard Make sure to clear your clipboard after pasting - + Terms of Service - NUMO WALLET - TERMS OF SERVICE 1. ACCEPTANCE By using this wallet application, you agree to these terms. 2. NATURE OF SERVICE This is a self-custodial Bitcoin wallet using Cashu ecash technology. You are solely responsible for your funds and seed phrase. 3. SEED PHRASE Your 12-word seed phrase is the ONLY way to recover your wallet. Never share it. Store it securely offline. We cannot recover lost seed phrases. 4. NO WARRANTY This software is provided "as is" without warranty of any kind. Use at your own risk. 5. LIMITATION OF LIABILITY We are not liable for any loss of funds, whether through bugs, user error, or third-party mint failures. 6. ECASH MINTS Ecash tokens are held by third-party mints. These mints may fail or become unavailable. Diversify across multiple mints. 7. PRIVACY This wallet does not collect personal data. Transactions are processed through ecash mints which may have their own privacy policies. 8. UPDATES These terms may be updated. Continued use constitutes acceptance. Last updated: November 2024 + NUMO WALLET - TERMS OF SERVICE + +1. ACCEPTANCE +By using this wallet application, you agree to these terms. + +2. NATURE OF SERVICE +This is a self-custodial Bitcoin wallet using Cashu ecash technology. You are solely responsible for your funds and seed phrase. + +3. SEED PHRASE +Your 12-word seed phrase is the ONLY way to recover your wallet. Never share it. Store it securely offline. We cannot recover lost seed phrases. + +4. NO WARRANTY +This software is provided "as is" without warranty of any kind. Use at your own risk. + +5. LIMITATION OF LIABILITY +We are not liable for any loss of funds, whether through bugs, user error, or third-party mint failures. + +6. ECASH MINTS +Ecash tokens are held by third-party mints. These mints may fail or become unavailable. Diversify across multiple mints. + +7. PRIVACY +This wallet does not collect personal data. Transactions are processed through ecash mints which may have their own privacy policies. + +8. UPDATES +These terms may be updated. Continued use constitutes acceptance. + +Last updated: November 2024 Close - + Back Developer Settings Debugging @@ -234,7 +261,7 @@ Start the onboarding flow again ⚠️ Developer settings are for testing and debugging purposes only. Use with caution. - + Scan to Pay Contactless payment Cashu @@ -242,7 +269,7 @@ Waiting for NFC... Cancel - + Back Top Up Enter Cashu Token @@ -266,7 +293,7 @@ An unexpected error occurred: %1$s No saved PIN available - + Scan NFC Card Ready to import proofs Scan Card Again @@ -274,11 +301,11 @@ Processing Import Importing proofs... - + Enter PIN PIN - + Please enter an amount first PIN-based rescan not supported in this build Insufficient funds on card @@ -289,11 +316,11 @@ Satocash Card Error: %1$s (SW: 0x%2$04X) An unexpected error occurred: %1$s - + OK or - + Back Withdraw Available Balance @@ -309,7 +336,7 @@ 1000 Continue - + Invalid mint URL Please enter a Lightning invoice Wallet not initialized @@ -318,7 +345,7 @@ Please enter a valid amount Error: %1$s - + Back Confirm Withdrawal Withdraw from %1$s @@ -330,7 +357,7 @@ The fee reserve will be returned if the actual Lightning fee is lower. Processing withdrawal... - + Invalid melt quote data Wallet not initialized Payment failed: Invoice not paid @@ -352,7 +379,7 @@ Lightning Invoice - + %1$s sent. To %1$s Lightning @@ -360,7 +387,7 @@ Success background Success - + Activity PAYMENT HISTORY No payment history @@ -422,7 +449,7 @@ Restoring... +0 sats - + ITEMS CATALOG No items in catalog Manage your product catalog for quick checkout. @@ -430,7 +457,7 @@ Manage Clear All Items - + Back Items Add item @@ -441,7 +468,7 @@ Import from CSV Clear All Items - + Back Add Item Save @@ -509,13 +536,13 @@ Cancel - + Drag to reorder Item image No image More - + Item image No image Decrease quantity @@ -524,7 +551,7 @@ Add Creates a new item with this variation - + Back Checkout Search @@ -557,21 +584,21 @@ Save Basket Enter a name (optional) - + Save Basket Give your basket a name to find it easily later. Table 5, John order, etc. Leave empty to use default name Save Basket - + Rename Basket Update the name for this basket. Enter new name Save Changes Editing: %1$s - + Back Saved Baskets No saved baskets @@ -585,7 +612,7 @@ Delete Basket Delete \"%1$s\"? This cannot be undone. - + Back Order History No archived orders @@ -602,12 +629,12 @@ View Payment - + Save Delete Confirm - + Basket Names Create preset names for quick basket saving.\nPerfect for restaurant tables or customer orders. PRESET NAMES @@ -617,7 +644,7 @@ Examples: Table 1, Table 2, VIP, Takeout, John\'s Order Clear All Names - + Add Preset Name Create a quick-select name for baskets e.g. Table 1 @@ -638,17 +665,17 @@ Remove all preset names? This cannot be undone. Clear All - + Please enter a name This name already exists - + %1$d item %1$d items - + Withdrawals Auto-Withdraw Automatically send funds to your Lightning wallet when balance exceeds a threshold @@ -695,27 +722,27 @@ Auto-Withdraw Error Details - + Withdrawals Auto-withdraw settings and history - + Manual Withdraw Withdraw Now Send funds to Lightning invoice or address Select Mint No balance available to withdraw. Add funds to your wallet first. - + Balance - + Select Mint Choose which mint to withdraw from Mint icon Select this mint - + Lightning Invoice Paste an invoice to pay instantly Lightning Address @@ -727,12 +754,12 @@ Send To Available: %1$s - + Scan QR Code Point camera at QR code Camera permission is required to scan QR codes - + Mints Total Balance Your Mints @@ -766,7 +793,7 @@ %1$d mints - + Lightning Mint All Mints Balance @@ -774,16 +801,16 @@ Set as Lightning Mint Lightning mint updated - + Mint icon Selected as Lightning mint - + Info Delete Add Mint - + Mint Details About Announcement @@ -805,4 +832,5 @@ Version: %1$s Software: Unknown Version: Unknown - + Save or update your basket before checking out + \ No newline at end of file diff --git a/app/src/test/java/com/electricdreams/numo/payment/PaymentRoutingCoreTest.kt b/app/src/test/java/com/electricdreams/numo/payment/PaymentRoutingCoreTest.kt index d8840da8..d12880d1 100644 --- a/app/src/test/java/com/electricdreams/numo/payment/PaymentRoutingCoreTest.kt +++ b/app/src/test/java/com/electricdreams/numo/payment/PaymentRoutingCoreTest.kt @@ -34,13 +34,13 @@ class PaymentRoutingCoreTest { val decision = PaymentRoutingCore.RoutingDecision(PaymentRoutingCore.TargetActivity.PAYMENT_REQUEST) val context: Context = ApplicationProvider.getApplicationContext() - val intent = decision.buildIntent(context, amount = 42L, formattedAmount = "42 sats", checkoutBasketJson = "{}") + val intent = decision.buildIntent(context, amount = 42L, formattedAmount = "42 sats", basketId = "basket-123") val component = intent.component assertNotNull(component) assertEquals(ComponentName(context, PaymentRequestActivity::class.java), component) assertEquals(42L, intent.getLongExtra(PaymentRequestActivity.EXTRA_PAYMENT_AMOUNT, -1)) assertEquals("42 sats", intent.getStringExtra(PaymentRequestActivity.EXTRA_FORMATTED_AMOUNT)) - assertEquals("{}", intent.getStringExtra(PaymentRequestActivity.EXTRA_CHECKOUT_BASKET_JSON)) + assertEquals("basket-123", intent.getStringExtra(PaymentRequestActivity.EXTRA_SAVED_BASKET_ID)) } }