1 Commits

Author SHA1 Message Date
Arseni
0091191d97 refactor of money utils with new money2 package 2026-03-13 03:17:29 +03:00
125 changed files with 653 additions and 1664 deletions

View File

@@ -8,7 +8,7 @@ require (
github.com/aws/aws-sdk-go-v2 v1.41.3 github.com/aws/aws-sdk-go-v2 v1.41.3
github.com/aws/aws-sdk-go-v2/config v1.32.11 github.com/aws/aws-sdk-go-v2/config v1.32.11
github.com/aws/aws-sdk-go-v2/credentials v1.19.11 github.com/aws/aws-sdk-go-v2/credentials v1.19.11
github.com/aws/aws-sdk-go-v2/service/s3 v1.97.0 github.com/aws/aws-sdk-go-v2/service/s3 v1.96.4
github.com/jung-kurt/gofpdf v1.16.2 github.com/jung-kurt/gofpdf v1.16.2
github.com/prometheus/client_golang v1.23.2 github.com/prometheus/client_golang v1.23.2
github.com/shopspring/decimal v1.4.0 github.com/shopspring/decimal v1.4.0
@@ -61,7 +61,7 @@ require (
go.uber.org/multierr v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect
go.yaml.in/yaml/v2 v2.4.4 // indirect go.yaml.in/yaml/v2 v2.4.4 // indirect
golang.org/x/crypto v0.49.0 // indirect golang.org/x/crypto v0.49.0 // indirect
golang.org/x/net v0.52.0 // indirect golang.org/x/net v0.51.0 // indirect
golang.org/x/sync v0.20.0 // indirect golang.org/x/sync v0.20.0 // indirect
golang.org/x/sys v0.42.0 // indirect golang.org/x/sys v0.42.0 // indirect
golang.org/x/text v0.35.0 // indirect golang.org/x/text v0.35.0 // indirect

View File

@@ -30,8 +30,8 @@ github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.19 h1:X1Tow7su
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.19/go.mod h1:/rARO8psX+4sfjUQXp5LLifjUt8DuATZ31WptNJTyQA= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.19/go.mod h1:/rARO8psX+4sfjUQXp5LLifjUt8DuATZ31WptNJTyQA=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.19 h1:JnQeStZvPHFHeyky/7LbMlyQjUa+jIBj36OlWm0pzIk= github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.19 h1:JnQeStZvPHFHeyky/7LbMlyQjUa+jIBj36OlWm0pzIk=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.19/go.mod h1:HGyasyHvYdFQeJhvDHfH7HXkHh57htcJGKDZ+7z+I24= github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.19/go.mod h1:HGyasyHvYdFQeJhvDHfH7HXkHh57htcJGKDZ+7z+I24=
github.com/aws/aws-sdk-go-v2/service/s3 v1.97.0 h1:zyKY4OxzUImu+DigelJI9o49QQv8CjREs5E1CywjtIA= github.com/aws/aws-sdk-go-v2/service/s3 v1.96.4 h1:4ExZyubQ6LQQVuF2Qp9OsfEvsTdAWh5Gfwf6PgIdLdk=
github.com/aws/aws-sdk-go-v2/service/s3 v1.97.0/go.mod h1:NF3JcMGOiARAss1ld3WGORCw71+4ExDD2cbbdKS5PpA= github.com/aws/aws-sdk-go-v2/service/s3 v1.96.4/go.mod h1:NF3JcMGOiARAss1ld3WGORCw71+4ExDD2cbbdKS5PpA=
github.com/aws/aws-sdk-go-v2/service/signin v1.0.7 h1:Y2cAXlClHsXkkOvWZFXATr34b0hxxloeQu/pAZz2row= github.com/aws/aws-sdk-go-v2/service/signin v1.0.7 h1:Y2cAXlClHsXkkOvWZFXATr34b0hxxloeQu/pAZz2row=
github.com/aws/aws-sdk-go-v2/service/signin v1.0.7/go.mod h1:idzZ7gmDeqeNrSPkdbtMp9qWMgcBwykA7P7Rzh5DXVU= github.com/aws/aws-sdk-go-v2/service/signin v1.0.7/go.mod h1:idzZ7gmDeqeNrSPkdbtMp9qWMgcBwykA7P7Rzh5DXVU=
github.com/aws/aws-sdk-go-v2/service/sso v1.30.12 h1:iSsvB9EtQ09YrsmIc44Heqlx5ByGErqhPK1ZQLppias= github.com/aws/aws-sdk-go-v2/service/sso v1.30.12 h1:iSsvB9EtQ09YrsmIc44Heqlx5ByGErqhPK1ZQLppias=
@@ -229,8 +229,8 @@ golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0= golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw= golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=

View File

@@ -3,8 +3,7 @@ package content
// Issuer details are intentionally centralized to avoid document text drift. // Issuer details are intentionally centralized to avoid document text drift.
const ( const (
IssuerLegalName = "SMX Operations Limited" IssuerLegalName = "SMX Operations Limited"
IssuerLegalAddress = "Room 607, 12/F., Block C, Hong Kong Industrial Centre, 489-491 Castle Peak Road, Lai Chi Kok, HongKong" IssuerLegalAddress = "Room 607, 12/F., Block C, Hong Kong Industrial Centre, 489-491 Castle Peak Road, Lai Chi Kok, Hong Kong"
IssuerEmail = "contact@sendico.io"
) )
const ( const (
@@ -75,77 +74,43 @@ var AcceptanceTemplate = AcceptanceTemplateContent{
// OperationDocumentContent contains all static copy for operation documents. // OperationDocumentContent contains all static copy for operation documents.
type OperationDocumentContent struct { type OperationDocumentContent struct {
Title string Title string
Subtitle string Subtitle string
MetaCertificateNumberLabel string MetaDocumentType string
MetaDateLabel string SectionOperation string
SectionParties string SectionFailure string
PartiesIntro string RowOrganization string
RowServiceProvider string RowGatewayService string
RowServiceProviderAddress string RowOperationRef string
RowServiceProviderEmail string RowPaymentRef string
RowClient string RowCode string
RowClientAddress string RowState string
RowClientReference string RowLabel string
SectionSubject string RowStartedAtUTC string
SubjectIntro string RowCompletedAtUTC string
SectionServicePeriod string RowAmount string
RowPeriodFrom string RowFailureCode string
RowPeriodTo string RowFailureReason string
SectionTotalAmount string MissingValuePlaceholder string
RowTotalAmount string
SectionClientConfirmation string
ConfirmationLine1 string
ConfirmationLine2 string
ConfirmationLine3 string
SectionSignatures string
SignatureServiceProviderLine string
SignatureClientNamePrefix string
SignatureClientTitleLine string
SignatureClientLine string
SectionOperationStatus string
RowOperationStatus string
RowOperationCode string
RowOperationLabel string
RowFailureCode string
RowFailureReason string
MissingValuePlaceholder string
} }
var OperationDocument = OperationDocumentContent{ var OperationDocument = OperationDocumentContent{
Title: "CERTIFICATE OF SERVICES RENDERED", Title: "OPERATION BILLING DOCUMENT",
Subtitle: "Payment operation completion and acceptance statement", Subtitle: "Gateway operation statement",
MetaCertificateNumberLabel: "Certificate No.", MetaDocumentType: "Document Type: Operation",
MetaDateLabel: "Date", SectionOperation: "OPERATION DETAILS",
SectionParties: "PARTIES", SectionFailure: "FAILURE DETAILS",
PartiesIntro: "This Certificate is made between:", RowOrganization: "Organization",
RowServiceProvider: "Service Provider", RowGatewayService: "Gateway Service",
RowServiceProviderAddress: "Service Provider Address", RowOperationRef: "Operation Ref",
RowServiceProviderEmail: "Service Provider Email", RowPaymentRef: "Payment Ref",
RowClient: "Client", RowCode: "Code",
RowClientAddress: "Client Address", RowState: "State",
RowClientReference: "Client Reference", RowLabel: "Label",
SectionSubject: "SUBJECT OF THE CERTIFICATE", RowStartedAtUTC: "Started At (UTC)",
SubjectIntro: "The Service Provider confirms that the following services have been fully rendered to the Client:", RowCompletedAtUTC: "Completed At (UTC)",
SectionServicePeriod: "SERVICE PERIOD", RowAmount: "Amount",
RowPeriodFrom: "From", RowFailureCode: "Failure Code",
RowPeriodTo: "To", RowFailureReason: "Failure Reason",
SectionTotalAmount: "TOTAL AMOUNT", MissingValuePlaceholder: "n/a",
RowTotalAmount: "Amount",
SectionClientConfirmation: "CLIENT CONFIRMATION",
ConfirmationLine1: "- the services were rendered in full;",
ConfirmationLine2: "- the services were rendered properly and within the agreed scope;",
ConfirmationLine3: "- the Client has no claims regarding the quality, quantity, or timing of the services rendered.",
SectionSignatures: "SIGNATURES",
SignatureServiceProviderLine: "Service Provider: Name: SMX Operations Limited | Signature: __________________",
SignatureClientNamePrefix: "Client: Name:",
SignatureClientTitleLine: "Title: Authorized Representative",
SignatureClientLine: "Signature: __________________",
SectionOperationStatus: "OPERATION STATUS",
RowOperationStatus: "Operation Status",
RowOperationCode: "Operation Code",
RowOperationLabel: "Operation Label",
RowFailureCode: "Failure Code",
RowFailureReason: "Failure Reason",
MissingValuePlaceholder: "n/a",
} }

View File

@@ -289,8 +289,6 @@ type operationSnapshot struct {
GatewayService string GatewayService string
OperationRef string OperationRef string
PaymentRef string PaymentRef string
ClientName string
ClientAddress string
OperationCode string OperationCode string
OperationLabel string OperationLabel string
OperationState string OperationState string
@@ -308,8 +306,6 @@ func operationSnapshotFromRequest(req *documentsv1.GetOperationDocumentRequest)
GatewayService: strings.TrimSpace(req.GetGatewayService()), GatewayService: strings.TrimSpace(req.GetGatewayService()),
OperationRef: strings.TrimSpace(req.GetOperationRef()), OperationRef: strings.TrimSpace(req.GetOperationRef()),
PaymentRef: strings.TrimSpace(req.GetPaymentRef()), PaymentRef: strings.TrimSpace(req.GetPaymentRef()),
ClientName: strings.TrimSpace(req.GetClientName()),
ClientAddress: strings.TrimSpace(req.GetClientAddress()),
OperationCode: strings.TrimSpace(req.GetOperationCode()), OperationCode: strings.TrimSpace(req.GetOperationCode()),
OperationLabel: strings.TrimSpace(req.GetOperationLabel()), OperationLabel: strings.TrimSpace(req.GetOperationLabel()),
OperationState: strings.TrimSpace(req.GetOperationState()), OperationState: strings.TrimSpace(req.GetOperationState()),
@@ -332,6 +328,21 @@ func operationSnapshotFromRequest(req *documentsv1.GetOperationDocumentRequest)
func buildOperationBlocks(snapshot operationSnapshot) []renderer.Block { func buildOperationBlocks(snapshot operationSnapshot) []renderer.Block {
documentCopy := content.OperationDocument documentCopy := content.OperationDocument
rows := [][]string{
{documentCopy.RowOrganization, snapshot.OrganizationRef},
{documentCopy.RowGatewayService, snapshot.GatewayService},
{documentCopy.RowOperationRef, snapshot.OperationRef},
{documentCopy.RowPaymentRef, safeValue(snapshot.PaymentRef)},
{documentCopy.RowCode, safeValue(snapshot.OperationCode)},
{documentCopy.RowState, safeValue(snapshot.OperationState)},
{documentCopy.RowLabel, safeValue(snapshot.OperationLabel)},
{documentCopy.RowStartedAtUTC, formatSnapshotTime(snapshot.StartedAt)},
{documentCopy.RowCompletedAtUTC, formatSnapshotTime(snapshot.CompletedAt)},
}
if snapshot.Amount != "" || snapshot.Currency != "" {
rows = append(rows, []string{documentCopy.RowAmount, strings.TrimSpace(strings.TrimSpace(snapshot.Amount) + " " + strings.TrimSpace(snapshot.Currency))})
}
blocks := []renderer.Block{ blocks := []renderer.Block{
{ {
Tag: renderer.TagTitle, Tag: renderer.TagTitle,
@@ -344,115 +355,30 @@ func buildOperationBlocks(snapshot operationSnapshot) []renderer.Block {
{ {
Tag: renderer.TagMeta, Tag: renderer.TagMeta,
Lines: []string{ Lines: []string{
fmt.Sprintf("%s: %s", documentCopy.MetaCertificateNumberLabel, certificateNumber(snapshot)), documentCopy.MetaDocumentType,
fmt.Sprintf("%s: %s", documentCopy.MetaDateLabel, formatCertificateDate(certificateDate(snapshot))),
}, },
}, },
{ {
Tag: renderer.TagSection, Tag: renderer.TagSection,
Lines: []string{documentCopy.SectionParties}, Lines: []string{documentCopy.SectionOperation},
}, },
{ {
Tag: renderer.TagText, Tag: renderer.TagKV,
Lines: []string{documentCopy.PartiesIntro}, Rows: rows,
},
{
Tag: renderer.TagKV,
Rows: [][]string{
{documentCopy.RowServiceProvider, content.IssuerLegalName},
{documentCopy.RowServiceProviderAddress, content.IssuerLegalAddress},
{documentCopy.RowServiceProviderEmail, content.IssuerEmail},
{documentCopy.RowClient, certificateClientName(snapshot)},
{documentCopy.RowClientAddress, certificateClientAddress(snapshot)},
{documentCopy.RowClientReference, safeValue(snapshot.PaymentRef)},
},
},
{
Tag: renderer.TagSection,
Lines: []string{documentCopy.SectionSubject},
},
{
Tag: renderer.TagText,
Lines: []string{
documentCopy.SubjectIntro,
"",
"- Payment execution and orchestration services for payment reference " + safeValue(snapshot.PaymentRef) + ".",
"- Gateway service: " + safeValue(snapshot.GatewayService) + ".",
"- Operation reference: " + safeValue(snapshot.OperationRef) + ".",
"- Operation descriptor: " + operationDescriptor(snapshot) + ".",
},
},
{
Tag: renderer.TagSection,
Lines: []string{documentCopy.SectionServicePeriod},
},
{
Tag: renderer.TagKV,
Rows: [][]string{
{documentCopy.RowPeriodFrom, formatSnapshotTime(snapshot.StartedAt)},
{documentCopy.RowPeriodTo, formatSnapshotTime(snapshot.CompletedAt)},
},
},
{
Tag: renderer.TagSection,
Lines: []string{documentCopy.SectionTotalAmount},
},
{
Tag: renderer.TagKV,
Rows: [][]string{
{documentCopy.RowTotalAmount, operationAmount(snapshot)},
},
},
{
Tag: renderer.TagSection,
Lines: []string{documentCopy.SectionClientConfirmation},
},
{
Tag: renderer.TagText,
Lines: []string{
documentCopy.ConfirmationLine1,
documentCopy.ConfirmationLine2,
documentCopy.ConfirmationLine3,
"",
"This Certificate serves as confirmation of the completion and acceptance of the services.",
},
},
{
Tag: renderer.TagSection,
Lines: []string{documentCopy.SectionSignatures},
},
{
Tag: renderer.TagSign,
Lines: []string{
documentCopy.SignatureServiceProviderLine,
"",
documentCopy.SignatureClientNamePrefix + " " + certificateClientName(snapshot),
documentCopy.SignatureClientTitleLine,
documentCopy.SignatureClientLine,
},
},
{
Tag: renderer.TagSection,
Lines: []string{documentCopy.SectionOperationStatus},
},
{
Tag: renderer.TagKV,
Rows: [][]string{
{documentCopy.RowOperationStatus, safeValue(snapshot.OperationState)},
{documentCopy.RowOperationCode, safeValue(snapshot.OperationCode)},
{documentCopy.RowOperationLabel, safeValue(snapshot.OperationLabel)},
},
}, },
} }
if snapshot.FailureCode != "" || snapshot.FailureReason != "" { if snapshot.FailureCode != "" || snapshot.FailureReason != "" {
blocks = append(blocks, renderer.Block{ blocks = append(blocks,
Tag: renderer.TagKV, renderer.Block{Tag: renderer.TagSection, Lines: []string{documentCopy.SectionFailure}},
Rows: [][]string{ renderer.Block{
{documentCopy.RowFailureCode, safeValue(snapshot.FailureCode)}, Tag: renderer.TagKV,
{documentCopy.RowFailureReason, safeValue(snapshot.FailureReason)}, Rows: [][]string{
{documentCopy.RowFailureCode, safeValue(snapshot.FailureCode)},
{documentCopy.RowFailureReason, safeValue(snapshot.FailureReason)},
},
}, },
}) )
} }
return blocks return blocks
@@ -466,80 +392,6 @@ func formatSnapshotTime(value time.Time) string {
return value.UTC().Format(time.RFC3339) return value.UTC().Format(time.RFC3339)
} }
func certificateNumber(snapshot operationSnapshot) string {
if paymentRef := strings.TrimSpace(snapshot.PaymentRef); paymentRef != "" {
return paymentRef
}
if operationRef := strings.TrimSpace(snapshot.OperationRef); operationRef != "" {
return operationRef
}
return content.OperationDocument.MissingValuePlaceholder
}
func certificateDate(snapshot operationSnapshot) time.Time {
if !snapshot.CompletedAt.IsZero() {
return snapshot.CompletedAt.UTC()
}
if !snapshot.StartedAt.IsZero() {
return snapshot.StartedAt.UTC()
}
return time.Now().UTC()
}
func formatCertificateDate(value time.Time) string {
if value.IsZero() {
return content.OperationDocument.MissingValuePlaceholder
}
return value.UTC().Format("January 2, 2006")
}
func operationAmount(snapshot operationSnapshot) string {
amount := strings.TrimSpace(snapshot.Amount)
currency := strings.TrimSpace(snapshot.Currency)
if amount == "" && currency == "" {
return content.OperationDocument.MissingValuePlaceholder
}
return strings.TrimSpace(amount + " " + currency)
}
func operationDescriptor(snapshot operationSnapshot) string {
label := strings.TrimSpace(snapshot.OperationLabel)
code := strings.TrimSpace(snapshot.OperationCode)
switch {
case label != "" && code != "":
return fmt.Sprintf("%s (%s)", label, code)
case label != "":
return label
case code != "":
return code
default:
return content.OperationDocument.MissingValuePlaceholder
}
}
func certificateClientName(snapshot operationSnapshot) string {
if name := strings.TrimSpace(snapshot.ClientName); name != "" {
return name
}
return "John Doe"
}
func certificateClientAddress(snapshot operationSnapshot) string {
if address := strings.TrimSpace(snapshot.ClientAddress); address != "" {
return address
}
return content.OperationDocument.MissingValuePlaceholder
}
func safeValue(value string) string { func safeValue(value string) string {
trimmed := strings.TrimSpace(value) trimmed := strings.TrimSpace(value)
if trimmed == "" { if trimmed == "" {

View File

@@ -3,7 +3,6 @@ package documents
import ( import (
"bytes" "bytes"
"context" "context"
"strings"
"testing" "testing"
"time" "time"
@@ -192,92 +191,3 @@ func TestGetOperationDocument_RequiresOperationRef(t *testing.T) {
t.Fatalf("expected InvalidArgument, got=%v err=%v", status.Code(err), err) t.Fatalf("expected InvalidArgument, got=%v err=%v", status.Code(err), err)
} }
} }
func TestBuildOperationBlocks_CertificateIncludesPaymentData(t *testing.T) {
snapshot := operationSnapshot{
OrganizationRef: "org-1",
GatewayService: "chain_gateway",
OperationRef: "op-123",
PaymentRef: "pay-123",
ClientName: "Jane Customer",
ClientAddress: "Main Street 1, City",
OperationCode: "transfer",
OperationLabel: "Outbound transfer",
OperationState: "completed",
Amount: "100.50",
Currency: "USDT",
StartedAt: time.Date(2026, 3, 1, 10, 0, 0, 0, time.UTC),
CompletedAt: time.Date(2026, 3, 2, 12, 0, 0, 0, time.UTC),
}
blocks := buildOperationBlocks(snapshot)
if len(blocks) == 0 {
t.Fatalf("expected blocks")
}
if got := blocks[0].Lines[0]; got != content.OperationDocument.Title {
t.Fatalf("title mismatch: got=%q want=%q", got, content.OperationDocument.Title)
}
meta := findTaggedBlock(blocks, renderer.TagMeta)
if meta == nil {
t.Fatalf("expected meta block")
}
metaText := strings.Join(meta.Lines, "\n")
if !strings.Contains(metaText, "Certificate No.: pay-123") {
t.Fatalf("meta should include certificate number, got=%q", metaText)
}
if !strings.Contains(metaText, "Date: March 2, 2026") {
t.Fatalf("meta should include certificate date, got=%q", metaText)
}
amountFound := false
clientFound := false
for _, block := range blocks {
if block.Tag != renderer.TagKV {
continue
}
for _, row := range block.Rows {
if len(row) >= 2 && row[0] == content.OperationDocument.RowTotalAmount && row[1] == "100.50 USDT" {
amountFound = true
}
if len(row) >= 2 && row[0] == content.OperationDocument.RowClient && row[1] == "Jane Customer" {
clientFound = true
}
}
}
if !amountFound {
t.Fatalf("expected total amount row with payment amount")
}
if !clientFound {
t.Fatalf("expected client row with customer name")
}
}
func TestCertificateNumber_FallsBackToOperationRef(t *testing.T) {
got := certificateNumber(operationSnapshot{
OperationRef: "op-777",
})
if got != "op-777" {
t.Fatalf("certificateNumber fallback mismatch: got=%q want=%q", got, "op-777")
}
}
func TestCertificateClientName_Fallback(t *testing.T) {
if got := certificateClientName(operationSnapshot{}); got != "John Doe" {
t.Fatalf("certificateClientName fallback mismatch: got=%q want=%q", got, "John Doe")
}
}
func findTaggedBlock(blocks []renderer.Block, tag renderer.Tag) *renderer.Block {
for i := range blocks {
if blocks[i].Tag == tag {
return &blocks[i]
}
}
return nil
}

View File

@@ -46,7 +46,7 @@ require (
go.uber.org/multierr v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect
go.yaml.in/yaml/v2 v2.4.4 // indirect go.yaml.in/yaml/v2 v2.4.4 // indirect
golang.org/x/crypto v0.49.0 // indirect golang.org/x/crypto v0.49.0 // indirect
golang.org/x/net v0.52.0 // indirect golang.org/x/net v0.51.0 // indirect
golang.org/x/sync v0.20.0 // indirect golang.org/x/sync v0.20.0 // indirect
golang.org/x/sys v0.42.0 // indirect golang.org/x/sys v0.42.0 // indirect
golang.org/x/text v0.35.0 // indirect golang.org/x/text v0.35.0 // indirect

View File

@@ -179,8 +179,8 @@ golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0= golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw= golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=

View File

@@ -39,7 +39,7 @@ require (
go.uber.org/multierr v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect
go.yaml.in/yaml/v2 v2.4.4 // indirect go.yaml.in/yaml/v2 v2.4.4 // indirect
golang.org/x/crypto v0.49.0 // indirect golang.org/x/crypto v0.49.0 // indirect
golang.org/x/net v0.52.0 // indirect golang.org/x/net v0.51.0 // indirect
golang.org/x/sync v0.20.0 // indirect golang.org/x/sync v0.20.0 // indirect
golang.org/x/sys v0.42.0 // indirect golang.org/x/sys v0.42.0 // indirect
golang.org/x/text v0.35.0 // indirect golang.org/x/text v0.35.0 // indirect

View File

@@ -179,8 +179,8 @@ golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0= golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw= golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=

View File

@@ -18,7 +18,7 @@ require (
github.com/aws/aws-sdk-go-v2 v1.41.3 github.com/aws/aws-sdk-go-v2 v1.41.3
github.com/aws/aws-sdk-go-v2/config v1.32.11 github.com/aws/aws-sdk-go-v2/config v1.32.11
github.com/aws/aws-sdk-go-v2/credentials v1.19.11 github.com/aws/aws-sdk-go-v2/credentials v1.19.11
github.com/aws/aws-sdk-go-v2/service/s3 v1.97.0 github.com/aws/aws-sdk-go-v2/service/s3 v1.96.4
github.com/go-chi/chi/v5 v5.2.5 github.com/go-chi/chi/v5 v5.2.5
github.com/go-chi/cors v1.2.2 github.com/go-chi/cors v1.2.2
github.com/go-chi/jwtauth/v5 v5.4.0 github.com/go-chi/jwtauth/v5 v5.4.0
@@ -37,7 +37,7 @@ require (
github.com/testcontainers/testcontainers-go/modules/mongodb v0.33.0 github.com/testcontainers/testcontainers-go/modules/mongodb v0.33.0
go.mongodb.org/mongo-driver/v2 v2.5.0 go.mongodb.org/mongo-driver/v2 v2.5.0
go.uber.org/zap v1.27.1 go.uber.org/zap v1.27.1
golang.org/x/net v0.52.0 golang.org/x/net v0.51.0
google.golang.org/grpc v1.79.2 google.golang.org/grpc v1.79.2
google.golang.org/protobuf v1.36.11 google.golang.org/protobuf v1.36.11
gopkg.in/yaml.v3 v3.0.1 gopkg.in/yaml.v3 v3.0.1
@@ -88,7 +88,7 @@ require (
github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-ole/go-ole v1.3.0 // indirect github.com/go-ole/go-ole v1.3.0 // indirect
github.com/goccy/go-json v0.10.6 // indirect github.com/goccy/go-json v0.10.5 // indirect
github.com/gogo/protobuf v1.3.2 // indirect github.com/gogo/protobuf v1.3.2 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect

View File

@@ -32,8 +32,8 @@ github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.19 h1:X1Tow7su
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.19/go.mod h1:/rARO8psX+4sfjUQXp5LLifjUt8DuATZ31WptNJTyQA= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.19/go.mod h1:/rARO8psX+4sfjUQXp5LLifjUt8DuATZ31WptNJTyQA=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.19 h1:JnQeStZvPHFHeyky/7LbMlyQjUa+jIBj36OlWm0pzIk= github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.19 h1:JnQeStZvPHFHeyky/7LbMlyQjUa+jIBj36OlWm0pzIk=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.19/go.mod h1:HGyasyHvYdFQeJhvDHfH7HXkHh57htcJGKDZ+7z+I24= github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.19/go.mod h1:HGyasyHvYdFQeJhvDHfH7HXkHh57htcJGKDZ+7z+I24=
github.com/aws/aws-sdk-go-v2/service/s3 v1.97.0 h1:zyKY4OxzUImu+DigelJI9o49QQv8CjREs5E1CywjtIA= github.com/aws/aws-sdk-go-v2/service/s3 v1.96.4 h1:4ExZyubQ6LQQVuF2Qp9OsfEvsTdAWh5Gfwf6PgIdLdk=
github.com/aws/aws-sdk-go-v2/service/s3 v1.97.0/go.mod h1:NF3JcMGOiARAss1ld3WGORCw71+4ExDD2cbbdKS5PpA= github.com/aws/aws-sdk-go-v2/service/s3 v1.96.4/go.mod h1:NF3JcMGOiARAss1ld3WGORCw71+4ExDD2cbbdKS5PpA=
github.com/aws/aws-sdk-go-v2/service/signin v1.0.7 h1:Y2cAXlClHsXkkOvWZFXATr34b0hxxloeQu/pAZz2row= github.com/aws/aws-sdk-go-v2/service/signin v1.0.7 h1:Y2cAXlClHsXkkOvWZFXATr34b0hxxloeQu/pAZz2row=
github.com/aws/aws-sdk-go-v2/service/signin v1.0.7/go.mod h1:idzZ7gmDeqeNrSPkdbtMp9qWMgcBwykA7P7Rzh5DXVU= github.com/aws/aws-sdk-go-v2/service/signin v1.0.7/go.mod h1:idzZ7gmDeqeNrSPkdbtMp9qWMgcBwykA7P7Rzh5DXVU=
github.com/aws/aws-sdk-go-v2/service/sso v1.30.12 h1:iSsvB9EtQ09YrsmIc44Heqlx5ByGErqhPK1ZQLppias= github.com/aws/aws-sdk-go-v2/service/sso v1.30.12 h1:iSsvB9EtQ09YrsmIc44Heqlx5ByGErqhPK1ZQLppias=
@@ -112,8 +112,8 @@ github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
github.com/go-test/deep v1.1.1 h1:0r/53hagsehfO4bzD2Pgr/+RgHqhmf+k1Bpse2cTu1U= github.com/go-test/deep v1.1.1 h1:0r/53hagsehfO4bzD2Pgr/+RgHqhmf+k1Bpse2cTu1U=
github.com/go-test/deep v1.1.1/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= github.com/go-test/deep v1.1.1/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
github.com/goccy/go-json v0.10.6 h1:p8HrPJzOakx/mn/bQtjgNjdTcN+/S6FcG2CTtQOrHVU= github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
github.com/goccy/go-json v0.10.6/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
@@ -346,8 +346,8 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0= golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw= golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=

View File

@@ -16,8 +16,6 @@ import (
"github.com/tech/sendico/pkg/mutil/mzap" "github.com/tech/sendico/pkg/mutil/mzap"
documentsv1 "github.com/tech/sendico/pkg/proto/billing/documents/v1" documentsv1 "github.com/tech/sendico/pkg/proto/billing/documents/v1"
connectorv1 "github.com/tech/sendico/pkg/proto/connector/v1" connectorv1 "github.com/tech/sendico/pkg/proto/connector/v1"
endpointv1 "github.com/tech/sendico/pkg/proto/payments/endpoint/v1"
orchestrationv2 "github.com/tech/sendico/pkg/proto/payments/orchestration/v2"
"github.com/tech/sendico/server/interface/api/sresponse" "github.com/tech/sendico/server/interface/api/sresponse"
mutil "github.com/tech/sendico/server/internal/mutil/param" mutil "github.com/tech/sendico/server/internal/mutil/param"
"go.mongodb.org/mongo-driver/v2/bson" "go.mongodb.org/mongo-driver/v2/bson"
@@ -26,6 +24,7 @@ import (
"google.golang.org/grpc/codes" "google.golang.org/grpc/codes"
"google.golang.org/grpc/credentials/insecure" "google.golang.org/grpc/credentials/insecure"
"google.golang.org/grpc/status" "google.golang.org/grpc/status"
"google.golang.org/protobuf/types/known/structpb"
) )
const ( const (
@@ -62,10 +61,6 @@ func (a *PaymentAPI) getOperationDocument(r *http.Request, account *model.Accoun
if operationRef == "" { if operationRef == "" {
return response.BadRequest(a.logger, a.Name(), "missing_parameter", "operation_ref is required") return response.BadRequest(a.logger, a.Name(), "missing_parameter", "operation_ref is required")
} }
paymentRef := strings.TrimSpace(query.Get("payment_ref"))
if paymentRef == "" {
return response.BadRequest(a.logger, a.Name(), "missing_parameter", "payment_ref is required")
}
service, gateway, h := a.resolveOperationDocumentDeps(r.Context(), gatewayService) service, gateway, h := a.resolveOperationDocumentDeps(r.Context(), gatewayService)
if h != nil { if h != nil {
@@ -78,18 +73,7 @@ func (a *PaymentAPI) getOperationDocument(r *http.Request, account *model.Accoun
return documentErrorResponse(a.logger, a.Name(), err) return documentErrorResponse(a.logger, a.Name(), err)
} }
req := operationDocumentRequest(orgRef.Hex(), gatewayService, operationRef, paymentRef, op) req := operationDocumentRequest(orgRef.Hex(), gatewayService, operationRef, op)
if payment, paymentErr := a.fetchPayment(r.Context(), orgRef.Hex(), paymentRef); paymentErr != nil {
a.logger.Warn(
"Failed to fetch payment snapshot for operation document",
zap.Error(paymentErr),
mzap.ObjRef("organization_ref", orgRef),
zap.String("payment_ref", paymentRef),
zap.String("operation_ref", operationRef),
)
} else {
enrichOperationDocumentRequestFromPayment(req, payment)
}
docResp, err := a.fetchOperationDocument(r.Context(), service.InvokeURI, req) docResp, err := a.fetchOperationDocument(r.Context(), service.InvokeURI, req)
if err != nil { if err != nil {
@@ -279,28 +263,6 @@ func (a *PaymentAPI) fetchGatewayOperation(ctx context.Context, invokeURI, opera
return op, nil return op, nil
} }
func (a *PaymentAPI) fetchPayment(ctx context.Context, organizationRef, paymentRef string) (*orchestrationv2.Payment, error) {
if a.execution == nil {
return nil, merrors.Internal("payment execution client is not configured")
}
resp, err := a.execution.GetPayment(ctx, &orchestrationv2.GetPaymentRequest{
Meta: requestMeta(organizationRef, ""),
PaymentRef: strings.TrimSpace(paymentRef),
})
if err != nil {
return nil, err
}
if resp == nil {
return nil, merrors.NoData("payment orchestrator returned empty response")
}
if resp.GetPayment() == nil {
return nil, merrors.NoData("payment orchestrator returned empty payment")
}
return resp.GetPayment(), nil
}
func findGatewayForService(gateways []discovery.GatewaySummary, gatewayService mservice.Type) *discovery.GatewaySummary { func findGatewayForService(gateways []discovery.GatewaySummary, gatewayService mservice.Type) *discovery.GatewaySummary {
candidates := make([]discovery.GatewaySummary, 0, len(gateways)) candidates := make([]discovery.GatewaySummary, 0, len(gateways))
for _, gw := range gateways { for _, gw := range gateways {
@@ -351,14 +313,13 @@ func findGatewayForService(gateways []discovery.GatewaySummary, gatewayService m
return &best return &best
} }
func operationDocumentRequest(organizationRef string, gatewayService mservice.Type, requestedOperationRef string, paymentRef string, op *connectorv1.Operation) *documentsv1.GetOperationDocumentRequest { func operationDocumentRequest(organizationRef string, gatewayService mservice.Type, requestedOperationRef string, op *connectorv1.Operation) *documentsv1.GetOperationDocumentRequest {
req := &documentsv1.GetOperationDocumentRequest{ req := &documentsv1.GetOperationDocumentRequest{
OrganizationRef: strings.TrimSpace(organizationRef), OrganizationRef: strings.TrimSpace(organizationRef),
GatewayService: gatewayService, GatewayService: gatewayService,
OperationRef: firstNonEmpty(strings.TrimSpace(op.GetOperationRef()), strings.TrimSpace(requestedOperationRef)), OperationRef: firstNonEmpty(strings.TrimSpace(op.GetOperationRef()), strings.TrimSpace(requestedOperationRef)),
PaymentRef: strings.TrimSpace(paymentRef),
OperationCode: strings.TrimSpace(op.GetType().String()), OperationCode: strings.TrimSpace(op.GetType().String()),
OperationLabel: strings.TrimSpace(op.GetType().String()), OperationLabel: operationLabel(op.GetType()),
OperationState: strings.TrimSpace(op.GetStatus().String()), OperationState: strings.TrimSpace(op.GetStatus().String()),
Amount: strings.TrimSpace(op.GetMoney().GetAmount()), Amount: strings.TrimSpace(op.GetMoney().GetAmount()),
Currency: strings.TrimSpace(op.GetMoney().GetCurrency()), Currency: strings.TrimSpace(op.GetMoney().GetCurrency()),
@@ -371,96 +332,65 @@ func operationDocumentRequest(organizationRef string, gatewayService mservice.Ty
req.CompletedAtUnixMs = ts.AsTime().UnixMilli() req.CompletedAtUnixMs = ts.AsTime().UnixMilli()
} }
if isFailedOperationStatus(op.GetStatus()) { req.PaymentRef = operationParamValue(op.GetParams(), "payment_ref", "parent_payment_ref", "paymentRef", "parentPaymentRef")
req.FailureCode = strings.TrimSpace(op.GetStatus().String()) req.FailureCode = firstNonEmpty(
} operationParamValue(op.GetParams(), "failure_code", "provider_code", "error_code"),
failureCodeFromStatus(op.GetStatus()),
)
req.FailureReason = operationParamValue(op.GetParams(), "failure_reason", "provider_message", "error", "message")
return req return req
} }
func isFailedOperationStatus(status connectorv1.OperationStatus) bool { func operationLabel(opType connectorv1.OperationType) string {
return status == connectorv1.OperationStatus_OPERATION_FAILED || status == connectorv1.OperationStatus_OPERATION_CANCELLED switch opType {
case connectorv1.OperationType_CREDIT:
return "Credit"
case connectorv1.OperationType_DEBIT:
return "Debit"
case connectorv1.OperationType_TRANSFER:
return "Transfer"
case connectorv1.OperationType_PAYOUT:
return "Payout"
case connectorv1.OperationType_FEE_ESTIMATE:
return "Fee Estimate"
case connectorv1.OperationType_FX:
return "FX"
case connectorv1.OperationType_GAS_TOPUP:
return "Gas Top Up"
default:
return strings.TrimSpace(opType.String())
}
} }
func enrichOperationDocumentRequestFromPayment(req *documentsv1.GetOperationDocumentRequest, payment *orchestrationv2.Payment) { func failureCodeFromStatus(status connectorv1.OperationStatus) string {
if req == nil || payment == nil { switch status {
return case connectorv1.OperationStatus_OPERATION_FAILED, connectorv1.OperationStatus_OPERATION_CANCELLED:
} return strings.TrimSpace(status.String())
req.PaymentRef = firstNonEmpty(strings.TrimSpace(req.GetPaymentRef()), strings.TrimSpace(payment.GetPaymentRef()))
req.ClientName = firstNonEmpty(strings.TrimSpace(req.GetClientName()), paymentClientName(payment))
}
func paymentClientName(payment *orchestrationv2.Payment) string {
if payment == nil {
return ""
}
intent := payment.GetIntentSnapshot()
if intent == nil {
return ""
}
if customerDescription := strings.TrimSpace(intent.GetComment()); customerDescription != "" {
return customerDescription
}
return firstNonEmpty(
paymentEndpointClientName(intent.GetDestination()),
paymentEndpointClientName(intent.GetSource()),
)
}
func paymentEndpointClientName(endpoint *endpointv1.PaymentEndpoint) string {
if endpoint == nil {
return ""
}
method := endpoint.GetPaymentMethod()
if method == nil {
return ""
}
switch method.GetType() {
case endpointv1.PaymentMethodType_PAYMENT_METHOD_TYPE_CARD:
type cardMethodData struct {
FirstName string `bson:"firstName"`
LastName string `bson:"lastName"`
}
var payload cardMethodData
if err := bson.Unmarshal(method.GetData(), &payload); err != nil {
return ""
}
return strings.TrimSpace(strings.Join([]string{
strings.TrimSpace(payload.FirstName),
strings.TrimSpace(payload.LastName),
}, " "))
case endpointv1.PaymentMethodType_PAYMENT_METHOD_TYPE_BANK_ACCOUNT:
type bankMethodData struct {
RecipientName string `bson:"recipientName"`
}
var payload bankMethodData
if err := bson.Unmarshal(method.GetData(), &payload); err != nil {
return ""
}
return strings.TrimSpace(payload.RecipientName)
case endpointv1.PaymentMethodType_PAYMENT_METHOD_TYPE_IBAN:
type ibanMethodData struct {
AccountHolder string `bson:"accountHolder"`
}
var payload ibanMethodData
if err := bson.Unmarshal(method.GetData(), &payload); err != nil {
return ""
}
return strings.TrimSpace(payload.AccountHolder)
default: default:
return "" return ""
} }
} }
func operationParamValue(params *structpb.Struct, keys ...string) string {
if params == nil {
return ""
}
values := params.AsMap()
for _, key := range keys {
raw, ok := values[key]
if !ok {
continue
}
if text := strings.TrimSpace(fmt.Sprint(raw)); text != "" && text != "<nil>" {
return text
}
}
return ""
}
func findDocumentsService(services []discovery.ServiceSummary) *discovery.ServiceSummary { func findDocumentsService(services []discovery.ServiceSummary) *discovery.ServiceSummary {
for _, svc := range services { for _, svc := range services {
if !strings.EqualFold(svc.Service, documentsServiceName) { if !strings.EqualFold(svc.Service, documentsServiceName) {

View File

@@ -1,120 +0,0 @@
package paymentapiimp
import (
"testing"
"github.com/tech/sendico/pkg/mservice"
documentsv1 "github.com/tech/sendico/pkg/proto/billing/documents/v1"
moneyv1 "github.com/tech/sendico/pkg/proto/common/money/v1"
connectorv1 "github.com/tech/sendico/pkg/proto/connector/v1"
endpointv1 "github.com/tech/sendico/pkg/proto/payments/endpoint/v1"
orchestrationv2 "github.com/tech/sendico/pkg/proto/payments/orchestration/v2"
quotationv2 "github.com/tech/sendico/pkg/proto/payments/quotation/v2"
"go.mongodb.org/mongo-driver/v2/bson"
)
func TestOperationDocumentRequest_UsesStructuredOperationFieldsOnly(t *testing.T) {
op := &connectorv1.Operation{
OperationRef: "pay-123:hop_1",
Type: connectorv1.OperationType_TRANSFER,
Status: connectorv1.OperationStatus_OPERATION_SUCCESS,
Money: &moneyv1.Money{
Amount: "100.50",
Currency: "USDT",
},
}
req := operationDocumentRequest("org-1", mservice.ChainGateway, "requested-op", "pay-123", op)
if req == nil {
t.Fatalf("expected request")
}
if got, want := req.GetPaymentRef(), "pay-123"; got != want {
t.Fatalf("payment_ref mismatch: got=%q want=%q", got, want)
}
if got := req.GetClientName(); got != "" {
t.Fatalf("expected empty client_name from operation-only request, got=%q", got)
}
if got := req.GetClientAddress(); got != "" {
t.Fatalf("expected empty client_address from operation-only request, got=%q", got)
}
}
func TestOperationDocumentRequest_FailureCodeFromStructuredStatus(t *testing.T) {
req := operationDocumentRequest("org-1", mservice.ChainGateway, "op", "pay-123", &connectorv1.Operation{
OperationRef: "pay-123:hop_1",
Type: connectorv1.OperationType_TRANSFER,
Status: connectorv1.OperationStatus_OPERATION_FAILED,
})
if req == nil {
t.Fatalf("expected request")
}
if got, want := req.GetFailureCode(), "OPERATION_FAILED"; got != want {
t.Fatalf("failure_code mismatch: got=%q want=%q", got, want)
}
}
func TestPaymentClientName_FromIntentComment(t *testing.T) {
payment := &orchestrationv2.Payment{
IntentSnapshot: &quotationv2.QuoteIntent{
Comment: "Jane Customer",
},
}
if got, want := paymentClientName(payment), "Jane Customer"; got != want {
t.Fatalf("paymentClientName mismatch: got=%q want=%q", got, want)
}
}
func TestPaymentClientName_FromStructuredCardEndpoint(t *testing.T) {
raw, err := bson.Marshal(map[string]string{
"firstName": "Jane",
"lastName": "Doe",
})
if err != nil {
t.Fatalf("Marshal: %v", err)
}
payment := &orchestrationv2.Payment{
IntentSnapshot: &quotationv2.QuoteIntent{
Destination: &endpointv1.PaymentEndpoint{
Source: &endpointv1.PaymentEndpoint_PaymentMethod{
PaymentMethod: &endpointv1.PaymentMethod{
Type: endpointv1.PaymentMethodType_PAYMENT_METHOD_TYPE_CARD,
Data: raw,
},
},
},
},
}
if got, want := paymentClientName(payment), "Jane Doe"; got != want {
t.Fatalf("paymentClientName mismatch: got=%q want=%q", got, want)
}
}
func TestEnrichOperationDocumentRequestFromPayment_SetsClientName(t *testing.T) {
req := &documentsv1.GetOperationDocumentRequest{
OperationRef: "pay-123:hop_1",
}
payment := &orchestrationv2.Payment{
PaymentRef: "pay-123",
IntentSnapshot: &quotationv2.QuoteIntent{
Comment: "Client Name",
},
}
enrichOperationDocumentRequestFromPayment(req, payment)
if got, want := req.GetPaymentRef(), "pay-123"; got != want {
t.Fatalf("payment_ref mismatch: got=%q want=%q", got, want)
}
if got, want := req.GetClientName(), "Client Name"; got != want {
t.Fatalf("client_name mismatch: got=%q want=%q", got, want)
}
}
func TestOperationDocumentRequest_Compatibility(t *testing.T) {
var _ *documentsv1.GetOperationDocumentRequest = operationDocumentRequest("org-1", mservice.ChainGateway, "op", "pay-1", &connectorv1.Operation{})
}

View File

@@ -165,10 +165,6 @@ func (*fakeExecutionClientForBatch) ListPayments(context.Context, *orchestration
return &orchestrationv2.ListPaymentsResponse{}, nil return &orchestrationv2.ListPaymentsResponse{}, nil
} }
func (*fakeExecutionClientForBatch) GetPayment(context.Context, *orchestrationv2.GetPaymentRequest) (*orchestrationv2.GetPaymentResponse, error) {
return &orchestrationv2.GetPaymentResponse{}, nil
}
func (*fakeExecutionClientForBatch) Close() error { return nil } func (*fakeExecutionClientForBatch) Close() error { return nil }
type fakeEnforcerForBatch struct { type fakeEnforcerForBatch struct {

View File

@@ -32,7 +32,6 @@ import (
type executionClient interface { type executionClient interface {
ExecutePayment(ctx context.Context, req *orchestrationv2.ExecutePaymentRequest) (*orchestrationv2.ExecutePaymentResponse, error) ExecutePayment(ctx context.Context, req *orchestrationv2.ExecutePaymentRequest) (*orchestrationv2.ExecutePaymentResponse, error)
ExecuteBatchPayment(ctx context.Context, req *orchestrationv2.ExecuteBatchPaymentRequest) (*orchestrationv2.ExecuteBatchPaymentResponse, error) ExecuteBatchPayment(ctx context.Context, req *orchestrationv2.ExecuteBatchPaymentRequest) (*orchestrationv2.ExecuteBatchPaymentResponse, error)
GetPayment(ctx context.Context, req *orchestrationv2.GetPaymentRequest) (*orchestrationv2.GetPaymentResponse, error)
ListPayments(ctx context.Context, req *orchestrationv2.ListPaymentsRequest) (*orchestrationv2.ListPaymentsResponse, error) ListPayments(ctx context.Context, req *orchestrationv2.ListPaymentsRequest) (*orchestrationv2.ListPaymentsResponse, error)
Close() error Close() error
} }

View File

@@ -53,7 +53,7 @@ require (
go.uber.org/multierr v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect
go.yaml.in/yaml/v2 v2.4.4 // indirect go.yaml.in/yaml/v2 v2.4.4 // indirect
golang.org/x/crypto v0.49.0 // indirect golang.org/x/crypto v0.49.0 // indirect
golang.org/x/net v0.52.0 // indirect golang.org/x/net v0.51.0 // indirect
golang.org/x/sync v0.20.0 // indirect golang.org/x/sync v0.20.0 // indirect
golang.org/x/sys v0.42.0 // indirect golang.org/x/sys v0.42.0 // indirect
golang.org/x/text v0.35.0 // indirect golang.org/x/text v0.35.0 // indirect

View File

@@ -212,8 +212,8 @@ golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0= golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw= golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=

View File

@@ -13,7 +13,7 @@ require (
github.com/tech/sendico/fx/storage v0.0.0 github.com/tech/sendico/fx/storage v0.0.0
github.com/tech/sendico/pkg v0.1.0 github.com/tech/sendico/pkg v0.1.0
go.uber.org/zap v1.27.1 go.uber.org/zap v1.27.1
golang.org/x/net v0.52.0 golang.org/x/net v0.51.0
gopkg.in/yaml.v3 v3.0.1 gopkg.in/yaml.v3 v3.0.1
) )

View File

@@ -179,8 +179,8 @@ golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0= golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw= golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=

View File

@@ -44,7 +44,7 @@ require (
go.uber.org/multierr v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect
go.yaml.in/yaml/v2 v2.4.4 // indirect go.yaml.in/yaml/v2 v2.4.4 // indirect
golang.org/x/crypto v0.49.0 // indirect golang.org/x/crypto v0.49.0 // indirect
golang.org/x/net v0.52.0 // indirect golang.org/x/net v0.51.0 // indirect
golang.org/x/sync v0.20.0 // indirect golang.org/x/sync v0.20.0 // indirect
golang.org/x/sys v0.42.0 // indirect golang.org/x/sys v0.42.0 // indirect
golang.org/x/text v0.35.0 // indirect golang.org/x/text v0.35.0 // indirect

View File

@@ -179,8 +179,8 @@ golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0= golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw= golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=

View File

@@ -47,7 +47,7 @@ require (
go.uber.org/multierr v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect
go.yaml.in/yaml/v2 v2.4.4 // indirect go.yaml.in/yaml/v2 v2.4.4 // indirect
golang.org/x/crypto v0.49.0 // indirect golang.org/x/crypto v0.49.0 // indirect
golang.org/x/net v0.52.0 // indirect golang.org/x/net v0.51.0 // indirect
golang.org/x/sync v0.20.0 // indirect golang.org/x/sync v0.20.0 // indirect
golang.org/x/sys v0.42.0 // indirect golang.org/x/sys v0.42.0 // indirect
golang.org/x/text v0.35.0 // indirect golang.org/x/text v0.35.0 // indirect

View File

@@ -181,8 +181,8 @@ golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0= golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw= golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=

View File

@@ -86,7 +86,7 @@ require (
go.yaml.in/yaml/v2 v2.4.4 // indirect go.yaml.in/yaml/v2 v2.4.4 // indirect
golang.org/x/crypto v0.49.0 // indirect golang.org/x/crypto v0.49.0 // indirect
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 // indirect golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 // indirect
golang.org/x/net v0.52.0 // indirect golang.org/x/net v0.51.0 // indirect
golang.org/x/sync v0.20.0 // indirect golang.org/x/sync v0.20.0 // indirect
golang.org/x/sys v0.42.0 // indirect golang.org/x/sys v0.42.0 // indirect
golang.org/x/text v0.35.0 // indirect golang.org/x/text v0.35.0 // indirect

View File

@@ -322,8 +322,8 @@ golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0= golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw= golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=

View File

@@ -44,7 +44,7 @@ require (
go.uber.org/multierr v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect
go.yaml.in/yaml/v2 v2.4.4 // indirect go.yaml.in/yaml/v2 v2.4.4 // indirect
golang.org/x/crypto v0.49.0 // indirect golang.org/x/crypto v0.49.0 // indirect
golang.org/x/net v0.52.0 // indirect golang.org/x/net v0.51.0 // indirect
golang.org/x/sync v0.20.0 // indirect golang.org/x/sync v0.20.0 // indirect
golang.org/x/sys v0.42.0 // indirect golang.org/x/sys v0.42.0 // indirect
golang.org/x/text v0.35.0 // indirect golang.org/x/text v0.35.0 // indirect

View File

@@ -179,8 +179,8 @@ golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0= golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw= golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=

View File

@@ -47,7 +47,7 @@ require (
go.uber.org/multierr v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect
go.yaml.in/yaml/v2 v2.4.4 // indirect go.yaml.in/yaml/v2 v2.4.4 // indirect
golang.org/x/crypto v0.49.0 // indirect golang.org/x/crypto v0.49.0 // indirect
golang.org/x/net v0.52.0 // indirect golang.org/x/net v0.51.0 // indirect
golang.org/x/sync v0.20.0 // indirect golang.org/x/sync v0.20.0 // indirect
golang.org/x/sys v0.42.0 // indirect golang.org/x/sys v0.42.0 // indirect
golang.org/x/text v0.35.0 // indirect golang.org/x/text v0.35.0 // indirect

View File

@@ -181,8 +181,8 @@ golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0= golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw= golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=

View File

@@ -44,7 +44,7 @@ require (
go.uber.org/multierr v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect
go.yaml.in/yaml/v2 v2.4.4 // indirect go.yaml.in/yaml/v2 v2.4.4 // indirect
golang.org/x/crypto v0.49.0 // indirect golang.org/x/crypto v0.49.0 // indirect
golang.org/x/net v0.52.0 // indirect golang.org/x/net v0.51.0 // indirect
golang.org/x/sync v0.20.0 // indirect golang.org/x/sync v0.20.0 // indirect
golang.org/x/sys v0.42.0 // indirect golang.org/x/sys v0.42.0 // indirect
golang.org/x/text v0.35.0 // indirect golang.org/x/text v0.35.0 // indirect

View File

@@ -179,8 +179,8 @@ golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0= golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw= golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=

View File

@@ -94,7 +94,7 @@ require (
go.yaml.in/yaml/v2 v2.4.4 // indirect go.yaml.in/yaml/v2 v2.4.4 // indirect
golang.org/x/crypto v0.49.0 // indirect golang.org/x/crypto v0.49.0 // indirect
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 // indirect golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 // indirect
golang.org/x/net v0.52.0 // indirect golang.org/x/net v0.51.0 // indirect
golang.org/x/sync v0.20.0 // indirect golang.org/x/sync v0.20.0 // indirect
golang.org/x/sys v0.42.0 // indirect golang.org/x/sys v0.42.0 // indirect
golang.org/x/text v0.35.0 // indirect golang.org/x/text v0.35.0 // indirect

View File

@@ -337,8 +337,8 @@ golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0= golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw= golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=

View File

@@ -45,7 +45,7 @@ require (
go.uber.org/multierr v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect
go.yaml.in/yaml/v2 v2.4.4 // indirect go.yaml.in/yaml/v2 v2.4.4 // indirect
golang.org/x/crypto v0.49.0 // indirect golang.org/x/crypto v0.49.0 // indirect
golang.org/x/net v0.52.0 // indirect golang.org/x/net v0.51.0 // indirect
golang.org/x/sync v0.20.0 // indirect golang.org/x/sync v0.20.0 // indirect
golang.org/x/sys v0.42.0 // indirect golang.org/x/sys v0.42.0 // indirect
golang.org/x/text v0.35.0 // indirect golang.org/x/text v0.35.0 // indirect

View File

@@ -181,8 +181,8 @@ golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0= golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw= golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=

View File

@@ -47,7 +47,7 @@ require (
go.uber.org/multierr v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect
go.yaml.in/yaml/v2 v2.4.4 // indirect go.yaml.in/yaml/v2 v2.4.4 // indirect
golang.org/x/crypto v0.49.0 // indirect golang.org/x/crypto v0.49.0 // indirect
golang.org/x/net v0.52.0 // indirect golang.org/x/net v0.51.0 // indirect
golang.org/x/sync v0.20.0 // indirect golang.org/x/sync v0.20.0 // indirect
golang.org/x/sys v0.42.0 // indirect golang.org/x/sys v0.42.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260311181403-84a4fc48630c // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260311181403-84a4fc48630c // indirect

View File

@@ -196,8 +196,8 @@ golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0= golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw= golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=

View File

@@ -45,7 +45,7 @@ require (
go.uber.org/multierr v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect
go.yaml.in/yaml/v2 v2.4.4 // indirect go.yaml.in/yaml/v2 v2.4.4 // indirect
golang.org/x/crypto v0.49.0 // indirect golang.org/x/crypto v0.49.0 // indirect
golang.org/x/net v0.52.0 // indirect golang.org/x/net v0.51.0 // indirect
golang.org/x/sync v0.20.0 // indirect golang.org/x/sync v0.20.0 // indirect
golang.org/x/sys v0.42.0 // indirect golang.org/x/sys v0.42.0 // indirect
golang.org/x/text v0.35.0 // indirect golang.org/x/text v0.35.0 // indirect

View File

@@ -181,8 +181,8 @@ golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0= golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw= golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=

View File

@@ -60,7 +60,7 @@ require (
go.uber.org/multierr v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect
go.yaml.in/yaml/v2 v2.4.4 // indirect go.yaml.in/yaml/v2 v2.4.4 // indirect
golang.org/x/crypto v0.49.0 // indirect golang.org/x/crypto v0.49.0 // indirect
golang.org/x/net v0.52.0 // indirect golang.org/x/net v0.51.0 // indirect
golang.org/x/sync v0.20.0 // indirect golang.org/x/sync v0.20.0 // indirect
golang.org/x/sys v0.42.0 // indirect golang.org/x/sys v0.42.0 // indirect
golang.org/x/text v0.35.0 // indirect golang.org/x/text v0.35.0 // indirect

View File

@@ -182,8 +182,8 @@ golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0= golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw= golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=

View File

@@ -59,7 +59,7 @@ require (
go.uber.org/multierr v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect
go.yaml.in/yaml/v2 v2.4.4 // indirect go.yaml.in/yaml/v2 v2.4.4 // indirect
golang.org/x/crypto v0.49.0 // indirect golang.org/x/crypto v0.49.0 // indirect
golang.org/x/net v0.52.0 // indirect golang.org/x/net v0.51.0 // indirect
golang.org/x/sync v0.20.0 // indirect golang.org/x/sync v0.20.0 // indirect
golang.org/x/sys v0.42.0 // indirect golang.org/x/sys v0.42.0 // indirect
golang.org/x/text v0.35.0 // indirect golang.org/x/text v0.35.0 // indirect

View File

@@ -182,8 +182,8 @@ golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0= golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw= golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=

View File

@@ -108,7 +108,7 @@ require (
go.opentelemetry.io/otel/trace v1.39.0 // indirect go.opentelemetry.io/otel/trace v1.39.0 // indirect
go.uber.org/multierr v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect
go.yaml.in/yaml/v2 v2.4.4 // indirect go.yaml.in/yaml/v2 v2.4.4 // indirect
golang.org/x/net v0.52.0 // indirect golang.org/x/net v0.51.0 // indirect
golang.org/x/sync v0.20.0 // indirect golang.org/x/sync v0.20.0 // indirect
golang.org/x/sys v0.42.0 // indirect golang.org/x/sys v0.42.0 // indirect
golang.org/x/text v0.35.0 // indirect golang.org/x/text v0.35.0 // indirect

View File

@@ -289,8 +289,8 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0= golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw= golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=

View File

@@ -48,6 +48,4 @@ message GetOperationDocumentRequest {
int64 started_at_unix_ms = 12; int64 started_at_unix_ms = 12;
int64 completed_at_unix_ms = 13; int64 completed_at_unix_ms = 13;
string client_name = 14;
string client_address = 15;
} }

View File

@@ -1,17 +1,20 @@
import 'package:money2/money2.dart';
import 'package:pshared/data/dto/money.dart'; import 'package:pshared/data/dto/money.dart';
import 'package:pshared/models/money.dart'; import 'package:pshared/utils/money.dart';
extension MoneyMapper on Money { extension MoneyMapper on Money {
MoneyDTO toDTO() => MoneyDTO( MoneyDTO toDTO() =>
amount: amount, MoneyDTO(amount: toDecimal().toString(), currency: currency.isoCode);
currency: currency,
);
} }
extension MoneyDTOMapper on MoneyDTO { extension MoneyDTOMapper on MoneyDTO {
Money toDomain() => Money( Money toDomain() {
amount: amount, final parsed = parseMoneyWithCurrencyCode(amount, currency);
currency: currency, if (parsed == null) {
); throw FormatException('Invalid money dto: $currency $amount');
}
return parsed;
}
} }

View File

@@ -1,18 +0,0 @@
import 'package:pshared/data/dto/wallet/chain_asset.dart';
import 'package:pshared/data/mapper/payment/enums.dart';
import 'package:pshared/models/wallet/chain_asset.dart';
extension ChainAssetDTOMapper on ChainAssetDTO {
ChainAsset toDomain() => ChainAsset(
chain: chainNetworkFromValue(chain),
tokenSymbol: tokenSymbol,
);
}
extension ChainAssetMapper on ChainAsset {
ChainAssetDTO toDTO() => ChainAssetDTO(
chain: chainNetworkToValue(chain),
tokenSymbol: tokenSymbol,
);
}

View File

@@ -1,16 +1,13 @@
import 'package:pshared/models/wallet/wallet.dart' as domain; import 'package:pshared/models/wallet/wallet.dart' as domain;
import 'package:pshared/models/payment/wallet.dart'; import 'package:pshared/models/payment/wallet.dart';
import 'package:pshared/utils/currency.dart'; import 'package:pshared/utils/currency.dart';
import 'package:pshared/utils/money.dart';
extension WalletUiMapper on domain.WalletModel { extension WalletUiMapper on domain.WalletModel {
Wallet toUi() => Wallet( Wallet toUi() => Wallet(
id: walletRef, id: walletRef,
walletUserID: walletRef, walletUserID: walletRef,
balance: parseMoneyAmount( balance: availableMoney?.toDouble() ?? balance?.available?.toDouble() ?? 0,
availableMoney?.amount ?? balance?.available?.amount,
),
currency: currencyStringToCode(asset.tokenSymbol), currency: currencyStringToCode(asset.tokenSymbol),
calculatedAt: balance?.calculatedAt ?? DateTime.now(), calculatedAt: balance?.calculatedAt ?? DateTime.now(),
depositAddress: depositAddress, depositAddress: depositAddress,

View File

@@ -16,15 +16,19 @@ extension WalletDTOMapper on WalletDTO {
depositAddress: depositAddress, depositAddress: depositAddress,
status: status, status: status,
metadata: metadata, metadata: metadata,
createdAt: (createdAt == null || createdAt!.isEmpty) ? null : DateTime.tryParse(createdAt!), createdAt: (createdAt == null || createdAt!.isEmpty)
updatedAt: (updatedAt == null || updatedAt!.isEmpty) ? null : DateTime.tryParse(updatedAt!), ? null
: DateTime.tryParse(createdAt!),
updatedAt: (updatedAt == null || updatedAt!.isEmpty)
? null
: DateTime.tryParse(updatedAt!),
balance: balance?.toDomain(), balance: balance?.toDomain(),
availableMoney: balance?.available?.toDomain(), availableMoney: balance?.available?.toDomain(),
describable: newDescribable( describable: newDescribable(
name: name.isNotEmpty ? name : (metadata?['name']?.toString() ?? ''), name: name.isNotEmpty ? name : (metadata?['name']?.toString() ?? ''),
description: (description != null && description!.isNotEmpty) description: (description != null && description!.isNotEmpty)
? description ? description
: metadata?['description'], : metadata?['description'],
), ),
); );
} }

View File

@@ -1,18 +0,0 @@
import 'package:pshared/models/currency.dart';
import 'package:pshared/utils/currency.dart';
class Asset {
final Currency currency;
final double amount;
const Asset({
required this.currency,
required this.amount,
});
}
Asset createAsset(String currencyCode, String amount) => Asset(
currency: currencyStringToCode(currencyCode),
amount: double.parse(amount),
);

View File

@@ -1 +1 @@
enum Currency {usd, eur, rub, usdt, usdc} enum CurrencyCode {usd, eur, rub, usdt, usdc}

View File

@@ -1,4 +1,4 @@
import 'package:pshared/models/money.dart'; import 'package:money2/money2.dart';
class LedgerBalance { class LedgerBalance {

View File

@@ -1,9 +0,0 @@
class Money {
final String amount;
final String currency;
const Money({
required this.amount,
required this.currency,
});
}

View File

@@ -1,4 +1,4 @@
import 'package:pshared/models/money.dart'; import 'package:money2/money2.dart';
class PaymentExecutionOperation { class PaymentExecutionOperation {

View File

@@ -1,4 +1,4 @@
import 'package:pshared/models/money.dart'; import 'package:money2/money2.dart';
class FeeLine { class FeeLine {

View File

@@ -1,4 +1,5 @@
import 'package:pshared/models/money.dart'; import 'package:money2/money2.dart';
class FxQuote { class FxQuote {
final String? quoteRef; final String? quoteRef;

View File

@@ -1,11 +1,13 @@
import 'package:money2/money2.dart';
import 'package:pshared/models/payment/fees/treatment.dart'; import 'package:pshared/models/payment/fees/treatment.dart';
import 'package:pshared/models/payment/fx/intent.dart'; import 'package:pshared/models/payment/fx/intent.dart';
import 'package:pshared/models/payment/kind.dart'; import 'package:pshared/models/payment/kind.dart';
import 'package:pshared/models/payment/customer.dart'; import 'package:pshared/models/payment/customer.dart';
import 'package:pshared/models/payment/methods/data.dart'; import 'package:pshared/models/payment/methods/data.dart';
import 'package:pshared/models/money.dart';
import 'package:pshared/models/payment/settlement_mode.dart'; import 'package:pshared/models/payment/settlement_mode.dart';
class PaymentIntent { class PaymentIntent {
final PaymentKind kind; final PaymentKind kind;
final String? sourceRef; final String? sourceRef;

View File

@@ -1,4 +1,5 @@
import 'package:pshared/models/money.dart'; import 'package:money2/money2.dart';
class QuoteAmounts { class QuoteAmounts {
final Money? sourcePrincipal; final Money? sourcePrincipal;

View File

@@ -7,7 +7,7 @@ class Wallet implements Describable {
final String id; final String id;
final String walletUserID; // ID or number that we show the user final String walletUserID; // ID or number that we show the user
final double balance; final double balance;
final Currency currency; final CurrencyCode currency;
final DateTime calculatedAt; final DateTime calculatedAt;
final String? depositAddress; final String? depositAddress;
final ChainNetwork? network; final ChainNetwork? network;

View File

@@ -1,12 +1,14 @@
import 'package:pshared/models/wallet/chain_asset.dart'; import 'package:pshared/models/payment/chain_network.dart';
class WalletAsset extends ChainAsset { class WalletAsset {
final ChainNetwork chain;
final String tokenSymbol;
final String contractAddress; final String contractAddress;
const WalletAsset({ const WalletAsset({
required super.chain, required this.chain,
required super.tokenSymbol, required this.tokenSymbol,
required this.contractAddress, required this.contractAddress,
}); });
} }

View File

@@ -1,4 +1,4 @@
import 'package:pshared/models/money.dart'; import 'package:money2/money2.dart';
class WalletBalance { class WalletBalance {

View File

@@ -1,12 +0,0 @@
import 'package:pshared/models/payment/chain_network.dart';
class ChainAsset {
final ChainNetwork chain;
final String tokenSymbol;
const ChainAsset({
required this.chain,
required this.tokenSymbol,
});
}

View File

@@ -1,5 +1,5 @@
import 'package:pshared/models/describable.dart'; import 'package:pshared/models/describable.dart';
import 'package:pshared/models/money.dart'; import 'package:money2/money2.dart';
import 'package:pshared/models/wallet/asset.dart'; import 'package:pshared/models/wallet/asset.dart';
import 'package:pshared/models/wallet/balance.dart'; import 'package:pshared/models/wallet/balance.dart';
@@ -39,9 +39,7 @@ class WalletModel implements Describable {
required this.describable, required this.describable,
}); });
WalletModel copyWith({ WalletModel copyWith({Describable? describable}) => WalletModel(
Describable? describable,
}) => WalletModel(
walletRef: walletRef, walletRef: walletRef,
organizationRef: organizationRef, organizationRef: organizationRef,
ownerRef: ownerRef, ownerRef: ownerRef,

View File

@@ -15,6 +15,7 @@ import 'package:pshared/provider/resource.dart';
import 'package:pshared/service/ledger.dart'; import 'package:pshared/service/ledger.dart';
import 'package:pshared/utils/exception.dart'; import 'package:pshared/utils/exception.dart';
class LedgerAccountsProvider with ChangeNotifier { class LedgerAccountsProvider with ChangeNotifier {
final LedgerService _service; final LedgerService _service;
OrganizationsProvider? _organizations; OrganizationsProvider? _organizations;
@@ -179,7 +180,7 @@ class LedgerAccountsProvider with ChangeNotifier {
Future<void> create({ Future<void> create({
required Describable describable, required Describable describable,
required Currency currency, required CurrencyCode currency,
String? ownerRef, String? ownerRef,
}) async { }) async {
final org = _organizations; final org = _organizations;

View File

@@ -1,3 +1,5 @@
import 'package:money2/money2.dart';
import 'package:pshared/controllers/payment/source.dart'; import 'package:pshared/controllers/payment/source.dart';
import 'package:pshared/models/payment/asset.dart'; import 'package:pshared/models/payment/asset.dart';
import 'package:pshared/models/payment/chain_network.dart'; import 'package:pshared/models/payment/chain_network.dart';
@@ -13,19 +15,18 @@ import 'package:pshared/models/payment/methods/data.dart';
import 'package:pshared/models/payment/methods/ledger.dart'; import 'package:pshared/models/payment/methods/ledger.dart';
import 'package:pshared/models/payment/methods/managed_wallet.dart'; import 'package:pshared/models/payment/methods/managed_wallet.dart';
import 'package:pshared/models/payment/methods/type.dart'; import 'package:pshared/models/payment/methods/type.dart';
import 'package:pshared/models/money.dart';
import 'package:pshared/models/payment/settlement_mode.dart'; import 'package:pshared/models/payment/settlement_mode.dart';
import 'package:pshared/models/payment/intent.dart'; import 'package:pshared/models/payment/intent.dart';
import 'package:pshared/models/recipient/recipient.dart'; import 'package:pshared/models/recipient/recipient.dart';
import 'package:pshared/provider/payment/amount.dart'; import 'package:pshared/provider/payment/amount.dart';
import 'package:pshared/provider/payment/flow.dart'; import 'package:pshared/provider/payment/flow.dart';
import 'package:pshared/provider/recipient/provider.dart'; import 'package:pshared/provider/recipient/provider.dart';
import 'package:pshared/utils/currency.dart';
import 'package:pshared/utils/payment/fx_helpers.dart'; import 'package:pshared/utils/payment/fx_helpers.dart';
class QuotationIntentBuilder { class QuotationIntentBuilder {
static const String _settlementCurrency = 'RUB'; static const String _settlementCurrency = 'RUB';
static const String _addressBookCustomerFallbackId = 'address_book_customer';
PaymentIntent? build({ PaymentIntent? build({
required PaymentAmountProvider payment, required PaymentAmountProvider payment,
@@ -38,10 +39,9 @@ class QuotationIntentBuilder {
final paymentData = flow.selectedPaymentData; final paymentData = flow.selectedPaymentData;
final selectedMethod = flow.selectedMethod; final selectedMethod = flow.selectedMethod;
final amountValue = payment.amount; final amountValue = payment.amount;
if (sourceMethod == null || sourceCurrency == null || paymentData == null) { if (sourceCurrency == null) {
return null; return null;
} }
if (amountValue == null) return null;
final customer = _buildCustomer( final customer = _buildCustomer(
recipient: recipients.currentObject, recipient: recipients.currentObject,
@@ -51,22 +51,22 @@ class QuotationIntentBuilder {
final amountCurrency = payment.settlementMode == SettlementMode.fixReceived final amountCurrency = payment.settlementMode == SettlementMode.fixReceived
? _settlementCurrency ? _settlementCurrency
: sourceCurrency; : sourceCurrency;
final amount = Money( final currency = money2CurrencyFromCode(amountCurrency);
amount: amountValue.toString(), if (currency == null) return null;
currency: amountCurrency, final amount = amountValue == null
); ? null
: Money.fromNumWithCurrency(amountValue, currency);
final isLedgerSource = source.selectedLedgerAccount != null; final isLedgerSource = source.selectedLedgerAccount != null;
final isCryptoToCrypto = final isCryptoToCrypto =
paymentData is CryptoAddressPaymentMethod && paymentData is CryptoAddressPaymentMethod &&
(paymentData.asset?.tokenSymbol ?? '').trim().toUpperCase() == (paymentData.asset?.tokenSymbol ?? '').trim().toUpperCase() ==
amount.currency; sourceCurrency.trim().toUpperCase();
final fxIntent = _buildFxIntent( final fxIntent = _buildFxIntent(
sourceCurrency: sourceCurrency, sourceCurrency: sourceCurrency,
settlementMode: payment.settlementMode, settlementMode: payment.settlementMode,
isLedgerSource: isLedgerSource, isLedgerSource: isLedgerSource,
enabled: !isCryptoToCrypto, enabled: !isCryptoToCrypto,
); );
final comment = _resolveComment(payment.comment);
return PaymentIntent( return PaymentIntent(
kind: PaymentKind.payout, kind: PaymentKind.payout,
amount: amount, amount: amount,
@@ -77,7 +77,7 @@ class QuotationIntentBuilder {
? FeeTreatment.addToSource ? FeeTreatment.addToSource
: FeeTreatment.deductFromDestination, : FeeTreatment.deductFromDestination,
settlementMode: payment.settlementMode, settlementMode: payment.settlementMode,
comment: comment, comment: payment.comment,
customer: customer, customer: customer,
); );
} }
@@ -94,14 +94,9 @@ class QuotationIntentBuilder {
// BFF maps only settlement currency + fx side, then quotation derives pair. // BFF maps only settlement currency + fx side, then quotation derives pair.
// For ledger this preserves source debit in ledger currency (e.g. USDT). // For ledger this preserves source debit in ledger currency (e.g. USDT).
if (isLedgerSource && settlementMode == SettlementMode.fixReceived) { if (isLedgerSource && settlementMode == SettlementMode.fixReceived) {
final base = sourceCurrency.trim();
final quote = _settlementCurrency;
if (base.isEmpty || base.toUpperCase() == quote.toUpperCase()) {
return null;
}
return FxIntent( return FxIntent(
pair: CurrencyPair(base: base, quote: quote), pair: CurrencyPair(base: sourceCurrency, quote: _settlementCurrency),
side: FxSide.sellBaseBuyQuote, side: FxSide.buyBaseSellQuote,
); );
} }
@@ -137,39 +132,59 @@ class QuotationIntentBuilder {
required PaymentMethod? method, required PaymentMethod? method,
required PaymentMethodData? data, required PaymentMethodData? data,
}) { }) {
final name = recipient?.name.trim(); final customerId = recipient?.id.trim() ?? '';
if (name == null || name.isEmpty) return null; final card = _resolveCard(method: method, data: data);
final id = recipient?.id.trim(); final fromRecipient = _buildCustomerFromName(
final customerId = id == null || id.isEmpty customerId: customerId,
? _addressBookCustomerFallbackId fullName: recipient?.name,
: id; country: card?.country,
);
if (fromRecipient != null) return fromRecipient;
final parts = name.split(RegExp(r'\s+')); if (card != null) {
final firstName = _normalizedOrNull(card.firstName);
final lastName = _normalizedOrNull(card.lastName);
if (firstName == null && lastName == null) return null;
return Customer(
id: customerId,
firstName: firstName,
lastName: lastName,
country: card.country,
);
}
return null;
}
CardPaymentMethod? _resolveCard({
required PaymentMethod? method,
required PaymentMethodData? data,
}) => method?.cardData ?? (data is CardPaymentMethod ? data : null);
Customer? _buildCustomerFromName({
required String customerId,
required String? fullName,
String? country,
}) {
final normalizedName = _normalizedOrNull(fullName);
if (normalizedName == null) return null;
final parts = normalizedName.split(RegExp(r'\s+'));
final firstName = parts.isNotEmpty ? parts.first : null; final firstName = parts.isNotEmpty ? parts.first : null;
final lastName = parts.length >= 2 ? parts.last : null; final lastName = parts.length >= 2 ? parts.last : null;
final middleName = parts.length > 2 final middleName = parts.length > 2
? parts.sublist(1, parts.length - 1).join(' ') ? parts.sublist(1, parts.length - 1).join(' ')
: null; : null;
return Customer( return Customer(
id: customerId, id: customerId,
firstName: firstName, firstName: firstName,
middleName: middleName, middleName: middleName,
lastName: lastName, lastName: lastName,
country: _resolveCustomerCountry(method: method, data: data), country: country,
); );
} }
String? _resolveCustomerCountry({ String? _normalizedOrNull(String? value) {
required PaymentMethod? method, if (value == null) return null;
required PaymentMethodData? data, final normalized = value.trim();
}) {
final card = method?.cardData ?? (data is CardPaymentMethod ? data : null);
return card?.country;
}
String? _resolveComment(String comment) {
final normalized = comment.trim();
return normalized.isEmpty ? null : normalized; return normalized.isEmpty ? null : normalized;
} }
} }

View File

@@ -6,13 +6,13 @@ import 'package:logging/logging.dart';
import 'package:uuid/uuid.dart'; import 'package:uuid/uuid.dart';
import 'package:money2/money2.dart';
import 'package:pshared/api/requests/payment/quote.dart'; import 'package:pshared/api/requests/payment/quote.dart';
import 'package:pshared/controllers/payment/source.dart'; import 'package:pshared/controllers/payment/source.dart';
import 'package:pshared/data/mapper/payment/intent/payment.dart'; import 'package:pshared/data/mapper/payment/intent/payment.dart';
import 'package:pshared/models/asset.dart';
import 'package:pshared/models/payment/intent.dart'; import 'package:pshared/models/payment/intent.dart';
import 'package:pshared/models/payment/quote/quote.dart'; import 'package:pshared/models/payment/quote/quote.dart';
import 'package:pshared/models/money.dart';
import 'package:pshared/models/auto_refresh_mode.dart'; import 'package:pshared/models/auto_refresh_mode.dart';
import 'package:pshared/provider/organizations.dart'; import 'package:pshared/provider/organizations.dart';
import 'package:pshared/provider/payment/amount.dart'; import 'package:pshared/provider/payment/amount.dart';
@@ -79,20 +79,12 @@ class QuotationProvider extends ChangeNotifier {
return DateTime.fromMillisecondsSinceEpoch(expiresAtUnixMs, isUtc: true); return DateTime.fromMillisecondsSinceEpoch(expiresAtUnixMs, isUtc: true);
} }
Asset? get fee => _assetFromMoney(quoteFeeTotal(quotation)); Money? get fee => quoteFeeTotal(quotation);
Asset? get total => _assetFromMoney( Money? get total => quoteSourceDebitTotal(
quoteSourceDebitTotal( quotation,
quotation, preferredSourceCurrency: _sourceCurrencyCode,
preferredSourceCurrency: _sourceCurrencyCode,
),
); );
Asset? get recipientGets => Money? get recipientGets => quotation?.amounts?.destinationSettlement;
_assetFromMoney(quotation?.amounts?.destinationSettlement);
Asset? _assetFromMoney(Money? money) {
if (money == null) return null;
return createAsset(money.currency, money.amount);
}
void _setResource(Resource<PaymentQuote> quotation) { void _setResource(Resource<PaymentQuote> quotation) {
_quotation = quotation; _quotation = quotation;

View File

@@ -5,14 +5,16 @@ import 'package:flutter/foundation.dart';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:pshared/models/currency.dart';
import 'package:pshared/models/describable.dart'; import 'package:pshared/models/describable.dart';
import 'package:pshared/models/payment/chain_network.dart';
import 'package:pshared/models/payment/wallet.dart'; import 'package:pshared/models/payment/wallet.dart';
import 'package:pshared/models/wallet/chain_asset.dart';
import 'package:pshared/provider/organizations.dart'; import 'package:pshared/provider/organizations.dart';
import 'package:pshared/provider/resource.dart'; import 'package:pshared/provider/resource.dart';
import 'package:pshared/service/payment/wallets.dart'; import 'package:pshared/service/payment/wallets.dart';
import 'package:pshared/utils/exception.dart'; import 'package:pshared/utils/exception.dart';
class WalletsProvider with ChangeNotifier { class WalletsProvider with ChangeNotifier {
final WalletsService _service; final WalletsService _service;
OrganizationsProvider? _organizations; OrganizationsProvider? _organizations;
@@ -180,7 +182,8 @@ class WalletsProvider with ChangeNotifier {
Future<void> create({ Future<void> create({
required Describable describable, required Describable describable,
required ChainAsset asset, required ChainNetwork chain,
required CurrencyCode currency,
required String? ownerRef, required String? ownerRef,
}) async { }) async {
final org = _organizations; final org = _organizations;
@@ -195,7 +198,8 @@ class WalletsProvider with ChangeNotifier {
await _service.create( await _service.create(
organizationRef: org.current.id, organizationRef: org.current.id,
describable: describable, describable: describable,
asset: asset, chain: chain,
currency: currency,
ownerRef: ownerRef, ownerRef: ownerRef,
); );
await loadWalletsWithBalances(); await loadWalletsWithBalances();

View File

@@ -43,7 +43,7 @@ class LedgerService {
required String organizationRef, required String organizationRef,
required Describable describable, required Describable describable,
required String? ownerRef, required String? ownerRef,
required Currency currency, required CurrencyCode currency,
}) async => AuthorizationService.getPOSTResponse( }) async => AuthorizationService.getPOSTResponse(
_objectType, _objectType,
'/$organizationRef', '/$organizationRef',

View File

@@ -13,12 +13,10 @@ class PaymentDocumentsService {
String organizationRef, String organizationRef,
String gatewayService, String gatewayService,
String operationRef, String operationRef,
String paymentRef,
) async { ) async {
final query = <String, String>{ final query = <String, String>{
'gateway_service': gatewayService, 'gateway_service': gatewayService,
'operation_ref': operationRef, 'operation_ref': operationRef,
'payment_ref': paymentRef,
}; };
final queryString = Uri(queryParameters: query).query; final queryString = Uri(queryParameters: query).query;
final url = '/documents/operation/$organizationRef?$queryString'; final url = '/documents/operation/$organizationRef?$queryString';

View File

@@ -1,9 +1,9 @@
import 'package:pshared/data/mapper/wallet/ui.dart'; import 'package:pshared/data/mapper/wallet/ui.dart';
import 'package:pshared/models/currency.dart';
import 'package:pshared/models/describable.dart'; import 'package:pshared/models/describable.dart';
import 'package:pshared/models/payment/chain_network.dart';
import 'package:pshared/models/payment/wallet.dart'; import 'package:pshared/models/payment/wallet.dart';
import 'package:pshared/models/wallet/chain_asset.dart';
import 'package:pshared/service/wallet.dart' as shared_wallet_service; import 'package:pshared/service/wallet.dart' as shared_wallet_service;
import 'package:pshared/utils/money.dart';
abstract class WalletsService { abstract class WalletsService {
@@ -12,7 +12,8 @@ abstract class WalletsService {
Future<void> create({ Future<void> create({
required String organizationRef, required String organizationRef,
required Describable describable, required Describable describable,
required ChainAsset asset, required ChainNetwork chain,
required CurrencyCode currency,
required String? ownerRef, required String? ownerRef,
}); });
} }
@@ -30,19 +31,21 @@ class ApiWalletsService implements WalletsService {
organizationRef: organizationRef, organizationRef: organizationRef,
walletRef: walletRef, walletRef: walletRef,
); );
return parseMoneyAmount(balance.available?.amount); return balance.available?.toDouble() ?? 0;
} }
@override @override
Future<void> create({ Future<void> create({
required String organizationRef, required String organizationRef,
required Describable describable, required Describable describable,
required ChainAsset asset, required ChainNetwork chain,
required CurrencyCode currency,
required String? ownerRef, required String? ownerRef,
}) => shared_wallet_service.WalletService.create( }) => shared_wallet_service.WalletService.create(
organizationRef: organizationRef, organizationRef: organizationRef,
describable: describable, describable: describable,
asset: asset, chain: chain,
currency: currency,
ownerRef: ownerRef, ownerRef: ownerRef,
); );
} }

View File

@@ -1,15 +1,18 @@
import 'package:pshared/api/requests/wallet/create.dart'; import 'package:pshared/api/requests/wallet/create.dart';
import 'package:pshared/api/responses/wallet_balance.dart'; import 'package:pshared/api/responses/wallet_balance.dart';
import 'package:pshared/api/responses/wallets.dart'; import 'package:pshared/api/responses/wallets.dart';
import 'package:pshared/data/dto/wallet/chain_asset.dart';
import 'package:pshared/data/mapper/describable.dart'; import 'package:pshared/data/mapper/describable.dart';
import 'package:pshared/data/mapper/wallet/chain_asset.dart'; import 'package:pshared/data/mapper/payment/enums.dart';
import 'package:pshared/data/mapper/wallet/response.dart'; import 'package:pshared/data/mapper/wallet/response.dart';
import 'package:pshared/models/currency.dart';
import 'package:pshared/models/describable.dart'; import 'package:pshared/models/describable.dart';
import 'package:pshared/models/payment/chain_network.dart';
import 'package:pshared/models/wallet/balance.dart'; import 'package:pshared/models/wallet/balance.dart';
import 'package:pshared/models/wallet/chain_asset.dart';
import 'package:pshared/models/wallet/wallet.dart'; import 'package:pshared/models/wallet/wallet.dart';
import 'package:pshared/service/authorization/service.dart'; import 'package:pshared/service/authorization/service.dart';
import 'package:pshared/service/services.dart'; import 'package:pshared/service/services.dart';
import 'package:pshared/utils/currency.dart';
class WalletService { class WalletService {
@@ -37,13 +40,19 @@ class WalletService {
static Future<void> create({ static Future<void> create({
required String organizationRef, required String organizationRef,
required Describable describable, required Describable describable,
required ChainAsset asset, required ChainNetwork chain,
required CurrencyCode currency,
required String? ownerRef, required String? ownerRef,
}) async => AuthorizationService.getPOSTResponse( }) async => AuthorizationService.getPOSTResponse(
_objectType, _objectType,
'/$organizationRef', '/$organizationRef',
CreateWalletRequest( CreateWalletRequest(
asset: asset.toDTO(), asset: ChainAssetDTO(
chain: chainNetworkToValue(chain),
tokenSymbol:
money2CurrencyFromCode(currencyCodeToString(currency))?.isoCode ??
currencyCodeToString(currency),
),
describable: describable.toDTO(), describable: describable.toDTO(),
ownerRef: ownerRef, ownerRef: ownerRef,
).toJson(), ).toJson(),

View File

@@ -1,102 +1,78 @@
import 'package:flutter/material.dart'; import 'package:money2/money2.dart';
import 'package:pshared/models/asset.dart';
import 'package:pshared/models/currency.dart'; import 'package:pshared/models/currency.dart';
const nonBreakingSpace = '\u00A0'; final Currency _usdtCurrency = Currency.create(
'USDT',
6,
symbol: '',
isIso: false,
country: 'Digital',
unit: 'Tether',
name: 'Tether',
);
String withTrailingNonBreakingSpace(String value) { final Currency _usdcCurrency = Currency.create(
return '$value$nonBreakingSpace'; 'USDC',
} 6,
symbol: r'($)',
isIso: false,
country: 'Digital',
unit: 'USD Coin',
name: 'USD Coin',
);
String joinWithNonBreakingSpace(String left, String right) { final Map<String, Currency> _commonCurrenciesByCode =
return '$left$nonBreakingSpace$right'; <String, Currency>{
} for (final currency in CommonCurrencies().asList())
currency.isoCode: currency,
_usdtCurrency.isoCode: _usdtCurrency,
_usdcCurrency.isoCode: _usdcCurrency,
};
String currencyCodeToSymbol(Currency currencyCode) { String currencyCodeToSymbol(CurrencyCode currencyCode) {
switch (currencyCode) { final symbol = currencySymbolFromCode(currencyCodeToString(currencyCode));
case Currency.usd: if (symbol == null || symbol.trim().isEmpty) {
return '\$'; return currencyCodeToString(currencyCode);
case Currency.usdt:
return '';
case Currency.usdc:
return '\$';
case Currency.rub:
return '';
case Currency.eur:
return '';
} }
return symbol;
} }
String amountToString(double amount) { String currencyToString(CurrencyCode currencyCode, double amount) {
return amount.toStringAsFixed(2); final code = currencyCodeToString(currencyCode);
} final currency = money2CurrencyFromCode(code);
if (currency == null) {
String currencyToString(Currency currencyCode, double amount) { return '$amount $code';
return joinWithNonBreakingSpace(
currencyCodeToSymbol(currencyCode),
amountToString(amount),
);
}
String assetToString(Asset asset) {
return currencyToString(asset.currency, asset.amount);
}
Currency currencyStringToCode(String currencyCode) {
switch (currencyCode) {
case 'USD':
return Currency.usd;
case 'USDT':
return Currency.usdt;
case 'USDC':
return Currency.usdc;
case 'RUB':
return Currency.rub;
case 'EUR':
return Currency.eur;
default:
throw ArgumentError('Unknown currency code: $currencyCode');
} }
final money = Money.fromNumWithCurrency(amount, currency);
return money.toString();
} }
String currencyCodeToString(Currency currencyCode) { CurrencyCode currencyStringToCode(String currencyCode) {
switch (currencyCode) { final normalized = currencyCode.trim().toUpperCase();
case Currency.usd: for (final value in CurrencyCode.values) {
return 'USD'; if (currencyCodeToString(value) == normalized) {
case Currency.usdt: return value;
return 'USDT'; }
case Currency.usdc:
return 'USDC';
case Currency.rub:
return 'RUB';
case Currency.eur:
return 'EUR';
} }
throw ArgumentError('Unknown currency code: $currencyCode');
} }
IconData iconForCurrencyType(Currency currencyCode) { String currencyCodeToString(CurrencyCode currencyCode) {
switch (currencyCode) { return currencyCode.name.toUpperCase();
case Currency.usd:
return Icons.currency_exchange;
case Currency.eur:
return Icons.currency_exchange;
case Currency.rub:
return Icons.currency_ruble;
case Currency.usdt:
return Icons.currency_exchange;
case Currency.usdc:
return Icons.money;
}
} }
String? currencySymbolFromCode(String? code) { String? currencySymbolFromCode(String? code) {
final normalized = code?.trim(); final currency = money2CurrencyFromCode(code);
if (normalized == null || normalized.isEmpty) return null; if (currency == null) return null;
try { final symbol = currency.symbol.trim();
return currencyCodeToSymbol(currencyStringToCode(normalized.toUpperCase())); return symbol.isEmpty ? null : symbol;
} catch (_) { }
return null;
} Currency? money2CurrencyFromCode(String? code) {
final normalized = code?.trim().toUpperCase();
if (normalized == null || normalized.isEmpty) return null;
return _commonCurrenciesByCode[normalized] ?? Currencies().find(normalized);
} }

View File

@@ -1,41 +1,35 @@
import 'package:pshared/models/money.dart'; import 'package:money2/money2.dart';
import 'package:pshared/utils/currency.dart'; import 'package:pshared/utils/currency.dart';
const String _decimalMoneyPattern = '0.##################';
double parseMoneyAmount(String? raw, {double fallback = 0}) { Money? parseMoneyWithCurrency(String? amount, Currency? currency) {
final trimmed = raw?.trim(); if (currency == null) return null;
if (trimmed == null || trimmed.isEmpty) return fallback; final value = _normalizeMoneyAmount(amount);
return double.tryParse(trimmed) ?? fallback; if (value == null || value.isEmpty) return null;
try {
return Money.parseWithCurrency(
value,
currency,
pattern: _decimalMoneyPattern,
);
} catch (_) {
return null;
}
} }
String formatMoneyDisplay( Money? parseMoneyWithCurrencyCode(String? amount, String? currencyCode) {
Money? money, { return parseMoneyWithCurrency(amount, money2CurrencyFromCode(currencyCode));
String fallback = '--',
String separator = ' ',
String invalidAmountFallback = '',
}) {
if (money == null) return fallback;
final rawAmount = money.amount.trim();
final rawCurrency = money.currency.trim();
final parsedAmount = parseMoneyAmount(rawAmount, fallback: double.nan);
final amountToken = parsedAmount.isNaN
? (rawAmount.isEmpty ? invalidAmountFallback : rawAmount)
: amountToString(parsedAmount);
final symbol = currencySymbolFromCode(rawCurrency);
final normalizedSymbol = symbol?.trim() ?? '';
final hasSymbol = normalizedSymbol.isNotEmpty;
final currencyToken = hasSymbol ? normalizedSymbol : rawCurrency;
final first = amountToken;
final second = currencyToken;
if (first.isEmpty && second.isEmpty) return fallback;
if (first.isEmpty) return second;
if (second.isEmpty) return first;
return '$first$separator$second';
} }
extension MoneyAmountX on Money { String? _normalizeMoneyAmount(String? value) {
double get amountValue => parseMoneyAmount(amount); final normalized = value?.trim();
if (normalized == null || normalized.isEmpty) return null;
if (normalized.contains(',') && !normalized.contains('.')) {
return normalized.replaceAll(',', '.');
}
return normalized;
} }

View File

@@ -1,7 +1,8 @@
import 'package:money2/money2.dart';
import 'package:pshared/models/payment/currency_pair.dart'; import 'package:pshared/models/payment/currency_pair.dart';
import 'package:pshared/models/payment/fx/intent.dart'; import 'package:pshared/models/payment/fx/intent.dart';
import 'package:pshared/models/payment/fx/side.dart'; import 'package:pshared/models/payment/fx/side.dart';
import 'package:pshared/models/money.dart';
class FxIntentHelper { class FxIntentHelper {
@@ -37,11 +38,15 @@ class FxIntentHelper {
case FxSide.unspecified: case FxSide.unspecified:
break; break;
} }
if (amount.currency == pair.base && pair.quote.isNotEmpty) return pair.quote; if (amount.currency.isoCode == pair.base && pair.quote.isNotEmpty) {
if (amount.currency == pair.quote && pair.base.isNotEmpty) return pair.base; return pair.quote;
}
if (amount.currency.isoCode == pair.quote && pair.base.isNotEmpty) {
return pair.base;
}
if (pair.quote.isNotEmpty) return pair.quote; if (pair.quote.isNotEmpty) return pair.quote;
if (pair.base.isNotEmpty) return pair.base; if (pair.base.isNotEmpty) return pair.base;
} }
return amount.currency; return amount.currency.isoCode;
} }
} }

View File

@@ -12,7 +12,7 @@ class PaymentQuotationCurrencyResolver {
PaymentMethodData? paymentData, PaymentMethodData? paymentData,
}) { }) {
final quoteCurrency = _normalizeCurrency( final quoteCurrency = _normalizeCurrency(
quote?.amounts?.destinationSettlement?.currency, quote?.amounts?.destinationSettlement?.currency.isoCode,
); );
if (quoteCurrency != null) return quoteCurrency; if (quoteCurrency != null) return quoteCurrency;

View File

@@ -1,17 +1,16 @@
import 'package:pshared/models/money.dart'; import 'package:money2/money2.dart';
import 'package:pshared/models/payment/fees/line.dart'; import 'package:pshared/models/payment/fees/line.dart';
import 'package:pshared/models/payment/quote/quote.dart'; import 'package:pshared/models/payment/quote/quote.dart';
import 'package:pshared/utils/currency.dart'; import 'package:pshared/utils/currency.dart';
import 'package:pshared/utils/money.dart';
Money? quoteFeeTotal(PaymentQuote? quote) { Money? quoteFeeTotal(PaymentQuote? quote) {
final preferredCurrency =
quote?.amounts?.sourcePrincipal?.currency ??
quote?.amounts?.sourceDebitTotal?.currency;
return quoteFeeTotalFromLines( return quoteFeeTotalFromLines(
quote?.fees?.lines, quote?.fees?.lines,
preferredCurrency: preferredCurrency, preferredCurrency:
quote?.amounts?.sourcePrincipal?.currency.isoCode ??
quote?.amounts?.sourceDebitTotal?.currency.isoCode,
); );
} }
@@ -21,7 +20,8 @@ Money? quoteSourceDebitTotal(
}) { }) {
final sourceDebitTotal = quote?.amounts?.sourceDebitTotal; final sourceDebitTotal = quote?.amounts?.sourceDebitTotal;
final preferredCurrency = _normalizeCurrency( final preferredCurrency = _normalizeCurrency(
preferredSourceCurrency ?? quote?.amounts?.sourcePrincipal?.currency, preferredSourceCurrency ??
quote?.amounts?.sourcePrincipal?.currency.isoCode,
); );
if (sourceDebitTotal == null) { if (sourceDebitTotal == null) {
@@ -31,10 +31,9 @@ Money? quoteSourceDebitTotal(
); );
} }
final debitCurrency = _normalizeCurrency(sourceDebitTotal.currency);
if (preferredCurrency == null || if (preferredCurrency == null ||
debitCurrency == null || _normalizeCurrency(sourceDebitTotal.currency.isoCode) ==
debitCurrency == preferredCurrency) { preferredCurrency) {
return sourceDebitTotal; return sourceDebitTotal;
} }
@@ -52,22 +51,18 @@ Money? quoteFeeTotalFromLines(
if (lines == null || lines.isEmpty) return null; if (lines == null || lines.isEmpty) return null;
final normalizedPreferred = _normalizeCurrency(preferredCurrency); final normalizedPreferred = _normalizeCurrency(preferredCurrency);
final totalsByCurrency = <String, double>{}; final totalsByCurrency = <String, Money>{};
for (final line in lines) { for (final line in lines) {
final money = line.amount; final parsedAmount = line.amount;
if (money == null) continue; if (parsedAmount == null) continue;
final currency = _normalizeCurrency(money.currency); final currencyCode = parsedAmount.currency.isoCode;
if (currency == null) continue; final signedAmount = _isCreditLine(line.side) ? -parsedAmount : parsedAmount;
final current = totalsByCurrency[currencyCode];
final amount = parseMoneyAmount(money.amount, fallback: double.nan); totalsByCurrency[currencyCode] = current == null
if (amount.isNaN) continue; ? signedAmount
: current + signedAmount;
final sign = _lineSign(line.side);
final signedAmount = sign * amount.abs();
totalsByCurrency[currency] =
(totalsByCurrency[currency] ?? 0) + signedAmount;
} }
if (totalsByCurrency.isEmpty) return null; if (totalsByCurrency.isEmpty) return null;
@@ -77,85 +72,59 @@ Money? quoteFeeTotalFromLines(
totalsByCurrency.containsKey(normalizedPreferred) totalsByCurrency.containsKey(normalizedPreferred)
? normalizedPreferred ? normalizedPreferred
: totalsByCurrency.keys.first; : totalsByCurrency.keys.first;
final total = totalsByCurrency[selectedCurrency]; return totalsByCurrency[selectedCurrency];
if (total == null) return null;
return Money(amount: amountToString(total), currency: selectedCurrency);
} }
List<Money> aggregateMoneyByCurrency(Iterable<Money?> values) { List<Money> aggregateMoneyByCurrency(Iterable<Money?> values) {
final totals = <String, double>{}; final totals = <String, Money>{};
for (final value in values) { for (final value in values) {
if (value == null) continue; if (value == null) continue;
final currency = value.currency.isoCode;
final currency = _normalizeCurrency(value.currency); final current = totals[currency];
if (currency == null) continue; totals[currency] = current == null ? value : current + value;
final amount = parseMoneyAmount(value.amount, fallback: double.nan);
if (amount.isNaN) continue;
totals[currency] = (totals[currency] ?? 0) + amount;
} }
return totals.entries return totals.values.toList();
.map(
(entry) =>
Money(amount: amountToString(entry.value), currency: entry.key),
)
.toList();
} }
Money? _rebuildSourceDebitTotal( Money? _rebuildSourceDebitTotal(
PaymentQuote? quote, { PaymentQuote? quote, {
String? preferredSourceCurrency, String? preferredSourceCurrency,
}) { }) {
final sourcePrincipal = quote?.amounts?.sourcePrincipal; final principal = quote?.amounts?.sourcePrincipal;
if (sourcePrincipal == null) return null; if (principal == null) return null;
final principalCurrency = _normalizeCurrency(sourcePrincipal.currency); final principalCurrency = principal.currency.isoCode;
if (principalCurrency == null) return null;
if (preferredSourceCurrency != null && if (preferredSourceCurrency != null &&
principalCurrency != preferredSourceCurrency) { principalCurrency != preferredSourceCurrency) {
return null; return null;
} }
final principalAmount = parseMoneyAmount( var totalAmount = principal;
sourcePrincipal.amount,
fallback: double.nan,
);
if (principalAmount.isNaN) return null;
double totalAmount = principalAmount;
final fee = quoteFeeTotalFromLines( final fee = quoteFeeTotalFromLines(
quote?.fees?.lines, quote?.fees?.lines,
preferredCurrency: principalCurrency, preferredCurrency: principalCurrency,
); );
if (fee != null && _normalizeCurrency(fee.currency) == principalCurrency) { if (fee != null && fee.currency.isoCode == principalCurrency) {
final feeAmount = parseMoneyAmount(fee.amount, fallback: double.nan); totalAmount += fee;
if (!feeAmount.isNaN) {
totalAmount += feeAmount;
}
} }
return Money( return totalAmount;
amount: amountToString(totalAmount),
currency: principalCurrency,
);
} }
double _lineSign(String? side) { bool _isCreditLine(String? side) {
final normalized = side?.trim().toLowerCase() ?? ''; final normalized = side?.trim().toLowerCase() ?? '';
switch (normalized) { switch (normalized) {
case 'entry_side_credit': case 'entry_side_credit':
case 'credit': case 'credit':
return -1; return true;
default: default:
return 1; return false;
} }
} }
String? _normalizeCurrency(String? currency) { String? _normalizeCurrency(String? currency) {
final normalized = currency?.trim().toUpperCase(); final normalized = currency?.trim();
if (normalized == null || normalized.isEmpty) return null; if (normalized == null || normalized.isEmpty) return null;
return normalized; return money2CurrencyFromCode(normalized)?.isoCode ?? normalized.toUpperCase();
} }

View File

@@ -28,6 +28,7 @@ dependencies:
uuid: ^4.5.1 uuid: ^4.5.1
image: ^4.5.4 image: ^4.5.4
shared_preferences: ^2.5.3 shared_preferences: ^2.5.3
money2: ^6.3.0
dev_dependencies: dev_dependencies:
flutter_lints: ^6.0.0 flutter_lints: ^6.0.0

View File

@@ -348,7 +348,7 @@ RouteBase payoutShellRoute() => ShellRoute(
path: PayoutRoutes.reportPaymentPath, path: PayoutRoutes.reportPaymentPath,
pageBuilder: (_, state) => NoTransitionPage( pageBuilder: (_, state) => NoTransitionPage(
child: PaymentDetailsPage( child: PaymentDetailsPage(
paymentRef: paymentId:
state.uri.queryParameters[PayoutRoutes.reportPaymentIdQuery] ?? state.uri.queryParameters[PayoutRoutes.reportPaymentIdQuery] ??
'', '',
), ),

View File

@@ -1,102 +0,0 @@
import 'package:flutter/material.dart';
import 'package:pshared/models/payment/status.dart';
import 'package:pweb/models/wallet/wallet_transaction.dart';
import 'package:pweb/providers/wallet_transactions.dart';
class WalletTransactionsController extends ChangeNotifier {
List<WalletTransaction> _filteredTransactions = [];
DateTimeRange? _dateRange;
final Set<OperationStatus> _selectedStatuses = {};
final Set<WalletTransactionType> _selectedTypes = {};
WalletTransactionsProvider? _provider;
List<WalletTransaction> get transactions =>
_provider?.transactions ?? const [];
List<WalletTransaction> get filteredTransactions => _filteredTransactions;
DateTimeRange? get dateRange => _dateRange;
Set<OperationStatus> get selectedStatuses => _selectedStatuses;
Set<WalletTransactionType> get selectedTypes => _selectedTypes;
bool get isLoading => _provider?.isLoading ?? false;
String? get error => _provider?.error;
bool get hasFilters =>
_dateRange != null ||
_selectedStatuses.isNotEmpty ||
_selectedTypes.isNotEmpty;
void update(WalletTransactionsProvider provider) {
if (identical(_provider, provider)) return;
_provider?.removeListener(_onProviderChanged);
_provider = provider;
_provider?.addListener(_onProviderChanged);
_rebuildFiltered(notify: false);
notifyListeners();
}
void setDateRange(DateTimeRange? range) {
_dateRange = range;
_rebuildFiltered();
}
void toggleStatus(OperationStatus status) {
if (_selectedStatuses.contains(status)) {
_selectedStatuses.remove(status);
} else {
_selectedStatuses.add(status);
}
_rebuildFiltered();
}
void toggleType(WalletTransactionType type) {
if (_selectedTypes.contains(type)) {
_selectedTypes.remove(type);
} else {
_selectedTypes.add(type);
}
_rebuildFiltered();
}
void resetFilters() {
_dateRange = null;
_selectedStatuses.clear();
_selectedTypes.clear();
_rebuildFiltered();
}
void _onProviderChanged() {
_rebuildFiltered();
}
void _rebuildFiltered({bool notify = true}) {
final source = _provider?.transactions ?? const <WalletTransaction>[];
final activeWalletId = _provider?.walletId;
_filteredTransactions = source.where((tx) {
final walletMatch =
activeWalletId == null || tx.walletId == activeWalletId;
final statusMatch =
_selectedStatuses.isEmpty || _selectedStatuses.contains(tx.status);
final typeMatch =
_selectedTypes.isEmpty || _selectedTypes.contains(tx.type);
final dateMatch =
_dateRange == null ||
(tx.date.isAfter(
_dateRange!.start.subtract(const Duration(seconds: 1)),
) &&
tx.date.isBefore(
_dateRange!.end.add(const Duration(seconds: 1)),
));
return walletMatch && statusMatch && typeMatch && dateMatch;
}).toList();
if (notify) notifyListeners();
}
@override
void dispose() {
_provider?.removeListener(_onProviderChanged);
super.dispose();
}
}

View File

@@ -1,10 +1,11 @@
import 'package:money2/money2.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:pshared/controllers/payment/source.dart'; import 'package:pshared/controllers/payment/source.dart';
import 'package:pshared/models/payment/settlement_mode.dart'; import 'package:pshared/models/payment/settlement_mode.dart';
import 'package:pshared/provider/payment/amount.dart'; import 'package:pshared/provider/payment/amount.dart';
import 'package:pshared/utils/currency.dart'; import 'package:pshared/utils/currency.dart';
import 'package:pshared/utils/money.dart';
import 'package:pweb/models/payment/amount/mode.dart'; import 'package:pweb/models/payment/amount/mode.dart';
@@ -22,7 +23,7 @@ class PaymentAmountFieldController extends ChangeNotifier {
PaymentAmountFieldController({required double? initialAmount}) PaymentAmountFieldController({required double? initialAmount})
: textController = TextEditingController( : textController = TextEditingController(
text: initialAmount == null ? '' : amountToString(initialAmount), text: initialAmount == null ? '' : initialAmount.toString(),
); );
PaymentAmountMode get mode => _mode; PaymentAmountMode get mode => _mode;
@@ -122,18 +123,14 @@ class PaymentAmountFieldController extends ChangeNotifier {
}; };
double? _parseAmount(String value) { double? _parseAmount(String value) {
final parsed = parseMoneyAmount( return double.tryParse(value.replaceAll(',', '.').trim());
value.replaceAll(',', '.'),
fallback: double.nan,
);
return parsed.isNaN ? null : parsed;
} }
void _syncTextWithAmount(double? amount) { void _syncTextWithAmount(double? amount) {
final parsedText = _parseAmount(textController.text); final parsedText = _parseAmount(textController.text);
if (parsedText == amount) return; if (parsedText == amount) return;
final nextText = amount == null ? '' : amountToString(amount); final nextText = amount == null ? '' : _formatAmount(amount);
_isSyncingText = true; _isSyncingText = true;
textController.value = TextEditingValue( textController.value = TextEditingValue(
text: nextText, text: nextText,
@@ -142,6 +139,12 @@ class PaymentAmountFieldController extends ChangeNotifier {
_isSyncingText = false; _isSyncingText = false;
} }
String _formatAmount(double amount) {
final currency = money2CurrencyFromCode(activeCurrencyCode);
if (currency == null) return amount.toString();
return Money.fromNumWithCurrency(amount, currency).toDecimal().toString();
}
@override @override
void dispose() { void dispose() {
_provider?.removeListener(_handleProviderChanged); _provider?.removeListener(_handleProviderChanged);

View File

@@ -9,14 +9,14 @@ import 'package:pweb/utils/report/operations/document_rule.dart';
class PaymentDetailsController extends ChangeNotifier { class PaymentDetailsController extends ChangeNotifier {
PaymentDetailsController({required String paymentRef}) PaymentDetailsController({required String paymentId})
: _paymentRef = paymentRef; : _paymentId = paymentId;
PaymentsProvider? _payments; PaymentsProvider? _payments;
String _paymentRef; String _paymentId;
Payment? _payment; Payment? _payment;
String get paymentId => _paymentRef; String get paymentId => _paymentId;
Payment? get payment => _payment; Payment? get payment => _payment;
bool get isLoading => _payments?.isLoading ?? false; bool get isLoading => _payments?.isLoading ?? false;
Exception? get error => _payments?.error; Exception? get error => _payments?.error;
@@ -44,8 +44,8 @@ class PaymentDetailsController extends ChangeNotifier {
operationDocumentRequest(operation) != null; operationDocumentRequest(operation) != null;
void update(PaymentsProvider provider, String paymentId) { void update(PaymentsProvider provider, String paymentId) {
if (_paymentRef != paymentId) { if (_paymentId != paymentId) {
_paymentRef = paymentId; _paymentId = paymentId;
} }
if (!identical(_payments, provider)) { if (!identical(_payments, provider)) {
@@ -60,7 +60,7 @@ class PaymentDetailsController extends ChangeNotifier {
} }
void _rebuild() { void _rebuild() {
_payment = _findPayment(_payments?.payments ?? const [], _paymentRef); _payment = _findPayment(_payments?.payments ?? const [], _paymentId);
notifyListeners(); notifyListeners();
} }

View File

@@ -29,5 +29,4 @@ class RecentPaymentsController extends ChangeNotifier {
_recent = sortOperations(operations).take(5).toList(); _recent = sortOperations(operations).take(5).toList();
notifyListeners(); notifyListeners();
} }
} }

View File

@@ -2,8 +2,9 @@ import 'dart:async';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:money2/money2.dart';
import 'package:pshared/controllers/payment/source.dart'; import 'package:pshared/controllers/payment/source.dart';
import 'package:pshared/models/money.dart';
import 'package:pshared/models/payment/asset.dart'; import 'package:pshared/models/payment/asset.dart';
import 'package:pshared/models/payment/chain_network.dart'; import 'package:pshared/models/payment/chain_network.dart';
import 'package:pshared/models/payment/methods/data.dart'; import 'package:pshared/models/payment/methods/data.dart';
@@ -17,6 +18,7 @@ import 'package:pweb/models/payment/multiple_payouts/state.dart';
import 'package:pweb/providers/multiple_payouts.dart'; import 'package:pweb/providers/multiple_payouts.dart';
import 'package:pweb/services/payments/csv_input.dart'; import 'package:pweb/services/payments/csv_input.dart';
class MultiplePayoutsController extends ChangeNotifier { class MultiplePayoutsController extends ChangeNotifier {
final CsvInputService _csvInput; final CsvInputService _csvInput;
MultiplePayoutsProvider? _provider; MultiplePayoutsProvider? _provider;

View File

@@ -31,10 +31,7 @@ import 'package:pweb/app/app.dart';
import 'package:pweb/pages/invitations/widgets/list/view_model.dart'; import 'package:pweb/pages/invitations/widgets/list/view_model.dart';
import 'package:pweb/app/timeago.dart'; import 'package:pweb/app/timeago.dart';
import 'package:pweb/providers/two_factor.dart'; import 'package:pweb/providers/two_factor.dart';
import 'package:pweb/controllers/operations/wallet_transactions.dart';
import 'package:pweb/providers/wallet_transactions.dart';
import 'package:pweb/services/posthog.dart'; import 'package:pweb/services/posthog.dart';
import 'package:pweb/services/wallet_transactions.dart';
import 'package:pweb/providers/account.dart'; import 'package:pweb/providers/account.dart';
import 'package:pweb/providers/locale.dart'; import 'package:pweb/providers/locale.dart';
@@ -142,18 +139,6 @@ void main() async {
create: (_) => WalletsController(), create: (_) => WalletsController(),
update: (_, wallets, controller) => controller!..update(wallets), update: (_, wallets, controller) => controller!..update(wallets),
), ),
ChangeNotifierProvider(
create: (_) => WalletTransactionsProvider(
MockWalletTransactionsService(),
),
),
ChangeNotifierProxyProvider<
WalletTransactionsProvider,
WalletTransactionsController
>(
create: (_) => WalletTransactionsController(),
update: (_, provider, controller) => controller!..update(provider),
),
], ],
child: const PayApp(), child: const PayApp(),
), ),

View File

@@ -29,7 +29,7 @@ class WalletTransaction {
final WalletTransactionType type; final WalletTransactionType type;
final OperationStatus status; final OperationStatus status;
final double amount; final double amount;
final Currency currency; final CurrencyCode currency;
final DateTime date; final DateTime date;
final String description; final String description;
final String? counterparty; final String? counterparty;
@@ -56,7 +56,7 @@ class WalletTransaction {
WalletTransactionType? type, WalletTransactionType? type,
OperationStatus? status, OperationStatus? status,
double? amount, double? amount,
Currency? currency, CurrencyCode? currency,
DateTime? date, DateTime? date,
String? description, String? description,
String? counterparty, String? counterparty,

View File

@@ -2,6 +2,6 @@ import 'package:pshared/models/currency.dart';
import 'package:pshared/models/payment/chain_network.dart'; import 'package:pshared/models/payment/chain_network.dart';
const Currency managedCurrencyDefault = Currency.usdt; const CurrencyCode managedCurrencyDefault = CurrencyCode.usdt;
const Currency ledgerCurrencyDefault = Currency.rub; const CurrencyCode ledgerCurrencyDefault = CurrencyCode.rub;
const ChainNetwork managedNetworkDefault = ChainNetwork.tronMainnet; const ChainNetwork managedNetworkDefault = ChainNetwork.tronMainnet;

View File

@@ -8,11 +8,9 @@ import 'package:pshared/models/currency.dart';
import 'package:pshared/models/describable.dart'; import 'package:pshared/models/describable.dart';
import 'package:pshared/models/payment/chain_network.dart'; import 'package:pshared/models/payment/chain_network.dart';
import 'package:pshared/models/payment/type.dart'; import 'package:pshared/models/payment/type.dart';
import 'package:pshared/models/wallet/chain_asset.dart';
import 'package:pshared/provider/accounts/employees.dart'; import 'package:pshared/provider/accounts/employees.dart';
import 'package:pshared/provider/ledger.dart'; import 'package:pshared/provider/ledger.dart';
import 'package:pshared/provider/payment/wallets.dart'; import 'package:pshared/provider/payment/wallets.dart';
import 'package:pshared/utils/currency.dart';
import 'package:pweb/pages/dashboard/buttons/balance/add/form.dart'; import 'package:pweb/pages/dashboard/buttons/balance/add/form.dart';
import 'package:pweb/pages/dashboard/buttons/balance/add/constants.dart'; import 'package:pweb/pages/dashboard/buttons/balance/add/constants.dart';
@@ -42,9 +40,9 @@ class _AddBalanceDialogState extends State<AddBalanceDialog> {
PaymentType _assetType = PaymentType.managedWallet; PaymentType _assetType = PaymentType.managedWallet;
String? _ownerRef; String? _ownerRef;
Currency _managedCurrency = managedCurrencyDefault; CurrencyCode _managedCurrency = managedCurrencyDefault;
ChainNetwork _network = managedNetworkDefault; ChainNetwork _network = managedNetworkDefault;
Currency _ledgerCurrency = ledgerCurrencyDefault; CurrencyCode _ledgerCurrency = ledgerCurrencyDefault;
@override @override
void dispose() { void dispose() {
@@ -60,7 +58,7 @@ class _AddBalanceDialogState extends State<AddBalanceDialog> {
void _setOwnerRef(String? value) => setState(() => _ownerRef = value); void _setOwnerRef(String? value) => setState(() => _ownerRef = value);
void _setManagedCurrency(Currency? value) { void _setManagedCurrency(CurrencyCode? value) {
if (value == null) return; if (value == null) return;
setState(() => _managedCurrency = value); setState(() => _managedCurrency = value);
} }
@@ -70,7 +68,7 @@ class _AddBalanceDialogState extends State<AddBalanceDialog> {
setState(() => _network = value); setState(() => _network = value);
} }
void _setLedgerCurrency(Currency? value) { void _setLedgerCurrency(CurrencyCode? value) {
if (value == null) return; if (value == null) return;
setState(() => _ledgerCurrency = value); setState(() => _ledgerCurrency = value);
} }
@@ -102,7 +100,8 @@ class _AddBalanceDialogState extends State<AddBalanceDialog> {
if (_assetType == PaymentType.managedWallet) { if (_assetType == PaymentType.managedWallet) {
await context.read<WalletsProvider>().create( await context.read<WalletsProvider>().create(
describable: newDescribable(name: name, description: description), describable: newDescribable(name: name, description: description),
asset: ChainAsset(chain: _network, tokenSymbol: currencyCodeToString(_managedCurrency)), chain: _network,
currency: _managedCurrency,
ownerRef: owner?.id, ownerRef: owner?.id,
); );
} else { } else {

View File

@@ -23,12 +23,12 @@ class AddBalanceForm extends StatelessWidget {
final ValueChanged<String?> onOwnerChanged; final ValueChanged<String?> onOwnerChanged;
final TextEditingController nameController; final TextEditingController nameController;
final TextEditingController descriptionController; final TextEditingController descriptionController;
final Currency managedCurrency; final CurrencyCode managedCurrency;
final ChainNetwork network; final ChainNetwork network;
final Currency ledgerCurrency; final CurrencyCode ledgerCurrency;
final ValueChanged<Currency?> onManagedCurrencyChanged; final ValueChanged<CurrencyCode?> onManagedCurrencyChanged;
final ValueChanged<ChainNetwork?> onNetworkChanged; final ValueChanged<ChainNetwork?> onNetworkChanged;
final ValueChanged<Currency?> onLedgerCurrencyChanged; final ValueChanged<CurrencyCode?> onLedgerCurrencyChanged;
final bool showEmployeesLoading; final bool showEmployeesLoading;
const AddBalanceForm({ const AddBalanceForm({

View File

@@ -4,7 +4,7 @@ import 'package:pshared/models/currency.dart';
import 'package:pshared/utils/currency.dart'; import 'package:pshared/utils/currency.dart';
DropdownMenuItem<Currency> currencyItem(Currency currency) => DropdownMenuItem( DropdownMenuItem<CurrencyCode> currencyItem(CurrencyCode currency) => DropdownMenuItem(
value: currency, value: currency,
child: Text(currencyCodeToString(currency)), child: Text(currencyCodeToString(currency)),
); );

View File

@@ -10,8 +10,8 @@ import 'package:pweb/generated/i18n/app_localizations.dart';
class LedgerFields extends StatelessWidget { class LedgerFields extends StatelessWidget {
final Currency currency; final CurrencyCode currency;
final ValueChanged<Currency?>? onCurrencyChanged; final ValueChanged<CurrencyCode?>? onCurrencyChanged;
const LedgerFields({ const LedgerFields({
super.key, super.key,
@@ -20,7 +20,7 @@ class LedgerFields extends StatelessWidget {
}); });
@override @override
Widget build(BuildContext context) => DropdownButtonFormField<Currency>( Widget build(BuildContext context) => DropdownButtonFormField<CurrencyCode>(
initialValue: currency, initialValue: currency,
decoration: getInputDecoration(context, AppLocalizations.of(context)!.currency, true), decoration: getInputDecoration(context, AppLocalizations.of(context)!.currency, true),
items: [ items: [

View File

@@ -12,9 +12,9 @@ import 'package:pweb/generated/i18n/app_localizations.dart';
class ManagedWalletFields extends StatelessWidget { class ManagedWalletFields extends StatelessWidget {
final Currency currency; final CurrencyCode currency;
final ChainNetwork network; final ChainNetwork network;
final ValueChanged<Currency?>? onCurrencyChanged; final ValueChanged<CurrencyCode?>? onCurrencyChanged;
final ValueChanged<ChainNetwork?>? onNetworkChanged; final ValueChanged<ChainNetwork?>? onNetworkChanged;
const ManagedWalletFields({ const ManagedWalletFields({
@@ -31,7 +31,7 @@ class ManagedWalletFields extends StatelessWidget {
return Column( return Column(
spacing: 12, spacing: 12,
children: [ children: [
DropdownButtonFormField<Currency>( DropdownButtonFormField<CurrencyCode>(
initialValue: currency, initialValue: currency,
decoration: getInputDecoration(context, l10n.currency, true), decoration: getInputDecoration(context, l10n.currency, true),
items: [ items: [

View File

@@ -3,7 +3,6 @@ import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:pshared/controllers/balance_mask/wallets.dart'; import 'package:pshared/controllers/balance_mask/wallets.dart';
import 'package:pshared/models/money.dart';
import 'package:pshared/models/payment/wallet.dart'; import 'package:pshared/models/payment/wallet.dart';
import 'package:pshared/utils/currency.dart'; import 'package:pshared/utils/currency.dart';
@@ -28,12 +27,10 @@ class BalanceAmount extends StatelessWidget {
final textTheme = Theme.of(context).textTheme; final textTheme = Theme.of(context).textTheme;
final colorScheme = Theme.of(context).colorScheme; final colorScheme = Theme.of(context).colorScheme;
final currencyBalance = currencyCodeToSymbol(wallet.currency); final currencyBalance = currencyCodeToSymbol(wallet.currency);
final formattedBalance = formatMoneyUi( final formattedBalance = formatAmountUi(
context, context,
Money( amount: wallet.balance,
amount: amountToString(wallet.balance), currency: currencyCodeToString(wallet.currency),
currency: currencyCodeToString(wallet.currency),
),
); );
final wallets = context.watch<WalletsController>(); final wallets = context.watch<WalletsController>();
final isMasked = wallets.isBalanceMasked(wallet.id); final isMasked = wallets.isBalanceMasked(wallet.id);

View File

@@ -36,9 +36,7 @@ class PaymentAmountField extends StatelessWidget {
decoration: InputDecoration( decoration: InputDecoration(
labelText: loc.amount, labelText: loc.amount,
border: const OutlineInputBorder(), border: const OutlineInputBorder(),
prefixText: symbol == null prefixText: symbol == null ? null : '$symbol ',
? null
: withTrailingNonBreakingSpace(symbol),
), ),
onChanged: ui.handleChanged, onChanged: ui.handleChanged,
), ),

View File

@@ -1,7 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:pshared/utils/currency.dart';
import 'package:pweb/controllers/payouts/multiple_payouts.dart'; import 'package:pweb/controllers/payouts/multiple_payouts.dart';
import 'package:pweb/models/dashboard/summary_values.dart'; import 'package:pweb/models/dashboard/summary_values.dart';
import 'package:pweb/pages/dashboard/payouts/summary/widget.dart'; import 'package:pweb/pages/dashboard/payouts/summary/widget.dart';
@@ -28,21 +26,12 @@ class SourceQuoteSummary extends StatelessWidget {
values: PaymentSummaryValues( values: PaymentSummaryValues(
fee: controller.aggregateFeeAmount == null fee: controller.aggregateFeeAmount == null
? l10n.noFee ? l10n.noFee
: formatMoneyUiWithL10n( : formatMoneyUi(context, controller.aggregateFeeAmount),
l10n, recipientReceives: formatMoneyUi(
controller.aggregateFeeAmount, context,
separator: nonBreakingSpace,
),
recipientReceives: formatMoneyUiWithL10n(
l10n,
controller.aggregateSettlementAmount, controller.aggregateSettlementAmount,
separator: nonBreakingSpace,
),
total: formatMoneyUiWithL10n(
l10n,
controller.aggregateDebitAmount,
separator: nonBreakingSpace,
), ),
total: formatMoneyUi(context, controller.aggregateDebitAmount),
), ),
); );
} }

View File

@@ -19,7 +19,10 @@ class UploadHistorySection extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return ChangeNotifierProxyProvider<PaymentsProvider, RecentPaymentsController>( return ChangeNotifierProxyProvider<
PaymentsProvider,
RecentPaymentsController
>(
create: (_) => RecentPaymentsController(), create: (_) => RecentPaymentsController(),
update: (_, payments, controller) => controller!..update(payments), update: (_, payments, controller) => controller!..update(payments),
child: const _RecentPaymentsView(), child: const _RecentPaymentsView(),

View File

@@ -8,7 +8,6 @@ import 'package:pweb/pages/dashboard/payouts/summary/row.dart';
import 'package:pweb/generated/i18n/app_localizations.dart'; import 'package:pweb/generated/i18n/app_localizations.dart';
class PaymentFeeRow extends StatelessWidget { class PaymentFeeRow extends StatelessWidget {
const PaymentFeeRow({super.key}); const PaymentFeeRow({super.key});
@@ -19,7 +18,7 @@ class PaymentFeeRow extends StatelessWidget {
final l10 = AppLocalizations.of(context)!; final l10 = AppLocalizations.of(context)!;
return PaymentSummaryRow( return PaymentSummaryRow(
labelFactory: l10.fee, labelFactory: l10.fee,
asset: fee, money: fee,
value: fee == null ? l10.noFee : null, value: fee == null ? l10.noFee : null,
style: Theme.of(context).textTheme.titleMedium, style: Theme.of(context).textTheme.titleMedium,
); );

View File

@@ -8,7 +8,6 @@ import 'package:pweb/pages/dashboard/payouts/summary/row.dart';
import 'package:pweb/generated/i18n/app_localizations.dart'; import 'package:pweb/generated/i18n/app_localizations.dart';
class PaymentRecipientReceivesRow extends StatelessWidget { class PaymentRecipientReceivesRow extends StatelessWidget {
const PaymentRecipientReceivesRow({super.key}); const PaymentRecipientReceivesRow({super.key});
@@ -16,7 +15,7 @@ class PaymentRecipientReceivesRow extends StatelessWidget {
Widget build(BuildContext context) => Consumer<QuotationProvider>( Widget build(BuildContext context) => Consumer<QuotationProvider>(
builder: (context, provider, _) => PaymentSummaryRow( builder: (context, provider, _) => PaymentSummaryRow(
labelFactory: AppLocalizations.of(context)!.recipientWillReceive, labelFactory: AppLocalizations.of(context)!.recipientWillReceive,
asset: provider.recipientGets, money: provider.recipientGets,
style: Theme.of(context).textTheme.titleMedium, style: Theme.of(context).textTheme.titleMedium,
), ),
); );

View File

@@ -1,27 +1,25 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:money2/money2.dart';
import 'package:pshared/models/asset.dart';
import 'package:pweb/utils/money_display.dart'; import 'package:pweb/utils/money_display.dart';
class PaymentSummaryRow extends StatelessWidget { class PaymentSummaryRow extends StatelessWidget {
final String Function(String) labelFactory; final String Function(String) labelFactory;
final Asset? asset; final Money? money;
final String? value; final String? value;
final TextStyle? style; final TextStyle? style;
const PaymentSummaryRow({ const PaymentSummaryRow({
super.key, super.key,
required this.labelFactory, required this.labelFactory,
required this.asset, required this.money,
this.value, this.value,
this.style, this.style,
}); });
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final formatted = value ?? formatAssetUi(context, asset); final formatted = value ?? formatMoneyUi(context, money);
return Text(labelFactory(formatted), style: style); return Text(labelFactory(formatted), style: style);
} }
} }

View File

@@ -8,7 +8,6 @@ import 'package:pweb/pages/dashboard/payouts/summary/row.dart';
import 'package:pweb/generated/i18n/app_localizations.dart'; import 'package:pweb/generated/i18n/app_localizations.dart';
class PaymentTotalRow extends StatelessWidget { class PaymentTotalRow extends StatelessWidget {
const PaymentTotalRow({super.key}); const PaymentTotalRow({super.key});
@@ -16,8 +15,10 @@ class PaymentTotalRow extends StatelessWidget {
Widget build(BuildContext context) => Consumer<QuotationProvider>( Widget build(BuildContext context) => Consumer<QuotationProvider>(
builder: (context, provider, _) => PaymentSummaryRow( builder: (context, provider, _) => PaymentSummaryRow(
labelFactory: AppLocalizations.of(context)!.total, labelFactory: AppLocalizations.of(context)!.total,
asset: provider.total, money: provider.total,
style: Theme.of(context).textTheme.titleMedium!.copyWith(fontWeight: FontWeight.w600), style: Theme.of(
context,
).textTheme.titleMedium!.copyWith(fontWeight: FontWeight.w600),
), ),
); );
} }

Some files were not shown because too many files have changed in this diff Show More