intent reference generation + propagation

This commit is contained in:
Stephan D
2026-02-26 18:43:44 +01:00
parent 0f95f898a8
commit 7661038868
18 changed files with 816 additions and 24 deletions

View File

@@ -1,13 +1,16 @@
import 'package:pshared/api/requests/payment/base.dart';
class InitiatePaymentsRequest extends PaymentBaseRequest {
final String quoteRef;
final String? intentRef;
final List<String>? intentRefs;
const InitiatePaymentsRequest({
required super.idempotencyKey,
super.metadata,
required this.quoteRef,
this.intentRef,
this.intentRefs,
});
factory InitiatePaymentsRequest.fromJson(Map<String, dynamic> json) {
@@ -17,6 +20,10 @@ class InitiatePaymentsRequest extends PaymentBaseRequest {
(key, value) => MapEntry(key, value as String),
),
quoteRef: json['quoteRef'] as String,
intentRef: json['intentRef'] as String?,
intentRefs: (json['intentRefs'] as List<dynamic>?)
?.map((value) => value as String)
.toList(),
);
}
@@ -26,6 +33,8 @@ class InitiatePaymentsRequest extends PaymentBaseRequest {
'idempotencyKey': idempotencyKey,
'metadata': metadata,
'quoteRef': quoteRef,
if (intentRef != null) 'intentRef': intentRef,
if (intentRefs != null) 'intentRefs': intentRefs,
};
}
}

View File

@@ -9,11 +9,18 @@ part 'payment_quote.g.dart';
@JsonSerializable()
class PaymentQuoteDTO {
final String? quoteRef;
final String? intentRef;
final QuoteAmountsDTO? amounts;
final QuoteFeesDTO? fees;
final FxQuoteDTO? fxQuote;
const PaymentQuoteDTO({this.quoteRef, this.amounts, this.fees, this.fxQuote});
const PaymentQuoteDTO({
this.quoteRef,
this.intentRef,
this.amounts,
this.fees,
this.fxQuote,
});
factory PaymentQuoteDTO.fromJson(Map<String, dynamic> json) =>
_$PaymentQuoteDTOFromJson(json);

View File

@@ -7,6 +7,7 @@ import 'package:pshared/models/payment/quote/quote.dart';
extension PaymentQuoteDTOMapper on PaymentQuoteDTO {
PaymentQuote toDomain({String? idempotencyKey}) => PaymentQuote(
quoteRef: quoteRef,
intentRef: intentRef,
idempotencyKey: idempotencyKey,
amounts: amounts?.toDomain(),
fees: fees?.toDomain(),
@@ -17,6 +18,7 @@ extension PaymentQuoteDTOMapper on PaymentQuoteDTO {
extension PaymentQuoteMapper on PaymentQuote {
PaymentQuoteDTO toDTO() => PaymentQuoteDTO(
quoteRef: quoteRef,
intentRef: intentRef,
amounts: amounts?.toDTO(),
fees: fees?.toDTO(),
fxQuote: fxQuote?.toDTO(),

View File

@@ -4,6 +4,7 @@ import 'package:pshared/models/payment/quote/fees.dart';
class PaymentQuote {
final String? quoteRef;
final String? intentRef;
final String? idempotencyKey;
final QuoteAmounts? amounts;
final QuoteFees? fees;
@@ -11,6 +12,7 @@ class PaymentQuote {
const PaymentQuote({
required this.quoteRef,
required this.intentRef,
required this.idempotencyKey,
required this.amounts,
required this.fees,

View File

@@ -7,7 +7,6 @@ import 'package:pshared/provider/resource.dart';
import 'package:pshared/service/payment/multiple.dart';
import 'package:pshared/utils/exception.dart';
class MultiPaymentProvider extends ChangeNotifier {
late OrganizationsProvider _organization;
late MultiQuotationProvider _quotation;
@@ -31,6 +30,8 @@ class MultiPaymentProvider extends ChangeNotifier {
Future<List<Payment>> pay({
String? idempotencyKey,
Map<String, String>? metadata,
String? intentRef,
List<String>? intentRefs,
}) async {
if (!_organization.isOrganizationSet) {
throw StateError('Organization is not set');
@@ -53,6 +54,8 @@ class MultiPaymentProvider extends ChangeNotifier {
quoteRef,
idempotencyKey: idempotencyKey,
metadata: metadata,
intentRef: intentRef,
intentRefs: intentRefs,
);
_setResource(

View File

@@ -13,7 +13,6 @@ import 'package:pshared/models/payment/quote/quotes.dart';
import 'package:pshared/service/authorization/service.dart';
import 'package:pshared/service/services.dart';
class MultiplePaymentsService {
static final _logger = Logger('service.payment.multiple');
static const String _objectType = Services.payments;
@@ -38,6 +37,8 @@ class MultiplePaymentsService {
String quoteRef, {
String? idempotencyKey,
Map<String, String>? metadata,
String? intentRef,
List<String>? intentRefs,
}) async {
_logger.fine(
'Executing multiple payments for quote $quoteRef in $organizationRef',
@@ -46,6 +47,8 @@ class MultiplePaymentsService {
idempotencyKey: idempotencyKey ?? const Uuid().v4(),
quoteRef: quoteRef,
metadata: metadata,
intentRef: intentRef,
intentRefs: intentRefs,
);
final response = await AuthorizationService.getPOSTResponse(

View File

@@ -114,6 +114,7 @@ void main() {
'idempotencyKey': 'idem-1',
'quote': {
'quoteRef': 'q-1',
'intentRef': 'intent-1',
'amounts': {
'sourcePrincipal': {'amount': '10', 'currency': 'USDT'},
'sourceDebitTotal': {'amount': '10.75', 'currency': 'USDT'},
@@ -148,6 +149,7 @@ void main() {
});
expect(response.quote.fxQuote?.pricedAtUnixMs, equals(1771945907000));
expect(response.quote.intentRef, equals('intent-1'));
expect(response.quote.amounts?.sourceDebitTotal?.amount, equals('10.75'));
expect(response.quote.fees?.lines?.length, equals(1));
});
@@ -174,16 +176,35 @@ void main() {
final request = InitiatePaymentsRequest(
idempotencyKey: 'idem-3',
quoteRef: 'q-2',
intentRefs: const ['intent-a', 'intent-b'],
metadata: const {'client_payment_ref': 'cp-1'},
);
final json = request.toJson();
expect(json['idempotencyKey'], equals('idem-3'));
expect(json['quoteRef'], equals('q-2'));
expect(json['intentRefs'], equals(const ['intent-a', 'intent-b']));
expect(
(json['metadata'] as Map<String, dynamic>)['client_payment_ref'],
equals('cp-1'),
);
});
test(
'initiate multi payments request supports single intentRef selector',
() {
final request = InitiatePaymentsRequest(
idempotencyKey: 'idem-4',
quoteRef: 'q-2',
intentRef: 'intent-single',
);
final json = request.toJson();
expect(json['idempotencyKey'], equals('idem-4'));
expect(json['quoteRef'], equals('q-2'));
expect(json['intentRef'], equals('intent-single'));
expect(json.containsKey('intentRefs'), isFalse);
},
);
});
}