From ca2913ad553788224a817556ff010701ed565cbc Mon Sep 17 00:00:00 2001 From: Dmitry Date: Wed, 7 Jan 2026 00:33:51 +0300 Subject: [PATCH] fixes --- .../lib/api/purchase/payment_manager.dart | 20 +++++++++++++++--- .../lib/api/v2/purchases_api_v2.dart | 21 ++++++++----------- .../lib/database/database.dart | 13 +++++++++--- .../dtos/payment/yookassa_payment_dto.dart | 7 ++++++- .../dtos/payment/yookassa_payment_dto.g.dart | 18 +++++++++++++++- .../pages/purchase/purchase_page.dart | 2 +- .../services/purchases_service_test.dart | 2 ++ .../state/purchase_state_manager_test.dart | 1 + 8 files changed, 63 insertions(+), 21 deletions(-) diff --git a/mnemo_cards_backend/lib/api/purchase/payment_manager.dart b/mnemo_cards_backend/lib/api/purchase/payment_manager.dart index 3ac686b..99f5143 100644 --- a/mnemo_cards_backend/lib/api/purchase/payment_manager.dart +++ b/mnemo_cards_backend/lib/api/purchase/payment_manager.dart @@ -12,6 +12,17 @@ import 'rustore/rustore_purchase_handler.dart'; import 'yoo_money.dart'; import '../../user/user_drift_extension.dart'; +/// Result of creating YooKassa payment URL +class YookassaPaymentResult { + final String confirmationUrl; + final String paymentId; + + YookassaPaymentResult({ + required this.confirmationUrl, + required this.paymentId, + }); +} + @lazySingleton class PaymentManager { final AppDatabase _db; @@ -318,7 +329,7 @@ class PaymentManager { } /// Создать URL для оплаты через YooKassa - Future createYookassaUrl({ + Future createYookassaUrl({ required String amount, required String description, required String userId, @@ -357,8 +368,11 @@ class PaymentManager { await createPayment(paymentDto, userId); print('✅ PaymentManager.createYookassaUrl: Payment created in database'); - print('✅ PaymentManager.createYookassaUrl: Returning confirmationUrl'); - return yookassaPayment.confirmationUrl!; + print('✅ PaymentManager.createYookassaUrl: Returning confirmationUrl and paymentId'); + return YookassaPaymentResult( + confirmationUrl: yookassaPayment.confirmationUrl!, + paymentId: yookassaPayment.id, + ); } /// Получить платежи пользователя diff --git a/mnemo_cards_backend/lib/api/v2/purchases_api_v2.dart b/mnemo_cards_backend/lib/api/v2/purchases_api_v2.dart index 66b14b9..1f3ec9c 100644 --- a/mnemo_cards_backend/lib/api/v2/purchases_api_v2.dart +++ b/mnemo_cards_backend/lib/api/v2/purchases_api_v2.dart @@ -137,26 +137,22 @@ class PurchasesApiV2 { print('🔍 createPackPurchase: Creating YooKassa payment URL'); // Create payment URL - final confirmationUrl = await _paymentManager.createYookassaUrl( + final paymentResult = await _paymentManager.createYookassaUrl( amount: price, description: 'Покупка пакета: ${pack.title}', userId: user.id!, products: products, ); - print('✅ createPackPurchase: Payment URL created: $confirmationUrl'); - - // Get payment by external token (we need to find it) - // Since createYookassaUrl creates payment internally, we need to get it - // For now, we'll create a simple response - // TODO: Improve this to return proper YookassaPaymentDto + print('✅ createPackPurchase: Payment URL created: ${paymentResult.confirmationUrl}, paymentId=${paymentResult.paymentId}'); // Build return URL for payment verification final baseUri = request.requestedUri; - final checkUrl = '${baseUri.scheme}://${baseUri.host}${baseUri.hasPort ? ':${baseUri.port}' : ''}/api/v2/purchases/payments/verify?packId=$packId'; + final checkUrl = '${baseUri.scheme}://${baseUri.host}${baseUri.hasPort ? ':${baseUri.port}' : ''}/api/v2/purchases/payments/${paymentResult.paymentId}/verify?productId=$packId&productType=pack'; return _ok({ - 'purchaseUrl': confirmationUrl, + 'purchaseUrl': paymentResult.confirmationUrl, 'checkUrl': checkUrl, + 'paymentId': paymentResult.paymentId, }); } catch (e, s) { print('❌ createPackPurchase ERROR: $e'); @@ -287,7 +283,7 @@ class PurchasesApiV2 { } // Create payment URL - final confirmationUrl = await _paymentManager.createYookassaUrl( + final paymentResult = await _paymentManager.createYookassaUrl( amount: price, description: description, userId: user.id!, @@ -296,11 +292,12 @@ class PurchasesApiV2 { // Build return URL for payment verification final baseUri = request.requestedUri; - final checkUrl = '${baseUri.scheme}://${baseUri.host}${baseUri.hasPort ? ':${baseUri.port}' : ''}/api/v2/purchases/payments/verify?productId=$productId&productType=$productTypeStr'; + final checkUrl = '${baseUri.scheme}://${baseUri.host}${baseUri.hasPort ? ':${baseUri.port}' : ''}/api/v2/purchases/payments/${paymentResult.paymentId}/verify?productId=$productId&productType=$productTypeStr'; return _ok({ - 'purchaseUrl': confirmationUrl, + 'purchaseUrl': paymentResult.confirmationUrl, 'checkUrl': checkUrl, + 'paymentId': paymentResult.paymentId, }); } catch (e, s) { developer.log('Error in createPayment: $e', error: e, stackTrace: s); diff --git a/mnemo_cards_backend/lib/database/database.dart b/mnemo_cards_backend/lib/database/database.dart index a5f68e2..2d079d0 100644 --- a/mnemo_cards_backend/lib/database/database.dart +++ b/mnemo_cards_backend/lib/database/database.dart @@ -336,8 +336,15 @@ class AppDatabase extends _$AppDatabase { // 2. Удаляем все старые вопросы (soft delete) print('Soft-deleting all existing test questions...'); - await customStatement( - 'UPDATE test_questions SET is_deleted = TRUE, deleted_at = NOW() WHERE is_deleted = FALSE', + final now = PgDateTime(DateTime.now()); + await (update(testQuestions) + ..where((tq) => tq.isDeleted.equals(false))) + .write( + TestQuestionsCompanion( + isDeleted: const Value(true), + deletedAt: Value(now), + updatedAt: Value(now), + ), ); print( 'All old questions marked as deleted. Create new questions via admin panel.', @@ -365,7 +372,7 @@ class AppDatabase extends _$AppDatabase { // 5. Удаляем всю старую статистику (она будет создаваться заново) print('Deleting old test statistics...'); - await customStatement('DELETE FROM test_statistics'); + await (delete(testStatistics)).go(); print( 'All old statistics deleted. New statistics will be collected automatically.', ); diff --git a/mnemo_cards_common/lib/src/dtos/payment/yookassa_payment_dto.dart b/mnemo_cards_common/lib/src/dtos/payment/yookassa_payment_dto.dart index 2210ae8..9cec4de 100644 --- a/mnemo_cards_common/lib/src/dtos/payment/yookassa_payment_dto.dart +++ b/mnemo_cards_common/lib/src/dtos/payment/yookassa_payment_dto.dart @@ -8,8 +8,13 @@ part 'yookassa_payment_dto.g.dart'; class YookassaPaymentDto { final String purchaseUrl; final String checkUrl; + final String paymentId; - YookassaPaymentDto({required this.purchaseUrl, required this.checkUrl}); + YookassaPaymentDto({ + required this.purchaseUrl, + required this.checkUrl, + required this.paymentId, + }); factory YookassaPaymentDto.fromJson(Map json) => _$YookassaPaymentDtoFromJson(json); diff --git a/mnemo_cards_common/lib/src/dtos/payment/yookassa_payment_dto.g.dart b/mnemo_cards_common/lib/src/dtos/payment/yookassa_payment_dto.g.dart index acb80d6..2f3d3a3 100644 --- a/mnemo_cards_common/lib/src/dtos/payment/yookassa_payment_dto.g.dart +++ b/mnemo_cards_common/lib/src/dtos/payment/yookassa_payment_dto.g.dart @@ -11,6 +11,8 @@ abstract class _$YookassaPaymentDtoCWProxy { YookassaPaymentDto checkUrl(String checkUrl); + YookassaPaymentDto paymentId(String paymentId); + /// Creates a new instance with the provided field values. /// Passing `null` to a nullable field nullifies it, while `null` for a non-nullable field is ignored. To update a single field use `YookassaPaymentDto(...).copyWith.fieldName(value)`. /// @@ -18,7 +20,11 @@ abstract class _$YookassaPaymentDtoCWProxy { /// ```dart /// YookassaPaymentDto(...).copyWith(id: 12, name: "My name") /// ``` - YookassaPaymentDto call({String purchaseUrl, String checkUrl}); + YookassaPaymentDto call({ + String purchaseUrl, + String checkUrl, + String paymentId, + }); } /// Callable proxy for `copyWith` functionality. @@ -35,6 +41,9 @@ class _$YookassaPaymentDtoCWProxyImpl implements _$YookassaPaymentDtoCWProxy { @override YookassaPaymentDto checkUrl(String checkUrl) => call(checkUrl: checkUrl); + @override + YookassaPaymentDto paymentId(String paymentId) => call(paymentId: paymentId); + @override /// Creates a new instance with the provided field values. /// Passing `null` to a nullable field nullifies it, while `null` for a non-nullable field is ignored. To update a single field use `YookassaPaymentDto(...).copyWith.fieldName(value)`. @@ -46,6 +55,7 @@ class _$YookassaPaymentDtoCWProxyImpl implements _$YookassaPaymentDtoCWProxy { YookassaPaymentDto call({ Object? purchaseUrl = const $CopyWithPlaceholder(), Object? checkUrl = const $CopyWithPlaceholder(), + Object? paymentId = const $CopyWithPlaceholder(), }) { return YookassaPaymentDto( purchaseUrl: @@ -57,6 +67,10 @@ class _$YookassaPaymentDtoCWProxyImpl implements _$YookassaPaymentDtoCWProxy { ? _value.checkUrl // ignore: cast_nullable_to_non_nullable : checkUrl as String, + paymentId: paymentId == const $CopyWithPlaceholder() || paymentId == null + ? _value.paymentId + // ignore: cast_nullable_to_non_nullable + : paymentId as String, ); } } @@ -77,10 +91,12 @@ YookassaPaymentDto _$YookassaPaymentDtoFromJson(Map json) => YookassaPaymentDto( purchaseUrl: json['purchaseUrl'] as String, checkUrl: json['checkUrl'] as String, + paymentId: json['paymentId'] as String, ); Map _$YookassaPaymentDtoToJson(YookassaPaymentDto instance) => { 'purchaseUrl': instance.purchaseUrl, 'checkUrl': instance.checkUrl, + 'paymentId': instance.paymentId, }; diff --git a/mnemo_cards_web_v2/lib/presentation/pages/purchase/purchase_page.dart b/mnemo_cards_web_v2/lib/presentation/pages/purchase/purchase_page.dart index 56b9d6b..3b4a36a 100644 --- a/mnemo_cards_web_v2/lib/presentation/pages/purchase/purchase_page.dart +++ b/mnemo_cards_web_v2/lib/presentation/pages/purchase/purchase_page.dart @@ -103,7 +103,7 @@ class _PurchasePageState extends State { // Show dialog with verification option if (mounted) { - _showPaymentVerificationDialog(payment.checkUrl); + _showPaymentVerificationDialog(payment.paymentId); } } else { if (mounted) { diff --git a/mnemo_cards_web_v2/test/domain/services/purchases_service_test.dart b/mnemo_cards_web_v2/test/domain/services/purchases_service_test.dart index 79891e4..f214c07 100644 --- a/mnemo_cards_web_v2/test/domain/services/purchases_service_test.dart +++ b/mnemo_cards_web_v2/test/domain/services/purchases_service_test.dart @@ -25,6 +25,7 @@ void main() { final paymentDto = YookassaPaymentDto( purchaseUrl: 'https://example.com/pay', checkUrl: 'https://example.com/check', + paymentId: paymentId, ); when( () => httpRepository.createPackPurchase(packId), @@ -59,6 +60,7 @@ void main() { final paymentDto = YookassaPaymentDto( purchaseUrl: 'https://example.com/pay', checkUrl: 'https://example.com/check', + paymentId: paymentId, ); when( () => httpRepository.createPayment( diff --git a/mnemo_cards_web_v2/test/domain/state/purchase_state_manager_test.dart b/mnemo_cards_web_v2/test/domain/state/purchase_state_manager_test.dart index ffb363f..5c6b39a 100644 --- a/mnemo_cards_web_v2/test/domain/state/purchase_state_manager_test.dart +++ b/mnemo_cards_web_v2/test/domain/state/purchase_state_manager_test.dart @@ -131,6 +131,7 @@ void main() { final testPayment = YookassaPaymentDto( purchaseUrl: 'https://yookassa.ru/pay/payment-123', checkUrl: 'https://example.com/check/payment-123', + paymentId: 'payment-123', ); test('returns null if pack info not loaded', () async {