TRON -> TRON_MAINNET
This commit is contained in:
11
frontend/pshared/lib/models/file/downloaded_file.dart
Normal file
11
frontend/pshared/lib/models/file/downloaded_file.dart
Normal file
@@ -0,0 +1,11 @@
|
||||
class DownloadedFile {
|
||||
final List<int> bytes;
|
||||
final String filename;
|
||||
final String mimeType;
|
||||
|
||||
const DownloadedFile({
|
||||
required this.bytes,
|
||||
required this.filename,
|
||||
required this.mimeType,
|
||||
});
|
||||
}
|
||||
@@ -10,6 +10,7 @@ class OperationItem {
|
||||
final double toAmount;
|
||||
final String toCurrency;
|
||||
final String payId;
|
||||
final String? paymentRef;
|
||||
final String? cardNumber;
|
||||
final PaymentMethod? paymentMethod;
|
||||
final String name;
|
||||
@@ -24,10 +25,11 @@ class OperationItem {
|
||||
required this.toAmount,
|
||||
required this.toCurrency,
|
||||
required this.payId,
|
||||
this.paymentRef,
|
||||
this.cardNumber,
|
||||
this.paymentMethod,
|
||||
required this.name,
|
||||
required this.date,
|
||||
required this.comment,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,6 +79,11 @@ class AuthorizationService {
|
||||
return httpr.getGETResponse(service, url, authToken: token);
|
||||
}
|
||||
|
||||
static Future<httpr.BinaryResponse> getGETBinaryResponse(String service, String url) async {
|
||||
final token = await TokenService.getAccessTokenSafe();
|
||||
return httpr.getBinaryGETResponse(service, url, authToken: token);
|
||||
}
|
||||
|
||||
static Future<Map<String, dynamic>> getPOSTResponse(String service, String url, Map<String, dynamic> body) async {
|
||||
final token = await TokenService.getAccessTokenSafe();
|
||||
return httpr.getPOSTResponse(service, url, body, authToken: token);
|
||||
|
||||
44
frontend/pshared/lib/service/payment/documents.dart
Normal file
44
frontend/pshared/lib/service/payment/documents.dart
Normal file
@@ -0,0 +1,44 @@
|
||||
import 'package:logging/logging.dart';
|
||||
|
||||
import 'package:pshared/models/file/downloaded_file.dart';
|
||||
import 'package:pshared/service/authorization/service.dart';
|
||||
import 'package:pshared/service/services.dart';
|
||||
|
||||
|
||||
class PaymentDocumentsService {
|
||||
static final _logger = Logger('service.payment_documents');
|
||||
static const String _objectType = Services.payments;
|
||||
|
||||
static Future<DownloadedFile> getAct(String organizationRef, String paymentRef) async {
|
||||
final encodedRef = Uri.encodeQueryComponent(paymentRef);
|
||||
final url = '/documents/act/$organizationRef?payment_ref=$encodedRef';
|
||||
_logger.fine('Downloading act document for payment $paymentRef');
|
||||
final response = await AuthorizationService.getGETBinaryResponse(_objectType, url);
|
||||
final filename = _filenameFromDisposition(response.header('content-disposition')) ??
|
||||
'act_$paymentRef.pdf';
|
||||
final mimeType = response.header('content-type') ?? 'application/pdf';
|
||||
return DownloadedFile(
|
||||
bytes: response.bytes,
|
||||
filename: filename,
|
||||
mimeType: mimeType,
|
||||
);
|
||||
}
|
||||
|
||||
static String? _filenameFromDisposition(String? disposition) {
|
||||
if (disposition == null || disposition.isEmpty) return null;
|
||||
final parts = disposition.split(';');
|
||||
for (final part in parts) {
|
||||
final trimmed = part.trim();
|
||||
if (trimmed.toLowerCase().startsWith('filename=')) {
|
||||
var value = trimmed.substring('filename='.length).trim();
|
||||
if (value.startsWith('"') && value.endsWith('"') && value.length > 1) {
|
||||
value = value.substring(1, value.length - 1);
|
||||
}
|
||||
if (value.isNotEmpty) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -163,3 +163,49 @@ Future<FileUploaded?> getFileUploadResponse(String service, String url, String f
|
||||
final streamedResponse = await _fileUploadRequest(service, url, fileName, fileType, mediaType, bytes, authToken: authToken);
|
||||
return FileUploaded.fromJson(await _handleResponse(http.Response.fromStream(streamedResponse)));
|
||||
}
|
||||
|
||||
class BinaryResponse {
|
||||
final List<int> bytes;
|
||||
final Map<String, String> headers;
|
||||
final int statusCode;
|
||||
|
||||
const BinaryResponse({
|
||||
required this.bytes,
|
||||
required this.headers,
|
||||
required this.statusCode,
|
||||
});
|
||||
|
||||
String? header(String key) => headers[key.toLowerCase()];
|
||||
}
|
||||
|
||||
Future<BinaryResponse> getBinaryGETResponse(String service, String url, {String? authToken}) async {
|
||||
late http.Response response;
|
||||
try {
|
||||
response = await getRequest(service, url, authToken: authToken);
|
||||
} catch (e) {
|
||||
throw ConnectivityError(message: e.toString());
|
||||
}
|
||||
|
||||
if (response.statusCode < 200 || response.statusCode >= 300) {
|
||||
late HTTPMessage message;
|
||||
try {
|
||||
message = HTTPMessage.fromJson(json.decode(response.body));
|
||||
} catch (e) {
|
||||
_throwConnectivityError(response, e);
|
||||
}
|
||||
|
||||
late ErrorResponse error;
|
||||
try {
|
||||
error = ErrorResponse.fromJson(message.data);
|
||||
} catch (e) {
|
||||
_throwConnectivityError(response, e);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
|
||||
return BinaryResponse(
|
||||
bytes: response.bodyBytes,
|
||||
headers: response.headers,
|
||||
statusCode: response.statusCode,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ environment:
|
||||
# Add regular dependencies here.
|
||||
dependencies:
|
||||
analyzer: ^10.0.0
|
||||
json_annotation: ^4.9.0
|
||||
json_annotation: ^4.10.0
|
||||
http: ^1.1.0
|
||||
provider: ^6.0.5
|
||||
flutter:
|
||||
|
||||
@@ -488,6 +488,14 @@
|
||||
"howItWorks": "How it works?",
|
||||
"exampleTitle": "File Format & Sample",
|
||||
"downloadSampleCSV": "Download sample.csv",
|
||||
"downloadAct": "Download Act",
|
||||
"@downloadAct": {
|
||||
"description": "Button label for downloading the acceptance act PDF"
|
||||
},
|
||||
"downloadActError": "Failed to download act",
|
||||
"@downloadActError": {
|
||||
"description": "Error message shown when act download fails"
|
||||
},
|
||||
"tokenColumn": "Token (required)",
|
||||
"currency": "Currency",
|
||||
"amount": "Amount",
|
||||
|
||||
@@ -488,6 +488,14 @@
|
||||
"howItWorks": "Как это работает?",
|
||||
"exampleTitle": "Формат файла и образец",
|
||||
"downloadSampleCSV": "Скачать sample.csv",
|
||||
"downloadAct": "Скачать акт",
|
||||
"@downloadAct": {
|
||||
"description": "Button label for downloading the acceptance act PDF"
|
||||
},
|
||||
"downloadActError": "Не удалось скачать акт",
|
||||
"@downloadActError": {
|
||||
"description": "Error message shown when act download fails"
|
||||
},
|
||||
"tokenColumn": "Токен (обязательно)",
|
||||
"currency": "Валюта",
|
||||
"amount": "Сумма",
|
||||
|
||||
@@ -104,6 +104,7 @@ class _OperationHistoryPageState extends State<OperationHistoryPage> {
|
||||
toAmount: toAmount,
|
||||
toCurrency: toCurrency,
|
||||
payId: payId,
|
||||
paymentRef: payment.paymentRef,
|
||||
cardNumber: null,
|
||||
name: name,
|
||||
date: _resolvePaymentDate(payment),
|
||||
@@ -141,6 +142,7 @@ class _OperationHistoryPageState extends State<OperationHistoryPage> {
|
||||
return OperationStatus.processing;
|
||||
|
||||
case 'settled':
|
||||
case 'success':
|
||||
return OperationStatus.success;
|
||||
|
||||
case 'failed':
|
||||
|
||||
@@ -1,23 +1,43 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import 'package:pshared/models/payment/operation.dart';
|
||||
import 'package:pshared/models/payment/status.dart';
|
||||
import 'package:pshared/provider/organizations.dart';
|
||||
import 'package:pshared/service/payment/documents.dart';
|
||||
import 'package:pshared/utils/currency.dart';
|
||||
|
||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||
import 'package:pweb/pages/report/table/badge.dart';
|
||||
import 'package:pweb/utils/download.dart';
|
||||
import 'package:pweb/utils/error/snackbar.dart';
|
||||
|
||||
|
||||
class OperationRow {
|
||||
static DataRow build(OperationItem op, BuildContext context) {
|
||||
final isUnknownDate = op.date.millisecondsSinceEpoch == 0;
|
||||
final localDate = op.date.toLocal();
|
||||
final loc = AppLocalizations.of(context)!;
|
||||
final dateLabel = isUnknownDate
|
||||
? '-'
|
||||
: '${TimeOfDay.fromDateTime(localDate).format(context)}\n'
|
||||
'${localDate.toIso8601String().split("T").first}';
|
||||
|
||||
final canDownload = op.status == OperationStatus.success &&
|
||||
(op.paymentRef ?? '').trim().isNotEmpty;
|
||||
|
||||
final documentCell = canDownload
|
||||
? TextButton.icon(
|
||||
onPressed: () => _downloadAct(context, op),
|
||||
icon: const Icon(Icons.download),
|
||||
label: Text(loc.downloadAct),
|
||||
)
|
||||
: Text(op.fileName ?? '');
|
||||
|
||||
return DataRow(cells: [
|
||||
DataCell(OperationStatusBadge(status: op.status)),
|
||||
DataCell(Text(op.fileName ?? '')),
|
||||
DataCell(documentCell),
|
||||
DataCell(Text('${amountToString(op.amount)} ${op.currency}')),
|
||||
DataCell(Text('${amountToString(op.toAmount)} ${op.toCurrency}')),
|
||||
DataCell(Text(op.payId)),
|
||||
@@ -27,4 +47,28 @@ class OperationRow {
|
||||
DataCell(Text(op.comment)),
|
||||
]);
|
||||
}
|
||||
|
||||
static Future<void> _downloadAct(BuildContext context, OperationItem op) async {
|
||||
final organizations = context.read<OrganizationsProvider>();
|
||||
if (!organizations.isOrganizationSet) {
|
||||
return;
|
||||
}
|
||||
final paymentRef = (op.paymentRef ?? '').trim();
|
||||
if (paymentRef.isEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
final loc = AppLocalizations.of(context)!;
|
||||
await executeActionWithNotification(
|
||||
context: context,
|
||||
action: () async {
|
||||
final file = await PaymentDocumentsService.getAct(
|
||||
organizations.current.id,
|
||||
paymentRef,
|
||||
);
|
||||
await downloadFile(file);
|
||||
},
|
||||
errorMessage: loc.downloadActError,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
25
frontend/pweb/lib/utils/download.dart
Normal file
25
frontend/pweb/lib/utils/download.dart
Normal file
@@ -0,0 +1,25 @@
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:universal_html/html.dart' as html;
|
||||
|
||||
import 'package:pshared/models/file/downloaded_file.dart';
|
||||
|
||||
|
||||
Future<void> downloadFile(DownloadedFile file) async {
|
||||
final blob = html.Blob(
|
||||
[Uint8List.fromList(file.bytes)],
|
||||
file.mimeType,
|
||||
);
|
||||
|
||||
final url = html.Url.createObjectUrlFromBlob(blob);
|
||||
|
||||
final anchor = html.AnchorElement(href: url)
|
||||
..download = file.filename
|
||||
..style.display = 'none';
|
||||
|
||||
html.document.body!.append(anchor);
|
||||
anchor.click();
|
||||
anchor.remove();
|
||||
|
||||
html.Url.revokeObjectUrl(url);
|
||||
}
|
||||
@@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
|
||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||
# In Windows, build-name is used as the major, minor, and patch parts
|
||||
# of the product and file versions while build-number is used as the build suffix.
|
||||
version: 2.5.0+495
|
||||
version: 2.6.0+507
|
||||
|
||||
environment:
|
||||
sdk: ^3.8.1
|
||||
@@ -53,7 +53,7 @@ dependencies:
|
||||
collection: ^1.18.0
|
||||
icann_tlds: ^1.0.0
|
||||
flutter_timezone: ^5.0.1
|
||||
json_annotation: ^4.9.0
|
||||
json_annotation: ^4.10.0
|
||||
go_router: ^17.0.0
|
||||
jovial_svg: ^1.1.23
|
||||
cached_network_image: ^3.4.1
|
||||
@@ -70,6 +70,7 @@ dependencies:
|
||||
dotted_border: ^3.1.0
|
||||
qr_flutter: ^4.1.0
|
||||
duration: ^4.0.3
|
||||
universal_html: ^2.3.0
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user