added download for operation and included fixes for source of payments #639
@@ -1,6 +1,7 @@
|
|||||||
class PaymentOperationDTO {
|
class PaymentOperationDTO {
|
||||||
final String? stepRef;
|
final String? stepRef;
|
||||||
final String? operationRef;
|
final String? operationRef;
|
||||||
|
final String? gateway;
|
||||||
final String? code;
|
final String? code;
|
||||||
final String? state;
|
final String? state;
|
||||||
final String? label;
|
final String? label;
|
||||||
@@ -12,6 +13,7 @@ class PaymentOperationDTO {
|
|||||||
const PaymentOperationDTO({
|
const PaymentOperationDTO({
|
||||||
this.stepRef,
|
this.stepRef,
|
||||||
this.operationRef,
|
this.operationRef,
|
||||||
|
this.gateway,
|
||||||
this.code,
|
this.code,
|
||||||
this.state,
|
this.state,
|
||||||
this.label,
|
this.label,
|
||||||
@@ -25,6 +27,7 @@ class PaymentOperationDTO {
|
|||||||
PaymentOperationDTO(
|
PaymentOperationDTO(
|
||||||
stepRef: _asString(json['stepRef'] ?? json['step_ref']),
|
stepRef: _asString(json['stepRef'] ?? json['step_ref']),
|
||||||
operationRef: _asString(json['operationRef'] ?? json['operation_ref']),
|
operationRef: _asString(json['operationRef'] ?? json['operation_ref']),
|
||||||
|
gateway: _asString(json['gateway']),
|
||||||
code: _asString(json['code']),
|
code: _asString(json['code']),
|
||||||
state: _asString(json['state']),
|
state: _asString(json['state']),
|
||||||
label: _asString(json['label']),
|
label: _asString(json['label']),
|
||||||
@@ -39,6 +42,7 @@ class PaymentOperationDTO {
|
|||||||
Map<String, dynamic> toJson() => <String, dynamic>{
|
Map<String, dynamic> toJson() => <String, dynamic>{
|
||||||
'stepRef': stepRef,
|
'stepRef': stepRef,
|
||||||
'operationRef': operationRef,
|
'operationRef': operationRef,
|
||||||
|
'gateway': gateway,
|
||||||
'code': code,
|
'code': code,
|
||||||
'state': state,
|
'state': state,
|
||||||
'label': label,
|
'label': label,
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import 'package:pshared/data/dto/payment/operation.dart';
|
import 'package:pshared/data/dto/payment/operation.dart';
|
||||||
import 'package:pshared/models/payment/execution_operation.dart';
|
import 'package:pshared/models/payment/execution_operation.dart';
|
||||||
|
|
||||||
|
|
||||||
extension PaymentOperationDTOMapper on PaymentOperationDTO {
|
extension PaymentOperationDTOMapper on PaymentOperationDTO {
|
||||||
PaymentExecutionOperation toDomain() => PaymentExecutionOperation(
|
PaymentExecutionOperation toDomain() => PaymentExecutionOperation(
|
||||||
stepRef: stepRef,
|
stepRef: stepRef,
|
||||||
operationRef: operationRef,
|
operationRef: operationRef,
|
||||||
|
gateway: gateway,
|
||||||
code: code,
|
code: code,
|
||||||
state: state,
|
state: state,
|
||||||
label: label,
|
label: label,
|
||||||
@@ -20,6 +20,7 @@ extension PaymentExecutionOperationMapper on PaymentExecutionOperation {
|
|||||||
PaymentOperationDTO toDTO() => PaymentOperationDTO(
|
PaymentOperationDTO toDTO() => PaymentOperationDTO(
|
||||||
stepRef: stepRef,
|
stepRef: stepRef,
|
||||||
operationRef: operationRef,
|
operationRef: operationRef,
|
||||||
|
gateway: gateway,
|
||||||
code: code,
|
code: code,
|
||||||
state: state,
|
state: state,
|
||||||
label: label,
|
label: label,
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
class PaymentExecutionOperation {
|
class PaymentExecutionOperation {
|
||||||
final String? stepRef;
|
final String? stepRef;
|
||||||
final String? operationRef;
|
final String? operationRef;
|
||||||
|
final String? gateway;
|
||||||
final String? code;
|
final String? code;
|
||||||
final String? state;
|
final String? state;
|
||||||
final String? label;
|
final String? label;
|
||||||
@@ -12,6 +13,7 @@ class PaymentExecutionOperation {
|
|||||||
const PaymentExecutionOperation({
|
const PaymentExecutionOperation({
|
||||||
required this.stepRef,
|
required this.stepRef,
|
||||||
required this.operationRef,
|
required this.operationRef,
|
||||||
|
required this.gateway,
|
||||||
required this.code,
|
required this.code,
|
||||||
required this.state,
|
required this.state,
|
||||||
required this.label,
|
required this.label,
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import 'package:pshared/models/payment/methods/type.dart';
|
import 'package:pshared/models/payment/methods/type.dart';
|
||||||
import 'package:pshared/models/payment/status.dart';
|
import 'package:pshared/models/payment/status.dart';
|
||||||
|
|
||||||
|
|
||||||
class OperationItem {
|
class OperationItem {
|
||||||
final OperationStatus status;
|
final OperationStatus status;
|
||||||
final String? fileName;
|
final String? fileName;
|
||||||
@@ -11,6 +10,8 @@ class OperationItem {
|
|||||||
final String toCurrency;
|
final String toCurrency;
|
||||||
final String payId;
|
final String payId;
|
||||||
final String? paymentRef;
|
final String? paymentRef;
|
||||||
|
final String? operationRef;
|
||||||
|
final String? gatewayService;
|
||||||
final String? cardNumber;
|
final String? cardNumber;
|
||||||
final PaymentMethod? paymentMethod;
|
final PaymentMethod? paymentMethod;
|
||||||
final String name;
|
final String name;
|
||||||
@@ -26,6 +27,8 @@ class OperationItem {
|
|||||||
required this.toCurrency,
|
required this.toCurrency,
|
||||||
required this.payId,
|
required this.payId,
|
||||||
this.paymentRef,
|
this.paymentRef,
|
||||||
|
this.operationRef,
|
||||||
|
this.gatewayService,
|
||||||
this.cardNumber,
|
this.cardNumber,
|
||||||
this.paymentMethod,
|
this.paymentMethod,
|
||||||
required this.name,
|
required this.name,
|
||||||
|
|||||||
@@ -8,27 +8,27 @@ class PaymentDocumentsService {
|
|||||||
static final _logger = Logger('service.payment_documents');
|
static final _logger = Logger('service.payment_documents');
|
||||||
static const String _objectType = Services.payments;
|
static const String _objectType = Services.payments;
|
||||||
|
|
||||||
static Future<DownloadedFile> getAct(
|
static Future<DownloadedFile> getOperationDocument(
|
||||||
String organizationRef,
|
String organizationRef,
|
||||||
String paymentRef, {
|
String gatewayService,
|
||||||
String? operationRef,
|
String operationRef,
|
||||||
}) async {
|
) async {
|
||||||
final query = <String, String>{'payment_ref': paymentRef};
|
final query = <String, String>{
|
||||||
final operationRefValue = operationRef;
|
'gateway_service': gatewayService,
|
||||||
if (operationRefValue != null && operationRefValue.isNotEmpty) {
|
'operation_ref': operationRef,
|
||||||
query['operation_ref'] = operationRefValue;
|
};
|
||||||
query['operationRef'] = operationRefValue;
|
|
||||||
}
|
|
||||||
final queryString = Uri(queryParameters: query).query;
|
final queryString = Uri(queryParameters: query).query;
|
||||||
final url = '/documents/act/$organizationRef?$queryString';
|
final url = '/documents/operation/$organizationRef?$queryString';
|
||||||
_logger.fine('Downloading act document for payment $paymentRef');
|
_logger.fine(
|
||||||
|
'Downloading operation document for operation $operationRef in gateway $gatewayService',
|
||||||
|
);
|
||||||
final response = await AuthorizationService.getGETBinaryResponse(
|
final response = await AuthorizationService.getGETBinaryResponse(
|
||||||
_objectType,
|
_objectType,
|
||||||
url,
|
url,
|
||||||
);
|
);
|
||||||
final filename =
|
final filename =
|
||||||
_filenameFromDisposition(response.header('content-disposition')) ??
|
_filenameFromDisposition(response.header('content-disposition')) ??
|
||||||
'act_$paymentRef.pdf';
|
'operation_$operationRef.pdf';
|
||||||
final mimeType = response.header('content-type') ?? 'application/pdf';
|
final mimeType = response.header('content-type') ?? 'application/pdf';
|
||||||
return DownloadedFile(
|
return DownloadedFile(
|
||||||
bytes: response.bytes,
|
bytes: response.bytes,
|
||||||
|
|||||||
@@ -26,9 +26,20 @@ class PaymentDetailsController extends ChangeNotifier {
|
|||||||
bool get canDownload {
|
bool get canDownload {
|
||||||
final current = _payment;
|
final current = _payment;
|
||||||
if (current == null) return false;
|
if (current == null) return false;
|
||||||
final status = statusFromPayment(current);
|
if (statusFromPayment(current) != OperationStatus.success) return false;
|
||||||
final paymentRef = current.paymentRef ?? '';
|
return primaryOperationDocumentRequest != null;
|
||||||
return status == OperationStatus.success && paymentRef.trim().isNotEmpty;
|
}
|
||||||
|
|
||||||
|
OperationDocumentRequestModel? get primaryOperationDocumentRequest {
|
||||||
|
final current = _payment;
|
||||||
|
if (current == null) return null;
|
||||||
|
for (final operation in current.operations) {
|
||||||
|
final request = operationDocumentRequest(operation);
|
||||||
|
if (request != null) {
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
OperationDocumentRequestModel? operationDocumentRequest(
|
OperationDocumentRequestModel? operationDocumentRequest(
|
||||||
@@ -37,18 +48,17 @@ class PaymentDetailsController extends ChangeNotifier {
|
|||||||
final current = _payment;
|
final current = _payment;
|
||||||
if (current == null) return null;
|
if (current == null) return null;
|
||||||
|
|
||||||
final paymentRef = current.paymentRef?.trim() ?? '';
|
|
||||||
if (paymentRef.isEmpty) return null;
|
|
||||||
|
|
||||||
final operationRef = operation.operationRef;
|
final operationRef = operation.operationRef;
|
||||||
if (operationRef == null || operationRef.isEmpty) return null;
|
if (operationRef == null || operationRef.isEmpty) return null;
|
||||||
|
final gatewayService = operation.gateway;
|
||||||
|
if (gatewayService == null || gatewayService.isEmpty) return null;
|
||||||
|
|
||||||
final pair = parseOperationCodePair(operation.code);
|
final pair = parseOperationCodePair(operation.code);
|
||||||
if (pair == null) return null;
|
if (pair == null) return null;
|
||||||
if (pair.operation != 'card_payout' || pair.action != 'send') return null;
|
if (pair.operation != 'card_payout' || pair.action != 'send') return null;
|
||||||
|
|
||||||
return OperationDocumentRequestModel(
|
return OperationDocumentRequestModel(
|
||||||
paymentRef: paymentRef,
|
gatewayService: gatewayService,
|
||||||
operationRef: operationRef,
|
operationRef: operationRef,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
class OperationDocumentRequestModel {
|
class OperationDocumentRequestModel {
|
||||||
final String paymentRef;
|
final String gatewayService;
|
||||||
final String operationRef;
|
final String operationRef;
|
||||||
|
|
||||||
const OperationDocumentRequestModel({
|
const OperationDocumentRequestModel({
|
||||||
required this.paymentRef,
|
required this.gatewayService,
|
||||||
required this.operationRef,
|
required this.operationRef,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
9
frontend/pweb/lib/models/report/operation/document.dart
Normal file
9
frontend/pweb/lib/models/report/operation/document.dart
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
class OperationDocumentInfo {
|
||||||
|
final String operationRef;
|
||||||
|
final String gatewayService;
|
||||||
|
|
||||||
|
const OperationDocumentInfo({
|
||||||
|
required this.operationRef,
|
||||||
|
required this.gatewayService,
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -65,7 +65,15 @@ class _PaymentDetailsView extends StatelessWidget {
|
|||||||
payment: payment,
|
payment: payment,
|
||||||
onBack: () => _handleBack(context),
|
onBack: () => _handleBack(context),
|
||||||
onDownloadAct: controller.canDownload
|
onDownloadAct: controller.canDownload
|
||||||
? () => downloadPaymentAct(context, payment.paymentRef ?? '')
|
? () {
|
||||||
|
final request = controller.primaryOperationDocumentRequest;
|
||||||
|
if (request == null) return;
|
||||||
|
downloadPaymentAct(
|
||||||
|
context,
|
||||||
|
gatewayService: request.gatewayService,
|
||||||
|
operationRef: request.operationRef,
|
||||||
|
);
|
||||||
|
}
|
||||||
: null,
|
: null,
|
||||||
canDownloadOperationDocument:
|
canDownloadOperationDocument:
|
||||||
controller.canDownloadOperationDocument,
|
controller.canDownloadOperationDocument,
|
||||||
@@ -74,7 +82,7 @@ class _PaymentDetailsView extends StatelessWidget {
|
|||||||
if (request == null) return;
|
if (request == null) return;
|
||||||
downloadPaymentAct(
|
downloadPaymentAct(
|
||||||
context,
|
context,
|
||||||
request.paymentRef,
|
gatewayService: request.gatewayService,
|
||||||
operationRef: request.operationRef,
|
operationRef: request.operationRef,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import 'package:pweb/utils/report/download_act.dart';
|
|||||||
|
|
||||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||||
|
|
||||||
|
|
||||||
class OperationRow {
|
class OperationRow {
|
||||||
static DataRow build(OperationItem op, BuildContext context) {
|
static DataRow build(OperationItem op, BuildContext context) {
|
||||||
final isUnknownDate = op.date.millisecondsSinceEpoch == 0;
|
final isUnknownDate = op.date.millisecondsSinceEpoch == 0;
|
||||||
@@ -20,18 +19,25 @@ class OperationRow {
|
|||||||
: '${TimeOfDay.fromDateTime(localDate).format(context)}\n'
|
: '${TimeOfDay.fromDateTime(localDate).format(context)}\n'
|
||||||
'${localDate.toIso8601String().split("T").first}';
|
'${localDate.toIso8601String().split("T").first}';
|
||||||
|
|
||||||
final canDownload = op.status == OperationStatus.success &&
|
final canDownload =
|
||||||
(op.paymentRef ?? '').trim().isNotEmpty;
|
op.status == OperationStatus.success &&
|
||||||
|
(op.operationRef ?? '').trim().isNotEmpty &&
|
||||||
|
(op.gatewayService ?? '').trim().isNotEmpty;
|
||||||
|
|
||||||
final documentCell = canDownload
|
final documentCell = canDownload
|
||||||
? TextButton.icon(
|
? TextButton.icon(
|
||||||
onPressed: () => downloadPaymentAct(context, op.paymentRef ?? ''),
|
onPressed: () => downloadPaymentAct(
|
||||||
|
context,
|
||||||
|
gatewayService: op.gatewayService ?? '',
|
||||||
|
operationRef: op.operationRef ?? '',
|
||||||
|
),
|
||||||
icon: const Icon(Icons.download),
|
icon: const Icon(Icons.download),
|
||||||
label: Text(loc.downloadAct),
|
label: Text(loc.downloadAct),
|
||||||
)
|
)
|
||||||
: Text(op.fileName ?? '');
|
: Text(op.fileName ?? '');
|
||||||
|
|
||||||
return DataRow(cells: [
|
return DataRow(
|
||||||
|
cells: [
|
||||||
DataCell(OperationStatusBadge(status: op.status)),
|
DataCell(OperationStatusBadge(status: op.status)),
|
||||||
DataCell(documentCell),
|
DataCell(documentCell),
|
||||||
DataCell(Text('${amountToString(op.amount)} ${op.currency}')),
|
DataCell(Text('${amountToString(op.amount)} ${op.currency}')),
|
||||||
@@ -41,6 +47,7 @@ class OperationRow {
|
|||||||
DataCell(Text(op.name)),
|
DataCell(Text(op.name)),
|
||||||
DataCell(Text(dateLabel)),
|
DataCell(Text(dateLabel)),
|
||||||
DataCell(Text(op.comment)),
|
DataCell(Text(op.comment)),
|
||||||
]);
|
],
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,18 +10,18 @@ import 'package:pweb/utils/error/snackbar.dart';
|
|||||||
|
|
||||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||||
|
|
||||||
|
|
||||||
Future<void> downloadPaymentAct(
|
Future<void> downloadPaymentAct(
|
||||||
BuildContext context,
|
BuildContext context, {
|
||||||
String paymentRef, {
|
required String gatewayService,
|
||||||
String? operationRef,
|
required String operationRef,
|
||||||
}) async {
|
}) async {
|
||||||
final organizations = context.read<OrganizationsProvider>();
|
final organizations = context.read<OrganizationsProvider>();
|
||||||
if (!organizations.isOrganizationSet) {
|
if (!organizations.isOrganizationSet) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final trimmed = paymentRef.trim();
|
final gateway = gatewayService.trim();
|
||||||
if (trimmed.isEmpty) {
|
final operation = operationRef.trim();
|
||||||
|
if (gateway.isEmpty || operation.isEmpty) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -29,10 +29,10 @@ Future<void> downloadPaymentAct(
|
|||||||
await executeActionWithNotification(
|
await executeActionWithNotification(
|
||||||
context: context,
|
context: context,
|
||||||
action: () async {
|
action: () async {
|
||||||
final file = await PaymentDocumentsService.getAct(
|
final file = await PaymentDocumentsService.getOperationDocument(
|
||||||
organizations.current.id,
|
organizations.current.id,
|
||||||
trimmed,
|
gateway,
|
||||||
operationRef: operationRef,
|
operation,
|
||||||
);
|
);
|
||||||
await downloadFile(file);
|
await downloadFile(file);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -4,6 +4,9 @@ import 'package:pshared/models/payment/state.dart';
|
|||||||
import 'package:pshared/models/payment/status.dart';
|
import 'package:pshared/models/payment/status.dart';
|
||||||
import 'package:pshared/utils/money.dart';
|
import 'package:pshared/utils/money.dart';
|
||||||
|
|
||||||
|
import 'package:pweb/models/report/operation/document.dart';
|
||||||
|
import 'package:pweb/utils/payment/operation_code.dart';
|
||||||
|
|
||||||
|
|
||||||
OperationItem mapPaymentToOperation(Payment payment) {
|
OperationItem mapPaymentToOperation(Payment payment) {
|
||||||
final debit = payment.lastQuote?.amounts?.sourceDebitTotal;
|
final debit = payment.lastQuote?.amounts?.sourceDebitTotal;
|
||||||
@@ -33,6 +36,7 @@ OperationItem mapPaymentToOperation(Payment payment) {
|
|||||||
payment.state,
|
payment.state,
|
||||||
]) ??
|
]) ??
|
||||||
'';
|
'';
|
||||||
|
final operationDocument = _resolveOperationDocument(payment);
|
||||||
|
|
||||||
return OperationItem(
|
return OperationItem(
|
||||||
status: statusFromPayment(payment),
|
status: statusFromPayment(payment),
|
||||||
@@ -43,6 +47,8 @@ OperationItem mapPaymentToOperation(Payment payment) {
|
|||||||
toCurrency: toCurrency,
|
toCurrency: toCurrency,
|
||||||
payId: payId,
|
payId: payId,
|
||||||
paymentRef: payment.paymentRef,
|
paymentRef: payment.paymentRef,
|
||||||
|
operationRef: operationDocument?.operationRef,
|
||||||
|
gatewayService: operationDocument?.gatewayService,
|
||||||
cardNumber: null,
|
cardNumber: null,
|
||||||
name: name,
|
name: name,
|
||||||
date: resolvePaymentDate(payment),
|
date: resolvePaymentDate(payment),
|
||||||
@@ -50,6 +56,25 @@ OperationItem mapPaymentToOperation(Payment payment) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
OperationDocumentInfo? _resolveOperationDocument(Payment payment) {
|
||||||
|
for (final operation in payment.operations) {
|
||||||
|
final operationRef = operation.operationRef;
|
||||||
|
final gatewayService = operation.gateway;
|
||||||
|
if (operationRef == null || operationRef.isEmpty) continue;
|
||||||
|
if (gatewayService == null || gatewayService.isEmpty) continue;
|
||||||
|
|
||||||
|
final pair = parseOperationCodePair(operation.code);
|
||||||
|
if (pair == null) continue;
|
||||||
|
if (pair.operation != 'card_payout' || pair.action != 'send') continue;
|
||||||
|
|
|||||||
|
|
||||||
|
return OperationDocumentInfo(
|
||||||
|
operationRef: operationRef,
|
||||||
|
gatewayService: gatewayService,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
OperationStatus statusFromPayment(Payment payment) {
|
OperationStatus statusFromPayment(Payment payment) {
|
||||||
switch (payment.orchestrationState) {
|
switch (payment.orchestrationState) {
|
||||||
case PaymentOrchestrationState.failed:
|
case PaymentOrchestrationState.failed:
|
||||||
|
|||||||
Reference in New Issue
Block a user
вот, говорил: вот эту штуку хардкодить не надо, лучше вынести куда-то в решающее правило, а не запаковывать внутрь провайдера, где найти ее непросто.