From 17e113df1a3791fa3f5a281c87576118d8ff841c Mon Sep 17 00:00:00 2001 From: Nikolaus Heger Date: Fri, 22 May 2026 22:20:14 +0800 Subject: [PATCH] just send the amount as bigint there's no real reason to parse it on both sides --- mobile-app/lib/services/pos_service.dart | 15 ++------ .../lib/v2/screens/pos/pos_qr_screen.dart | 8 ++-- .../v2/screens/send/input_amount_screen.dart | 3 +- .../unit/number_formatting_service_test.dart | 35 ------------------ mobile-app/test/unit/pos_service_test.dart | 10 ++--- .../services/number_formatting_service.dart | 37 ------------------- 6 files changed, 11 insertions(+), 97 deletions(-) diff --git a/mobile-app/lib/services/pos_service.dart b/mobile-app/lib/services/pos_service.dart index 7af8a22b..233d71ed 100644 --- a/mobile-app/lib/services/pos_service.dart +++ b/mobile-app/lib/services/pos_service.dart @@ -1,19 +1,11 @@ -import 'package:quantus_sdk/quantus_sdk.dart'; - class PosPaymentRequest { final String paymentUrl; final String refId; - final String amount; - const PosPaymentRequest({required this.paymentUrl, required this.refId, required this.amount}); + const PosPaymentRequest({required this.paymentUrl, required this.refId}); } class PosService { - final NumberFormattingService _formattingService; - - PosService({NumberFormattingService? formattingService}) - : _formattingService = formattingService ?? NumberFormattingService(); - String generateRefId() { final now = DateTime.now().millisecondsSinceEpoch; return now.toRadixString(36).toUpperCase(); @@ -26,8 +18,7 @@ class PosService { PosPaymentRequest createPaymentRequest({required String accountId, required BigInt amountPlanck}) { final refId = generateRefId(); - final wireAmount = _formattingService.formatWireAmount(amountPlanck); - final url = buildPaymentUrl(accountId: accountId, amount: wireAmount, refId: refId); - return PosPaymentRequest(paymentUrl: url, refId: refId, amount: wireAmount); + final url = buildPaymentUrl(accountId: accountId, amount: amountPlanck.toString(), refId: refId); + return PosPaymentRequest(paymentUrl: url, refId: refId); } } diff --git a/mobile-app/lib/v2/screens/pos/pos_qr_screen.dart b/mobile-app/lib/v2/screens/pos/pos_qr_screen.dart index 3f5023df..18af3c47 100644 --- a/mobile-app/lib/v2/screens/pos/pos_qr_screen.dart +++ b/mobile-app/lib/v2/screens/pos/pos_qr_screen.dart @@ -159,7 +159,6 @@ class _PosQrScreenState extends ConsumerState { final colors = context.colors; final text = context.themeText; final accountAsync = ref.watch(activeAccountProvider); - final formattingService = ref.watch(numberFormattingServiceProvider); final display = ref.watch(txAmountDisplayProvider)( widget.amountPlanck, withSignPrefix: false, @@ -176,9 +175,10 @@ class _PosQrScreenState extends ConsumerState { ), data: (active) { if (active == null) return const Center(child: Text('No active account')); - _request ??= PosService( - formattingService: formattingService, - ).createPaymentRequest(accountId: active.account.accountId, amountPlanck: widget.amountPlanck); + _request ??= PosService().createPaymentRequest( + accountId: active.account.accountId, + amountPlanck: widget.amountPlanck, + ); if (_isPaid) return _buildPaidContent(colors, text, display.primaryAmount); return _buildQrContent(_request!, colors, text, display); }, diff --git a/mobile-app/lib/v2/screens/send/input_amount_screen.dart b/mobile-app/lib/v2/screens/send/input_amount_screen.dart index a4a72255..03c55121 100644 --- a/mobile-app/lib/v2/screens/send/input_amount_screen.dart +++ b/mobile-app/lib/v2/screens/send/input_amount_screen.dart @@ -66,9 +66,8 @@ class _InputAmountScreenState extends ConsumerState { assert(widget.recipientAddress.trim().isNotEmpty, 'InputAmountScreen requires a recipient'); _amountFocus.addListener(_onAmountFocusChanged); if (widget.initialAmount != null && widget.initialAmount!.isNotEmpty) { - final formattingService = ref.read(numberFormattingServiceProvider); final planck = widget.isPayMode - ? formattingService.parseWireAmount(widget.initialAmount!) ?? BigInt.zero + ? BigInt.tryParse(widget.initialAmount!) ?? BigInt.zero : _amountInputLogic.parseQuanAmount(widget.initialAmount!); if (planck > BigInt.zero) { _amount = planck; diff --git a/mobile-app/test/unit/number_formatting_service_test.dart b/mobile-app/test/unit/number_formatting_service_test.dart index 0bb43a24..54b9fe18 100644 --- a/mobile-app/test/unit/number_formatting_service_test.dart +++ b/mobile-app/test/unit/number_formatting_service_test.dart @@ -90,40 +90,5 @@ void main() { }); }); - group('formatWireAmount / parseWireAmount', () { - test('formatWireAmount always uses dot decimal without grouping', () { - final commaService = NumberFormattingService(localeConfig: LocaleNumberConfig.commaDecimal); - final balance = BigInt.parse('1500000000000'); - expect(commaService.formatWireAmount(balance), '1.5'); - }); - - test('round-trips canonical wire amounts', () { - final balance = BigInt.parse('1500000000000'); - final wire = service.formatWireAmount(balance); - expect(wire, '1.5'); - expect(service.parseWireAmount(wire), balance); - }); - - test('parses legacy comma-decimal amounts', () { - expect(service.parseWireAmount('1,5'), BigInt.parse('1500000000000')); - }); - - test('parses legacy dot-decimal amounts', () { - expect(service.parseWireAmount('1.5'), BigInt.parse('1500000000000')); - }); - - test('parses integer amounts', () { - expect(service.parseWireAmount('1000'), scaleFactor * BigInt.from(1000)); - }); - - test('parses mixed separators using rightmost decimal mark', () { - expect(service.parseWireAmount('1.000,50'), BigInt.parse('1000500000000000')); - expect(service.parseWireAmount('1,000.50'), BigInt.parse('1000500000000000')); - }); - - test('returns zero for empty wire amount', () { - expect(service.parseWireAmount(''), BigInt.zero); - }); - }); }); } diff --git a/mobile-app/test/unit/pos_service_test.dart b/mobile-app/test/unit/pos_service_test.dart index 774592b9..407c38bf 100644 --- a/mobile-app/test/unit/pos_service_test.dart +++ b/mobile-app/test/unit/pos_service_test.dart @@ -1,19 +1,15 @@ import 'package:flutter_test/flutter_test.dart'; -import 'package:quantus_sdk/quantus_sdk.dart'; import 'package:resonance_network_wallet/services/pos_service.dart'; void main() { group('PosService', () { - test('createPaymentRequest embeds canonical dot-decimal wire amount', () { - final formattingService = NumberFormattingService(localeConfig: LocaleNumberConfig.commaDecimal); - final service = PosService(formattingService: formattingService); + test('createPaymentRequest embeds raw planck integer amount', () { + final service = PosService(); final amountPlanck = BigInt.parse('1500000000000'); final request = service.createPaymentRequest(accountId: 'account123', amountPlanck: amountPlanck); - expect(request.amount, '1.5'); - expect(request.paymentUrl, contains('amount=1.5')); - expect(request.paymentUrl, isNot(contains('amount=1%2C5'))); + expect(request.paymentUrl, contains('amount=1500000000000')); expect(request.paymentUrl, contains('to=account123')); expect(request.refId, isNotEmpty); }); diff --git a/quantus_sdk/lib/src/services/number_formatting_service.dart b/quantus_sdk/lib/src/services/number_formatting_service.dart index 648d97d7..d30b6671 100644 --- a/quantus_sdk/lib/src/services/number_formatting_service.dart +++ b/quantus_sdk/lib/src/services/number_formatting_service.dart @@ -58,43 +58,6 @@ class NumberFormattingService { return resultString; } - /// Formats a balance for payment URL wire transport: dot decimal, no grouping. - /// - /// Wire amounts are locale-neutral and must be parsed with [parseWireAmount]. - String formatWireAmount(BigInt balance) { - return NumberFormattingService( - localeConfig: LocaleNumberConfig.dotDecimal, - ).formatBalance(balance, maxDecimals: decimals, addThousandsSeparators: false); - } - - /// Parses a payment URL amount without assuming the payer's locale. - /// - /// Supports canonical dot-decimal wire amounts and legacy locale-formatted - /// amounts from older POS QR codes. - BigInt? parseWireAmount(String formattedAmount) { - if (formattedAmount.isEmpty) { - return BigInt.zero; - } - - final config = _wireLocaleConfigFor(formattedAmount); - return NumberFormattingService(localeConfig: config).parseAmount(formattedAmount); - } - - static LocaleNumberConfig _wireLocaleConfigFor(String input) { - final hasComma = input.contains(','); - final hasDot = input.contains('.'); - - if (hasComma && hasDot) { - final lastComma = input.lastIndexOf(','); - final lastDot = input.lastIndexOf('.'); - return lastComma > lastDot ? LocaleNumberConfig.commaDecimal : LocaleNumberConfig.dotDecimal; - } - if (hasComma) { - return LocaleNumberConfig.commaDecimal; - } - return LocaleNumberConfig.dotDecimal; - } - /// Parses a user-entered formatted string amount into a raw BigInt amount /// scaled by the chain's decimals. ///