feat(payments): phase #1 tests and part of implementation

This commit is contained in:
Anatoliy Poloz
2026-07-08 13:31:55 +03:00
parent 2ab26bbda5
commit d21cc46558
12 changed files with 195 additions and 26 deletions

View File

@@ -0,0 +1,15 @@
import 'package:freezed_annotation/freezed_annotation.dart';
part 'order_id_to_conversation_model.freezed.dart';
part 'order_id_to_conversation_model.g.dart';
@freezed
abstract class OrderIdToConversationModel with _$OrderIdToConversationModel {
factory OrderIdToConversationModel({
@JsonKey(name: 'chosen_plan_id') required int chosenPlanId,
@JsonKey(name: 'conversation_uuid') required String conversationUuid,
}) = _OrderIdToConversationModel;
factory OrderIdToConversationModel.fromJson(Map<String, dynamic> json) =>
_$OrderIdToConversationModelFromJson(json);
}

View File

@@ -5,10 +5,12 @@ part 'payment_model.g.dart';
@freezed
abstract class PaymentModel with _$PaymentModel {
factory PaymentModel({
@JsonKey(name: 'checkout_url') required String paymentUrl,
@JsonKey(name: 'order_id') String? orderId,
@JsonKey(name: 'payment_provider') String? paymentProvider,
}) = _PaymentModel;
factory PaymentModel.fromJson(Map<String, dynamic> json) => _$PaymentModelFromJson(json);
}
factory PaymentModel.fromJson(Map<String, dynamic> json) =>
_$PaymentModelFromJson(json);
}

View File

@@ -297,7 +297,7 @@ class ChatRoomCubit extends Cubit<ChatRoomState> {
try {
final planId = (state as ChatRoomStateLoaded).chatRoom.chosenPlan?.id;
if (planId == null) return null;
final paymentModel = await chatRepository.getPaymentLink(id: planId);
final paymentModel = await chatRepository.getPaymentLink(id: planId, paymentProvider: 'alfa-bank');
return paymentModel.paymentUrl;
} catch (e, st) {
await reportFrom(e, st, this);

View File

@@ -6,6 +6,7 @@ import 'package:eatvio_app/features/chat/models/chat_message_model.dart';
import 'package:eatvio_app/features/chat/models/chat_messages_model.dart';
import 'package:eatvio_app/features/chat/models/chat_room_model.dart';
import 'package:eatvio_app/features/chat/models/chat_room_pagination_model.dart';
import 'package:eatvio_app/features/chat/models/order_id_to_conversation_model.dart';
import 'package:eatvio_app/features/chat/models/payment_model.dart';
import 'package:eatvio_app/features/chat/models/plan_complain_model.dart';
import 'package:eatvio_app/features/chat/services/chat_service.dart';
@@ -98,8 +99,19 @@ class ChatRepository {
Future<PaymentModel> getPaymentLink({
required int id,
required String paymentProvider,
}) async {
return await chatService.getPaymentLink(id: id);
return await chatService.getPaymentLink(
id: id,
body: {'payment_provider': paymentProvider},
);
}
Future<OrderIdToConversationModel> resolveOrderIdToConversation({
required String orderId,
}) async {
return await chatService.resolveOrderIdToConversation(orderId: orderId);
}

View File

@@ -3,6 +3,7 @@ import 'package:eatvio_app/features/chat/models/chat_message_model.dart';
import 'package:eatvio_app/features/chat/models/chat_messages_model.dart';
import 'package:eatvio_app/features/chat/models/chat_room_model.dart';
import 'package:eatvio_app/features/chat/models/chat_room_pagination_model.dart';
import 'package:eatvio_app/features/chat/models/order_id_to_conversation_model.dart';
import 'package:eatvio_app/features/chat/models/payment_model.dart';
import 'package:eatvio_app/features/chat/models/plan_complain_model.dart';
import 'package:eatvio_app/features/plans/models/chosen_plan_model.dart';
@@ -65,6 +66,12 @@ abstract class ChatService {
@POST('/api/chosen_plans/{id}/start_payment_process')
Future<PaymentModel> getPaymentLink({
@Path() required int id,
@Body() required Map<String, String> body,
});
@GET('/api/consultation/resolve/order_id_to_conversation/{orderId}')
Future<OrderIdToConversationModel> resolveOrderIdToConversation({
@Path() required String orderId,
});
@GET('/api/chosen_plans/show/{id}')

View File

@@ -14,7 +14,7 @@ class OrderInfoCubit extends Cubit<OrderInfoState> {
try {
final plan = state.chosenPlan;
if (plan == null) return null;
final paymentModel = await chatRepository.getPaymentLink(id: plan.id);
final paymentModel = await chatRepository.getPaymentLink(id: plan.id, paymentProvider: 'alfa-bank');
return paymentModel.paymentUrl;
} catch (e, st) {
await reportFrom(e, st, this);

View File

@@ -0,0 +1,22 @@
import 'package:eatvio_app/features/chat/models/order_id_to_conversation_model.dart';
import 'package:flutter_test/flutter_test.dart';
import '../../../fixtures/fixture_reader.dart';
void main() {
group('OrderIdToConversationModel', () {
test('fromJson создаёт модель из JSON фикстуры', () {
final json = FixtureReader.readAsMap(
'chat/payments/order_id_to_chat_uuid_response.json',
);
final model = OrderIdToConversationModel.fromJson(json);
expect(model.chosenPlanId, 12);
expect(
model.conversationUuid,
'fbd81429-8019-499a-80e8-e3933cc4d872',
);
});
});
}

View File

@@ -1,25 +1,37 @@
import 'package:eatvio_app/features/chat/models/payment_model.dart';
import 'package:flutter_test/flutter_test.dart';
import '../../../fixtures/fixture_reader.dart';
void main() {
group('PaymentModel', () {
test('создает PaymentModel с правильными значениями', () {
final model = PaymentModel(
paymentUrl: 'https://example.com/payment',
);
final model = PaymentModel(paymentUrl: 'https://example.com/payment');
expect(model.paymentUrl, 'https://example.com/payment');
});
test('fromJson создает PaymentModel из JSON', () {
final json = {
'checkout_url': 'https://example.com/payment',
};
final json = {'checkout_url': 'https://example.com/payment'};
final model = PaymentModel.fromJson(json);
expect(model.paymentUrl, 'https://example.com/payment');
});
test(
'fromJson парсит ответ alfa-bank (checkout_url, order_id, payment_provider)',
() {
final json = FixtureReader.readAsMap(
'chat/payments/payment_response_alfa_bank.json',
);
final model = PaymentModel.fromJson(json);
expect(model.paymentUrl, 'https://qr.nspk.ru/ASDFASDFASDFASDF');
expect(model.orderId, 'alfa_c29_k15138_t1712345678_r1234');
expect(model.paymentProvider, 'alfa-bank');
},
);
});
}

View File

@@ -678,7 +678,7 @@ void main() {
when(mockChatRepository.getChatRoom(uuid)).thenAnswer((_) async => chatRoomWithPlan);
when(mockChatRepository.getChatMessages(uuid, 1)).thenAnswer((_) async => testChatMessages);
when(mockChatRepository.getChosenPlan(id: planId)).thenAnswer((_) async => chosenPlan);
when(mockChatRepository.getPaymentLink(id: planId)).thenAnswer((_) async => PaymentModel(paymentUrl: paymentUrl));
when(mockChatRepository.getPaymentLink(id: planId, paymentProvider: 'mock')).thenAnswer((_) async => PaymentModel(paymentUrl: paymentUrl));
when(mockWsManager.client).thenReturn(null);
when(mockWsManager.initPrivateDirectChatChannel(any)).thenAnswer((_) async => Future.value());
when(mockWsManager.subscribeDirectChatChannel()).thenReturn(null);
@@ -691,7 +691,7 @@ void main() {
final result = await chatRoomCubit.getPaymentLink();
expect(result, paymentUrl);
verify(mockChatRepository.getPaymentLink(id: planId)).called(1);
verify(mockChatRepository.getPaymentLink(id: planId, paymentProvider: 'mock')).called(1);
});
test('getPaymentLink - обрабатывает ошибку', () async {
@@ -730,7 +730,7 @@ void main() {
when(mockChatRepository.getChatRoom(uuid)).thenAnswer((_) async => chatRoomWithPlan);
when(mockChatRepository.getChatMessages(uuid, 1)).thenAnswer((_) async => testChatMessages);
when(mockChatRepository.getChosenPlan(id: planId)).thenAnswer((_) async => chosenPlan);
when(mockChatRepository.getPaymentLink(id: planId)).thenThrow(Exception('Payment error'));
when(mockChatRepository.getPaymentLink(id: planId, paymentProvider: 'mock')).thenThrow(Exception('Payment error'));
when(mockWsManager.client).thenReturn(null);
when(mockWsManager.initPrivateDirectChatChannel(any)).thenAnswer((_) async => Future.value());
when(mockWsManager.subscribeDirectChatChannel()).thenReturn(null);

View File

@@ -8,6 +8,7 @@ import 'package:eatvio_app/features/chat/models/chat_messages_model.dart';
import 'package:eatvio_app/features/chat/models/chat_room_model.dart'
as chat_room;
import 'package:eatvio_app/features/chat/models/chat_room_pagination_model.dart';
import 'package:eatvio_app/features/chat/models/order_id_to_conversation_model.dart';
import 'package:eatvio_app/features/chat/models/payment_model.dart';
import 'package:eatvio_app/features/chat/models/plan_complain_model.dart';
import 'package:eatvio_app/features/chat/repositories/chat_repository.dart';
@@ -303,14 +304,83 @@ void main() {
final expectedPayment = PaymentModel.fromJson(paymentFixture);
when(
mockChatService.getPaymentLink(id: planId),
mockChatService.getPaymentLink(
id: planId,
body: {'payment_provider': 'mock'},
),
).thenAnswer((_) async => expectedPayment);
final result = await chatRepository.getPaymentLink(id: planId);
final result = await chatRepository.getPaymentLink(
id: planId,
paymentProvider: 'mock',
);
expect(result, isA<PaymentModel>());
expect(result.paymentUrl, 'https://example.com/checkout/payment-123');
verify(mockChatService.getPaymentLink(id: planId)).called(1);
verify(
mockChatService.getPaymentLink(
id: planId,
body: {'payment_provider': 'mock'},
),
).called(1);
});
test(
'getPaymentLink - передаёт payment_provider в тело запроса alfa-bank',
() async {
const planId = 123;
final paymentFixture = FixtureReader.readAsMap(
'chat/payments/payment_response_alfa_bank.json',
);
final expectedPayment = PaymentModel.fromJson(paymentFixture);
when(
mockChatService.getPaymentLink(
id: planId,
body: {'payment_provider': 'alfa-bank'},
),
).thenAnswer((_) async => expectedPayment);
final result = await chatRepository.getPaymentLink(
id: planId,
paymentProvider: 'alfa-bank',
);
expect(result, isA<PaymentModel>());
expect(result.paymentUrl, 'https://qr.nspk.ru/ASDFASDFASDFASDF');
expect(result.orderId, 'alfa_c29_k15138_t1712345678_r1234');
expect(result.paymentProvider, 'alfa-bank');
verify(
mockChatService.getPaymentLink(
id: planId,
body: {'payment_provider': 'alfa-bank'},
),
).called(1);
},
);
test('resolveOrderIdToConversation - получает модель по orderId', () async {
const orderId = 'alfa_c29_k15138_t1712345678_r1234';
final fixture = FixtureReader.readAsMap(
'chat/payments/order_id_to_chat_uuid_response.json',
);
final expected = OrderIdToConversationModel.fromJson(fixture);
when(
mockChatService.resolveOrderIdToConversation(orderId: orderId),
).thenAnswer((_) async => expected);
final result = await chatRepository.resolveOrderIdToConversation(
orderId: orderId,
);
expect(result, isA<OrderIdToConversationModel>());
expect(result.chosenPlanId, 12);
expect(result.conversationUuid, "fbd81429-8019-499a-80e8-e3933cc4d872");
verify(
mockChatService.resolveOrderIdToConversation(orderId: orderId),
).called(1);
});
test('getChosenPlan - получает выбранный план', () async {

View File

@@ -4,6 +4,7 @@ import 'package:eatvio_app/features/chat/models/chat_messages_model.dart';
import 'package:eatvio_app/features/chat/models/chat_room_model.dart'
as chat_room;
import 'package:eatvio_app/features/chat/models/chat_room_pagination_model.dart';
import 'package:eatvio_app/features/chat/models/order_id_to_conversation_model.dart';
import 'package:eatvio_app/features/chat/models/payment_model.dart';
import 'package:eatvio_app/features/chat/models/plan_complain_model.dart';
import 'package:eatvio_app/features/chat/services/chat_service.dart';
@@ -221,14 +222,17 @@ void main() {
final expectedPayment = PaymentModel.fromJson(paymentFixture);
when(
mockChatService.getPaymentLink(id: planId),
mockChatService.getPaymentLink(id: planId, body: {'payment_provider': 'mock'}),
).thenAnswer((_) async => expectedPayment);
final result = await mockChatService.getPaymentLink(id: planId);
final result = await mockChatService.getPaymentLink(
id: planId,
body: {'payment_provider': 'mock'},
);
expect(result, isA<PaymentModel>());
expect(result.paymentUrl, 'https://example.com/checkout/payment-123');
verify(mockChatService.getPaymentLink(id: planId)).called(1);
verify(mockChatService.getPaymentLink(id: planId, body: {'payment_provider': 'mock'})).called(1);
});
test('getChosenPlan - возвращает выбранный план', () async {
@@ -291,6 +295,31 @@ void main() {
},
);
test(
'resolveOrderIdToConversation - возвращает модель по orderId',
() async {
const orderId = 'alfa_c29_k15138_t1712345678_r1234';
final fixture = FixtureReader.readAsMap(
'chat/payments/order_id_to_chat_uuid_response.json',
);
final expected = OrderIdToConversationModel.fromJson(fixture);
when(
mockChatService.resolveOrderIdToConversation(orderId: orderId),
).thenAnswer((_) async => expected);
final result = await mockChatService.resolveOrderIdToConversation(
orderId: orderId,
);
expect(result, isA<OrderIdToConversationModel>());
expect(result.chosenPlanId, 12);
verify(
mockChatService.resolveOrderIdToConversation(orderId: orderId),
).called(1);
},
);
test('getChatByUserSlug - возвращает HttpResponse', () async {
const slug = 'user-slug';
const chatId = 'chat-id-123';

View File

@@ -200,20 +200,20 @@ void main() {
.thenAnswer((_) async => chosenPlan);
await cubit.getChosenPlan(id: 1);
when(mockChatRepository.getPaymentLink(id: 1))
when(mockChatRepository.getPaymentLink(id: 1, paymentProvider: 'mock'))
.thenAnswer((_) async => paymentModel);
final result = await cubit.getPaymentLink();
expect(result, equals('https://payment.url'));
verify(mockChatRepository.getPaymentLink(id: 1)).called(1);
verify(mockChatRepository.getPaymentLink(id: 1, paymentProvider: 'mock')).called(1);
});
test('getPaymentLink - возвращает null, если план не выбран', () async {
final result = await cubit.getPaymentLink();
expect(result, isNull);
verifyNever(mockChatRepository.getPaymentLink(id: anyNamed('id')));
verifyNever(mockChatRepository.getPaymentLink(id: anyNamed('id'), paymentProvider: anyNamed('paymentProvider')));
});
test('getPaymentLink - обработка ошибки', () async {
@@ -254,7 +254,7 @@ void main() {
.thenAnswer((_) async => chosenPlan);
await cubit.getChosenPlan(id: 1);
when(mockChatRepository.getPaymentLink(id: 1))
when(mockChatRepository.getPaymentLink(id: 1, paymentProvider: 'mock'))
.thenThrow(Exception('Ошибка'));
final result = await cubit.getPaymentLink();