fixes
Some checks failed
Web App CI / test (push) Waiting to run
Web App CI / build (push) Blocked by required conditions
Deploy Mnemo Cards / Deploy Backend (push) Waiting to run
Deploy Mnemo Cards / Deploy Web App (push) Blocked by required conditions
Deploy Mnemo Cards / Final Verification (push) Blocked by required conditions
Backend CI / test (push) Has been cancelled
Mobile App CI / test (push) Has been cancelled
Deploy Telegram Bot / Deploy Telegram Bot (push) Has been cancelled
Backend CI / build (push) Has been cancelled
Mobile App CI / build-android (push) Has been cancelled
Mobile App CI / build-ios (push) Has been cancelled

This commit is contained in:
Dmitry 2026-01-07 00:33:51 +03:00
parent 496aac9135
commit ca2913ad55
8 changed files with 63 additions and 21 deletions

View file

@ -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<String> createYookassaUrl({
Future<YookassaPaymentResult> 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,
);
}
/// Получить платежи пользователя

View file

@ -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);

View file

@ -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.',
);

View file

@ -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<String, dynamic> json) =>
_$YookassaPaymentDtoFromJson(json);

View file

@ -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<String, dynamic> json) =>
YookassaPaymentDto(
purchaseUrl: json['purchaseUrl'] as String,
checkUrl: json['checkUrl'] as String,
paymentId: json['paymentId'] as String,
);
Map<String, dynamic> _$YookassaPaymentDtoToJson(YookassaPaymentDto instance) =>
<String, dynamic>{
'purchaseUrl': instance.purchaseUrl,
'checkUrl': instance.checkUrl,
'paymentId': instance.paymentId,
};

View file

@ -103,7 +103,7 @@ class _PurchasePageState extends State<PurchasePage> {
// Show dialog with verification option
if (mounted) {
_showPaymentVerificationDialog(payment.checkUrl);
_showPaymentVerificationDialog(payment.paymentId);
}
} else {
if (mounted) {

View file

@ -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(

View file

@ -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 {