Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0091191d97 |
@@ -8,7 +8,7 @@ require (
|
||||
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/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/prometheus/client_golang v1.23.2
|
||||
github.com/shopspring/decimal v1.4.0
|
||||
@@ -61,7 +61,7 @@ require (
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
go.yaml.in/yaml/v2 v2.4.4 // 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/sys v0.42.0 // indirect
|
||||
golang.org/x/text v0.35.0 // indirect
|
||||
|
||||
@@ -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/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/s3 v1.97.0 h1:zyKY4OxzUImu+DigelJI9o49QQv8CjREs5E1CywjtIA=
|
||||
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 h1:4ExZyubQ6LQQVuF2Qp9OsfEvsTdAWh5Gfwf6PgIdLdk=
|
||||
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/go.mod h1:idzZ7gmDeqeNrSPkdbtMp9qWMgcBwykA7P7Rzh5DXVU=
|
||||
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-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.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0=
|
||||
golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw=
|
||||
golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
|
||||
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-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
|
||||
|
||||
@@ -3,8 +3,7 @@ package content
|
||||
// Issuer details are intentionally centralized to avoid document text drift.
|
||||
const (
|
||||
IssuerLegalName = "SMX Operations Limited"
|
||||
IssuerLegalAddress = "Room 607, 12/F., Block C, Hong Kong Industrial Centre, 489-491 Castle Peak Road, Lai Chi Kok, HongKong"
|
||||
IssuerEmail = "contact@sendico.io"
|
||||
IssuerLegalAddress = "Room 607, 12/F., Block C, Hong Kong Industrial Centre, 489-491 Castle Peak Road, Lai Chi Kok, Hong Kong"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -77,74 +76,40 @@ var AcceptanceTemplate = AcceptanceTemplateContent{
|
||||
type OperationDocumentContent struct {
|
||||
Title string
|
||||
Subtitle string
|
||||
MetaCertificateNumberLabel string
|
||||
MetaDateLabel string
|
||||
SectionParties string
|
||||
PartiesIntro string
|
||||
RowServiceProvider string
|
||||
RowServiceProviderAddress string
|
||||
RowServiceProviderEmail string
|
||||
RowClient string
|
||||
RowClientAddress string
|
||||
RowClientReference string
|
||||
SectionSubject string
|
||||
SubjectIntro string
|
||||
SectionServicePeriod string
|
||||
RowPeriodFrom string
|
||||
RowPeriodTo string
|
||||
SectionTotalAmount 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
|
||||
MetaDocumentType string
|
||||
SectionOperation string
|
||||
SectionFailure string
|
||||
RowOrganization string
|
||||
RowGatewayService string
|
||||
RowOperationRef string
|
||||
RowPaymentRef string
|
||||
RowCode string
|
||||
RowState string
|
||||
RowLabel string
|
||||
RowStartedAtUTC string
|
||||
RowCompletedAtUTC string
|
||||
RowAmount string
|
||||
RowFailureCode string
|
||||
RowFailureReason string
|
||||
MissingValuePlaceholder string
|
||||
}
|
||||
|
||||
var OperationDocument = OperationDocumentContent{
|
||||
Title: "CERTIFICATE OF SERVICES RENDERED",
|
||||
Subtitle: "Payment operation completion and acceptance statement",
|
||||
MetaCertificateNumberLabel: "Certificate No.",
|
||||
MetaDateLabel: "Date",
|
||||
SectionParties: "PARTIES",
|
||||
PartiesIntro: "This Certificate is made between:",
|
||||
RowServiceProvider: "Service Provider",
|
||||
RowServiceProviderAddress: "Service Provider Address",
|
||||
RowServiceProviderEmail: "Service Provider Email",
|
||||
RowClient: "Client",
|
||||
RowClientAddress: "Client Address",
|
||||
RowClientReference: "Client Reference",
|
||||
SectionSubject: "SUBJECT OF THE CERTIFICATE",
|
||||
SubjectIntro: "The Service Provider confirms that the following services have been fully rendered to the Client:",
|
||||
SectionServicePeriod: "SERVICE PERIOD",
|
||||
RowPeriodFrom: "From",
|
||||
RowPeriodTo: "To",
|
||||
SectionTotalAmount: "TOTAL AMOUNT",
|
||||
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",
|
||||
Title: "OPERATION BILLING DOCUMENT",
|
||||
Subtitle: "Gateway operation statement",
|
||||
MetaDocumentType: "Document Type: Operation",
|
||||
SectionOperation: "OPERATION DETAILS",
|
||||
SectionFailure: "FAILURE DETAILS",
|
||||
RowOrganization: "Organization",
|
||||
RowGatewayService: "Gateway Service",
|
||||
RowOperationRef: "Operation Ref",
|
||||
RowPaymentRef: "Payment Ref",
|
||||
RowCode: "Code",
|
||||
RowState: "State",
|
||||
RowLabel: "Label",
|
||||
RowStartedAtUTC: "Started At (UTC)",
|
||||
RowCompletedAtUTC: "Completed At (UTC)",
|
||||
RowAmount: "Amount",
|
||||
RowFailureCode: "Failure Code",
|
||||
RowFailureReason: "Failure Reason",
|
||||
MissingValuePlaceholder: "n/a",
|
||||
|
||||
@@ -289,8 +289,6 @@ type operationSnapshot struct {
|
||||
GatewayService string
|
||||
OperationRef string
|
||||
PaymentRef string
|
||||
ClientName string
|
||||
ClientAddress string
|
||||
OperationCode string
|
||||
OperationLabel string
|
||||
OperationState string
|
||||
@@ -308,8 +306,6 @@ func operationSnapshotFromRequest(req *documentsv1.GetOperationDocumentRequest)
|
||||
GatewayService: strings.TrimSpace(req.GetGatewayService()),
|
||||
OperationRef: strings.TrimSpace(req.GetOperationRef()),
|
||||
PaymentRef: strings.TrimSpace(req.GetPaymentRef()),
|
||||
ClientName: strings.TrimSpace(req.GetClientName()),
|
||||
ClientAddress: strings.TrimSpace(req.GetClientAddress()),
|
||||
OperationCode: strings.TrimSpace(req.GetOperationCode()),
|
||||
OperationLabel: strings.TrimSpace(req.GetOperationLabel()),
|
||||
OperationState: strings.TrimSpace(req.GetOperationState()),
|
||||
@@ -332,6 +328,21 @@ func operationSnapshotFromRequest(req *documentsv1.GetOperationDocumentRequest)
|
||||
func buildOperationBlocks(snapshot operationSnapshot) []renderer.Block {
|
||||
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{
|
||||
{
|
||||
Tag: renderer.TagTitle,
|
||||
@@ -344,115 +355,30 @@ func buildOperationBlocks(snapshot operationSnapshot) []renderer.Block {
|
||||
{
|
||||
Tag: renderer.TagMeta,
|
||||
Lines: []string{
|
||||
fmt.Sprintf("%s: %s", documentCopy.MetaCertificateNumberLabel, certificateNumber(snapshot)),
|
||||
fmt.Sprintf("%s: %s", documentCopy.MetaDateLabel, formatCertificateDate(certificateDate(snapshot))),
|
||||
documentCopy.MetaDocumentType,
|
||||
},
|
||||
},
|
||||
{
|
||||
Tag: renderer.TagSection,
|
||||
Lines: []string{documentCopy.SectionParties},
|
||||
},
|
||||
{
|
||||
Tag: renderer.TagText,
|
||||
Lines: []string{documentCopy.PartiesIntro},
|
||||
Lines: []string{documentCopy.SectionOperation},
|
||||
},
|
||||
{
|
||||
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)},
|
||||
},
|
||||
Rows: rows,
|
||||
},
|
||||
}
|
||||
|
||||
if snapshot.FailureCode != "" || snapshot.FailureReason != "" {
|
||||
blocks = append(blocks, renderer.Block{
|
||||
blocks = append(blocks,
|
||||
renderer.Block{Tag: renderer.TagSection, Lines: []string{documentCopy.SectionFailure}},
|
||||
renderer.Block{
|
||||
Tag: renderer.TagKV,
|
||||
Rows: [][]string{
|
||||
{documentCopy.RowFailureCode, safeValue(snapshot.FailureCode)},
|
||||
{documentCopy.RowFailureReason, safeValue(snapshot.FailureReason)},
|
||||
},
|
||||
})
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
return blocks
|
||||
@@ -466,80 +392,6 @@ func formatSnapshotTime(value time.Time) string {
|
||||
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 {
|
||||
trimmed := strings.TrimSpace(value)
|
||||
if trimmed == "" {
|
||||
|
||||
@@ -3,7 +3,6 @@ package documents
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -192,92 +191,3 @@ func TestGetOperationDocument_RequiresOperationRef(t *testing.T) {
|
||||
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
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ require (
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
go.yaml.in/yaml/v2 v2.4.4 // 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/sys v0.42.0 // indirect
|
||||
golang.org/x/text v0.35.0 // indirect
|
||||
|
||||
@@ -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-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.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0=
|
||||
golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw=
|
||||
golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
|
||||
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-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
|
||||
|
||||
@@ -39,7 +39,7 @@ require (
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
go.yaml.in/yaml/v2 v2.4.4 // 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/sys v0.42.0 // indirect
|
||||
golang.org/x/text v0.35.0 // indirect
|
||||
|
||||
@@ -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-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.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0=
|
||||
golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw=
|
||||
golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
|
||||
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-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
|
||||
|
||||
@@ -18,7 +18,7 @@ require (
|
||||
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/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/cors v1.2.2
|
||||
github.com/go-chi/jwtauth/v5 v5.4.0
|
||||
@@ -37,7 +37,7 @@ require (
|
||||
github.com/testcontainers/testcontainers-go/modules/mongodb v0.33.0
|
||||
go.mongodb.org/mongo-driver/v2 v2.5.0
|
||||
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/protobuf v1.36.11
|
||||
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/stdr v1.2.2 // 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/hashicorp/errwrap v1.1.0 // indirect
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
||||
|
||||
@@ -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/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/s3 v1.97.0 h1:zyKY4OxzUImu+DigelJI9o49QQv8CjREs5E1CywjtIA=
|
||||
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 h1:4ExZyubQ6LQQVuF2Qp9OsfEvsTdAWh5Gfwf6PgIdLdk=
|
||||
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/go.mod h1:idzZ7gmDeqeNrSPkdbtMp9qWMgcBwykA7P7Rzh5DXVU=
|
||||
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-test/deep v1.1.1 h1:0r/53hagsehfO4bzD2Pgr/+RgHqhmf+k1Bpse2cTu1U=
|
||||
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.6/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
|
||||
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/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
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-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.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0=
|
||||
golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw=
|
||||
golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
|
||||
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-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
|
||||
@@ -16,8 +16,6 @@ import (
|
||||
"github.com/tech/sendico/pkg/mutil/mzap"
|
||||
documentsv1 "github.com/tech/sendico/pkg/proto/billing/documents/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"
|
||||
mutil "github.com/tech/sendico/server/internal/mutil/param"
|
||||
"go.mongodb.org/mongo-driver/v2/bson"
|
||||
@@ -26,6 +24,7 @@ import (
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
"google.golang.org/grpc/status"
|
||||
"google.golang.org/protobuf/types/known/structpb"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -62,10 +61,6 @@ func (a *PaymentAPI) getOperationDocument(r *http.Request, account *model.Accoun
|
||||
if operationRef == "" {
|
||||
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)
|
||||
if h != nil {
|
||||
@@ -78,18 +73,7 @@ func (a *PaymentAPI) getOperationDocument(r *http.Request, account *model.Accoun
|
||||
return documentErrorResponse(a.logger, a.Name(), err)
|
||||
}
|
||||
|
||||
req := operationDocumentRequest(orgRef.Hex(), gatewayService, operationRef, paymentRef, 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)
|
||||
}
|
||||
req := operationDocumentRequest(orgRef.Hex(), gatewayService, operationRef, op)
|
||||
|
||||
docResp, err := a.fetchOperationDocument(r.Context(), service.InvokeURI, req)
|
||||
if err != nil {
|
||||
@@ -279,28 +263,6 @@ func (a *PaymentAPI) fetchGatewayOperation(ctx context.Context, invokeURI, opera
|
||||
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 {
|
||||
candidates := make([]discovery.GatewaySummary, 0, len(gateways))
|
||||
for _, gw := range gateways {
|
||||
@@ -351,14 +313,13 @@ func findGatewayForService(gateways []discovery.GatewaySummary, gatewayService m
|
||||
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{
|
||||
OrganizationRef: strings.TrimSpace(organizationRef),
|
||||
GatewayService: gatewayService,
|
||||
OperationRef: firstNonEmpty(strings.TrimSpace(op.GetOperationRef()), strings.TrimSpace(requestedOperationRef)),
|
||||
PaymentRef: strings.TrimSpace(paymentRef),
|
||||
OperationCode: strings.TrimSpace(op.GetType().String()),
|
||||
OperationLabel: strings.TrimSpace(op.GetType().String()),
|
||||
OperationLabel: operationLabel(op.GetType()),
|
||||
OperationState: strings.TrimSpace(op.GetStatus().String()),
|
||||
Amount: strings.TrimSpace(op.GetMoney().GetAmount()),
|
||||
Currency: strings.TrimSpace(op.GetMoney().GetCurrency()),
|
||||
@@ -371,96 +332,65 @@ func operationDocumentRequest(organizationRef string, gatewayService mservice.Ty
|
||||
req.CompletedAtUnixMs = ts.AsTime().UnixMilli()
|
||||
}
|
||||
|
||||
if isFailedOperationStatus(op.GetStatus()) {
|
||||
req.FailureCode = strings.TrimSpace(op.GetStatus().String())
|
||||
}
|
||||
req.PaymentRef = operationParamValue(op.GetParams(), "payment_ref", "parent_payment_ref", "paymentRef", "parentPaymentRef")
|
||||
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
|
||||
}
|
||||
|
||||
func isFailedOperationStatus(status connectorv1.OperationStatus) bool {
|
||||
return status == connectorv1.OperationStatus_OPERATION_FAILED || status == connectorv1.OperationStatus_OPERATION_CANCELLED
|
||||
func operationLabel(opType connectorv1.OperationType) string {
|
||||
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) {
|
||||
if req == nil || payment == nil {
|
||||
return
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
func failureCodeFromStatus(status connectorv1.OperationStatus) string {
|
||||
switch status {
|
||||
case connectorv1.OperationStatus_OPERATION_FAILED, connectorv1.OperationStatus_OPERATION_CANCELLED:
|
||||
return strings.TrimSpace(status.String())
|
||||
default:
|
||||
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 {
|
||||
for _, svc := range services {
|
||||
if !strings.EqualFold(svc.Service, documentsServiceName) {
|
||||
|
||||
@@ -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: "ationv2.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: "ationv2.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: "ationv2.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{})
|
||||
}
|
||||
@@ -165,10 +165,6 @@ func (*fakeExecutionClientForBatch) ListPayments(context.Context, *orchestration
|
||||
return &orchestrationv2.ListPaymentsResponse{}, nil
|
||||
}
|
||||
|
||||
func (*fakeExecutionClientForBatch) GetPayment(context.Context, *orchestrationv2.GetPaymentRequest) (*orchestrationv2.GetPaymentResponse, error) {
|
||||
return &orchestrationv2.GetPaymentResponse{}, nil
|
||||
}
|
||||
|
||||
func (*fakeExecutionClientForBatch) Close() error { return nil }
|
||||
|
||||
type fakeEnforcerForBatch struct {
|
||||
|
||||
@@ -32,7 +32,6 @@ import (
|
||||
type executionClient interface {
|
||||
ExecutePayment(ctx context.Context, req *orchestrationv2.ExecutePaymentRequest) (*orchestrationv2.ExecutePaymentResponse, 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)
|
||||
Close() error
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ require (
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
go.yaml.in/yaml/v2 v2.4.4 // 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/sys v0.42.0 // indirect
|
||||
golang.org/x/text v0.35.0 // indirect
|
||||
|
||||
@@ -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-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.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0=
|
||||
golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw=
|
||||
golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
|
||||
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-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
|
||||
|
||||
@@ -13,7 +13,7 @@ require (
|
||||
github.com/tech/sendico/fx/storage v0.0.0
|
||||
github.com/tech/sendico/pkg v0.1.0
|
||||
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
|
||||
)
|
||||
|
||||
|
||||
@@ -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-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.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0=
|
||||
golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw=
|
||||
golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
|
||||
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-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
|
||||
|
||||
@@ -44,7 +44,7 @@ require (
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
go.yaml.in/yaml/v2 v2.4.4 // 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/sys v0.42.0 // indirect
|
||||
golang.org/x/text v0.35.0 // indirect
|
||||
|
||||
@@ -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-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.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0=
|
||||
golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw=
|
||||
golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
|
||||
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-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
|
||||
|
||||
@@ -47,7 +47,7 @@ require (
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
go.yaml.in/yaml/v2 v2.4.4 // 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/sys v0.42.0 // indirect
|
||||
golang.org/x/text v0.35.0 // indirect
|
||||
|
||||
@@ -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-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.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0=
|
||||
golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw=
|
||||
golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
|
||||
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-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
|
||||
|
||||
@@ -86,7 +86,7 @@ require (
|
||||
go.yaml.in/yaml/v2 v2.4.4 // indirect
|
||||
golang.org/x/crypto v0.49.0 // 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/sys v0.42.0 // indirect
|
||||
golang.org/x/text v0.35.0 // indirect
|
||||
|
||||
@@ -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-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.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0=
|
||||
golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw=
|
||||
golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
|
||||
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-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
|
||||
|
||||
@@ -44,7 +44,7 @@ require (
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
go.yaml.in/yaml/v2 v2.4.4 // 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/sys v0.42.0 // indirect
|
||||
golang.org/x/text v0.35.0 // indirect
|
||||
|
||||
@@ -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-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.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0=
|
||||
golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw=
|
||||
golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
|
||||
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-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
|
||||
|
||||
@@ -47,7 +47,7 @@ require (
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
go.yaml.in/yaml/v2 v2.4.4 // 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/sys v0.42.0 // indirect
|
||||
golang.org/x/text v0.35.0 // indirect
|
||||
|
||||
@@ -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-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.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0=
|
||||
golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw=
|
||||
golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
|
||||
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-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
|
||||
|
||||
@@ -44,7 +44,7 @@ require (
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
go.yaml.in/yaml/v2 v2.4.4 // 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/sys v0.42.0 // indirect
|
||||
golang.org/x/text v0.35.0 // indirect
|
||||
|
||||
@@ -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-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.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0=
|
||||
golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw=
|
||||
golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
|
||||
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-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
|
||||
|
||||
@@ -94,7 +94,7 @@ require (
|
||||
go.yaml.in/yaml/v2 v2.4.4 // indirect
|
||||
golang.org/x/crypto v0.49.0 // 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/sys v0.42.0 // indirect
|
||||
golang.org/x/text v0.35.0 // indirect
|
||||
|
||||
@@ -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-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.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0=
|
||||
golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw=
|
||||
golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
|
||||
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-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
|
||||
|
||||
@@ -45,7 +45,7 @@ require (
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
go.yaml.in/yaml/v2 v2.4.4 // 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/sys v0.42.0 // indirect
|
||||
golang.org/x/text v0.35.0 // indirect
|
||||
|
||||
@@ -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-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.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0=
|
||||
golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw=
|
||||
golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
|
||||
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-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
|
||||
|
||||
@@ -47,7 +47,7 @@ require (
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
go.yaml.in/yaml/v2 v2.4.4 // 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/sys v0.42.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260311181403-84a4fc48630c // indirect
|
||||
|
||||
@@ -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-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.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0=
|
||||
golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw=
|
||||
golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
|
||||
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-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
|
||||
|
||||
@@ -45,7 +45,7 @@ require (
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
go.yaml.in/yaml/v2 v2.4.4 // 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/sys v0.42.0 // indirect
|
||||
golang.org/x/text v0.35.0 // indirect
|
||||
|
||||
@@ -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-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.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0=
|
||||
golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw=
|
||||
golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
|
||||
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-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
|
||||
|
||||
@@ -60,7 +60,7 @@ require (
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
go.yaml.in/yaml/v2 v2.4.4 // 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/sys v0.42.0 // indirect
|
||||
golang.org/x/text v0.35.0 // indirect
|
||||
|
||||
@@ -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-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.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0=
|
||||
golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw=
|
||||
golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
|
||||
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-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
|
||||
|
||||
@@ -59,7 +59,7 @@ require (
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
go.yaml.in/yaml/v2 v2.4.4 // 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/sys v0.42.0 // indirect
|
||||
golang.org/x/text v0.35.0 // indirect
|
||||
|
||||
@@ -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-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.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0=
|
||||
golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw=
|
||||
golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
|
||||
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-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
|
||||
|
||||
@@ -108,7 +108,7 @@ require (
|
||||
go.opentelemetry.io/otel/trace v1.39.0 // indirect
|
||||
go.uber.org/multierr v1.11.0 // 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/sys v0.42.0 // indirect
|
||||
golang.org/x/text v0.35.0 // indirect
|
||||
|
||||
@@ -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-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.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0=
|
||||
golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw=
|
||||
golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
|
||||
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-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
|
||||
@@ -48,6 +48,4 @@ message GetOperationDocumentRequest {
|
||||
|
||||
int64 started_at_unix_ms = 12;
|
||||
int64 completed_at_unix_ms = 13;
|
||||
string client_name = 14;
|
||||
string client_address = 15;
|
||||
}
|
||||
|
||||
@@ -1,17 +1,20 @@
|
||||
import 'package:money2/money2.dart';
|
||||
|
||||
import 'package:pshared/data/dto/money.dart';
|
||||
import 'package:pshared/models/money.dart';
|
||||
import 'package:pshared/utils/money.dart';
|
||||
|
||||
|
||||
extension MoneyMapper on Money {
|
||||
MoneyDTO toDTO() => MoneyDTO(
|
||||
amount: amount,
|
||||
currency: currency,
|
||||
);
|
||||
MoneyDTO toDTO() =>
|
||||
MoneyDTO(amount: toDecimal().toString(), currency: currency.isoCode);
|
||||
}
|
||||
|
||||
extension MoneyDTOMapper on MoneyDTO {
|
||||
Money toDomain() => Money(
|
||||
amount: amount,
|
||||
currency: currency,
|
||||
);
|
||||
Money toDomain() {
|
||||
final parsed = parseMoneyWithCurrencyCode(amount, currency);
|
||||
if (parsed == null) {
|
||||
throw FormatException('Invalid money dto: $currency $amount');
|
||||
}
|
||||
return parsed;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
@@ -1,16 +1,13 @@
|
||||
import 'package:pshared/models/wallet/wallet.dart' as domain;
|
||||
import 'package:pshared/models/payment/wallet.dart';
|
||||
import 'package:pshared/utils/currency.dart';
|
||||
import 'package:pshared/utils/money.dart';
|
||||
|
||||
|
||||
extension WalletUiMapper on domain.WalletModel {
|
||||
Wallet toUi() => Wallet(
|
||||
id: walletRef,
|
||||
walletUserID: walletRef,
|
||||
balance: parseMoneyAmount(
|
||||
availableMoney?.amount ?? balance?.available?.amount,
|
||||
),
|
||||
balance: availableMoney?.toDouble() ?? balance?.available?.toDouble() ?? 0,
|
||||
currency: currencyStringToCode(asset.tokenSymbol),
|
||||
calculatedAt: balance?.calculatedAt ?? DateTime.now(),
|
||||
depositAddress: depositAddress,
|
||||
|
||||
@@ -16,8 +16,12 @@ extension WalletDTOMapper on WalletDTO {
|
||||
depositAddress: depositAddress,
|
||||
status: status,
|
||||
metadata: metadata,
|
||||
createdAt: (createdAt == null || createdAt!.isEmpty) ? null : DateTime.tryParse(createdAt!),
|
||||
updatedAt: (updatedAt == null || updatedAt!.isEmpty) ? null : DateTime.tryParse(updatedAt!),
|
||||
createdAt: (createdAt == null || createdAt!.isEmpty)
|
||||
? null
|
||||
: DateTime.tryParse(createdAt!),
|
||||
updatedAt: (updatedAt == null || updatedAt!.isEmpty)
|
||||
? null
|
||||
: DateTime.tryParse(updatedAt!),
|
||||
balance: balance?.toDomain(),
|
||||
availableMoney: balance?.available?.toDomain(),
|
||||
describable: newDescribable(
|
||||
|
||||
@@ -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),
|
||||
);
|
||||
@@ -1 +1 @@
|
||||
enum Currency {usd, eur, rub, usdt, usdc}
|
||||
enum CurrencyCode {usd, eur, rub, usdt, usdc}
|
||||
@@ -1,4 +1,4 @@
|
||||
import 'package:pshared/models/money.dart';
|
||||
import 'package:money2/money2.dart';
|
||||
|
||||
|
||||
class LedgerBalance {
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
class Money {
|
||||
final String amount;
|
||||
final String currency;
|
||||
|
||||
const Money({
|
||||
required this.amount,
|
||||
required this.currency,
|
||||
});
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import 'package:pshared/models/money.dart';
|
||||
import 'package:money2/money2.dart';
|
||||
|
||||
|
||||
class PaymentExecutionOperation {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import 'package:pshared/models/money.dart';
|
||||
import 'package:money2/money2.dart';
|
||||
|
||||
|
||||
class FeeLine {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:pshared/models/money.dart';
|
||||
import 'package:money2/money2.dart';
|
||||
|
||||
|
||||
class FxQuote {
|
||||
final String? quoteRef;
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import 'package:money2/money2.dart';
|
||||
|
||||
import 'package:pshared/models/payment/fees/treatment.dart';
|
||||
import 'package:pshared/models/payment/fx/intent.dart';
|
||||
import 'package:pshared/models/payment/kind.dart';
|
||||
import 'package:pshared/models/payment/customer.dart';
|
||||
import 'package:pshared/models/payment/methods/data.dart';
|
||||
import 'package:pshared/models/money.dart';
|
||||
import 'package:pshared/models/payment/settlement_mode.dart';
|
||||
|
||||
|
||||
class PaymentIntent {
|
||||
final PaymentKind kind;
|
||||
final String? sourceRef;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:pshared/models/money.dart';
|
||||
import 'package:money2/money2.dart';
|
||||
|
||||
|
||||
class QuoteAmounts {
|
||||
final Money? sourcePrincipal;
|
||||
|
||||
@@ -7,7 +7,7 @@ class Wallet implements Describable {
|
||||
final String id;
|
||||
final String walletUserID; // ID or number that we show the user
|
||||
final double balance;
|
||||
final Currency currency;
|
||||
final CurrencyCode currency;
|
||||
final DateTime calculatedAt;
|
||||
final String? depositAddress;
|
||||
final ChainNetwork? network;
|
||||
|
||||
@@ -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;
|
||||
|
||||
const WalletAsset({
|
||||
required super.chain,
|
||||
required super.tokenSymbol,
|
||||
required this.chain,
|
||||
required this.tokenSymbol,
|
||||
required this.contractAddress,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import 'package:pshared/models/money.dart';
|
||||
import 'package:money2/money2.dart';
|
||||
|
||||
|
||||
class WalletBalance {
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
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/balance.dart';
|
||||
|
||||
@@ -39,9 +39,7 @@ class WalletModel implements Describable {
|
||||
required this.describable,
|
||||
});
|
||||
|
||||
WalletModel copyWith({
|
||||
Describable? describable,
|
||||
}) => WalletModel(
|
||||
WalletModel copyWith({Describable? describable}) => WalletModel(
|
||||
walletRef: walletRef,
|
||||
organizationRef: organizationRef,
|
||||
ownerRef: ownerRef,
|
||||
|
||||
@@ -15,6 +15,7 @@ import 'package:pshared/provider/resource.dart';
|
||||
import 'package:pshared/service/ledger.dart';
|
||||
import 'package:pshared/utils/exception.dart';
|
||||
|
||||
|
||||
class LedgerAccountsProvider with ChangeNotifier {
|
||||
final LedgerService _service;
|
||||
OrganizationsProvider? _organizations;
|
||||
@@ -179,7 +180,7 @@ class LedgerAccountsProvider with ChangeNotifier {
|
||||
|
||||
Future<void> create({
|
||||
required Describable describable,
|
||||
required Currency currency,
|
||||
required CurrencyCode currency,
|
||||
String? ownerRef,
|
||||
}) async {
|
||||
final org = _organizations;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import 'package:money2/money2.dart';
|
||||
|
||||
import 'package:pshared/controllers/payment/source.dart';
|
||||
import 'package:pshared/models/payment/asset.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/managed_wallet.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/intent.dart';
|
||||
import 'package:pshared/models/recipient/recipient.dart';
|
||||
import 'package:pshared/provider/payment/amount.dart';
|
||||
import 'package:pshared/provider/payment/flow.dart';
|
||||
import 'package:pshared/provider/recipient/provider.dart';
|
||||
import 'package:pshared/utils/currency.dart';
|
||||
import 'package:pshared/utils/payment/fx_helpers.dart';
|
||||
|
||||
|
||||
class QuotationIntentBuilder {
|
||||
static const String _settlementCurrency = 'RUB';
|
||||
static const String _addressBookCustomerFallbackId = 'address_book_customer';
|
||||
|
||||
PaymentIntent? build({
|
||||
required PaymentAmountProvider payment,
|
||||
@@ -38,10 +39,9 @@ class QuotationIntentBuilder {
|
||||
final paymentData = flow.selectedPaymentData;
|
||||
final selectedMethod = flow.selectedMethod;
|
||||
final amountValue = payment.amount;
|
||||
if (sourceMethod == null || sourceCurrency == null || paymentData == null) {
|
||||
if (sourceCurrency == null) {
|
||||
return null;
|
||||
}
|
||||
if (amountValue == null) return null;
|
||||
|
||||
final customer = _buildCustomer(
|
||||
recipient: recipients.currentObject,
|
||||
@@ -51,22 +51,22 @@ class QuotationIntentBuilder {
|
||||
final amountCurrency = payment.settlementMode == SettlementMode.fixReceived
|
||||
? _settlementCurrency
|
||||
: sourceCurrency;
|
||||
final amount = Money(
|
||||
amount: amountValue.toString(),
|
||||
currency: amountCurrency,
|
||||
);
|
||||
final currency = money2CurrencyFromCode(amountCurrency);
|
||||
if (currency == null) return null;
|
||||
final amount = amountValue == null
|
||||
? null
|
||||
: Money.fromNumWithCurrency(amountValue, currency);
|
||||
final isLedgerSource = source.selectedLedgerAccount != null;
|
||||
final isCryptoToCrypto =
|
||||
paymentData is CryptoAddressPaymentMethod &&
|
||||
(paymentData.asset?.tokenSymbol ?? '').trim().toUpperCase() ==
|
||||
amount.currency;
|
||||
sourceCurrency.trim().toUpperCase();
|
||||
final fxIntent = _buildFxIntent(
|
||||
sourceCurrency: sourceCurrency,
|
||||
settlementMode: payment.settlementMode,
|
||||
isLedgerSource: isLedgerSource,
|
||||
enabled: !isCryptoToCrypto,
|
||||
);
|
||||
final comment = _resolveComment(payment.comment);
|
||||
return PaymentIntent(
|
||||
kind: PaymentKind.payout,
|
||||
amount: amount,
|
||||
@@ -77,7 +77,7 @@ class QuotationIntentBuilder {
|
||||
? FeeTreatment.addToSource
|
||||
: FeeTreatment.deductFromDestination,
|
||||
settlementMode: payment.settlementMode,
|
||||
comment: comment,
|
||||
comment: payment.comment,
|
||||
customer: customer,
|
||||
);
|
||||
}
|
||||
@@ -94,14 +94,9 @@ class QuotationIntentBuilder {
|
||||
// BFF maps only settlement currency + fx side, then quotation derives pair.
|
||||
// For ledger this preserves source debit in ledger currency (e.g. USDT).
|
||||
if (isLedgerSource && settlementMode == SettlementMode.fixReceived) {
|
||||
final base = sourceCurrency.trim();
|
||||
final quote = _settlementCurrency;
|
||||
if (base.isEmpty || base.toUpperCase() == quote.toUpperCase()) {
|
||||
return null;
|
||||
}
|
||||
return FxIntent(
|
||||
pair: CurrencyPair(base: base, quote: quote),
|
||||
side: FxSide.sellBaseBuyQuote,
|
||||
pair: CurrencyPair(base: sourceCurrency, quote: _settlementCurrency),
|
||||
side: FxSide.buyBaseSellQuote,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -137,39 +132,59 @@ class QuotationIntentBuilder {
|
||||
required PaymentMethod? method,
|
||||
required PaymentMethodData? data,
|
||||
}) {
|
||||
final name = recipient?.name.trim();
|
||||
if (name == null || name.isEmpty) return null;
|
||||
final id = recipient?.id.trim();
|
||||
final customerId = id == null || id.isEmpty
|
||||
? _addressBookCustomerFallbackId
|
||||
: id;
|
||||
final customerId = recipient?.id.trim() ?? '';
|
||||
final card = _resolveCard(method: method, data: data);
|
||||
final fromRecipient = _buildCustomerFromName(
|
||||
customerId: customerId,
|
||||
fullName: recipient?.name,
|
||||
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 lastName = parts.length >= 2 ? parts.last : null;
|
||||
final middleName = parts.length > 2
|
||||
? parts.sublist(1, parts.length - 1).join(' ')
|
||||
: null;
|
||||
|
||||
return Customer(
|
||||
id: customerId,
|
||||
firstName: firstName,
|
||||
middleName: middleName,
|
||||
lastName: lastName,
|
||||
country: _resolveCustomerCountry(method: method, data: data),
|
||||
country: country,
|
||||
);
|
||||
}
|
||||
|
||||
String? _resolveCustomerCountry({
|
||||
required PaymentMethod? method,
|
||||
required PaymentMethodData? data,
|
||||
}) {
|
||||
final card = method?.cardData ?? (data is CardPaymentMethod ? data : null);
|
||||
return card?.country;
|
||||
}
|
||||
|
||||
String? _resolveComment(String comment) {
|
||||
final normalized = comment.trim();
|
||||
String? _normalizedOrNull(String? value) {
|
||||
if (value == null) return null;
|
||||
final normalized = value.trim();
|
||||
return normalized.isEmpty ? null : normalized;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,13 +6,13 @@ import 'package:logging/logging.dart';
|
||||
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
import 'package:money2/money2.dart';
|
||||
|
||||
import 'package:pshared/api/requests/payment/quote.dart';
|
||||
import 'package:pshared/controllers/payment/source.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/quote/quote.dart';
|
||||
import 'package:pshared/models/money.dart';
|
||||
import 'package:pshared/models/auto_refresh_mode.dart';
|
||||
import 'package:pshared/provider/organizations.dart';
|
||||
import 'package:pshared/provider/payment/amount.dart';
|
||||
@@ -79,20 +79,12 @@ class QuotationProvider extends ChangeNotifier {
|
||||
return DateTime.fromMillisecondsSinceEpoch(expiresAtUnixMs, isUtc: true);
|
||||
}
|
||||
|
||||
Asset? get fee => _assetFromMoney(quoteFeeTotal(quotation));
|
||||
Asset? get total => _assetFromMoney(
|
||||
quoteSourceDebitTotal(
|
||||
Money? get fee => quoteFeeTotal(quotation);
|
||||
Money? get total => quoteSourceDebitTotal(
|
||||
quotation,
|
||||
preferredSourceCurrency: _sourceCurrencyCode,
|
||||
),
|
||||
);
|
||||
Asset? get recipientGets =>
|
||||
_assetFromMoney(quotation?.amounts?.destinationSettlement);
|
||||
|
||||
Asset? _assetFromMoney(Money? money) {
|
||||
if (money == null) return null;
|
||||
return createAsset(money.currency, money.amount);
|
||||
}
|
||||
Money? get recipientGets => quotation?.amounts?.destinationSettlement;
|
||||
|
||||
void _setResource(Resource<PaymentQuote> quotation) {
|
||||
_quotation = quotation;
|
||||
|
||||
@@ -5,14 +5,16 @@ import 'package:flutter/foundation.dart';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
|
||||
import 'package:pshared/models/currency.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/wallet/chain_asset.dart';
|
||||
import 'package:pshared/provider/organizations.dart';
|
||||
import 'package:pshared/provider/resource.dart';
|
||||
import 'package:pshared/service/payment/wallets.dart';
|
||||
import 'package:pshared/utils/exception.dart';
|
||||
|
||||
|
||||
class WalletsProvider with ChangeNotifier {
|
||||
final WalletsService _service;
|
||||
OrganizationsProvider? _organizations;
|
||||
@@ -180,7 +182,8 @@ class WalletsProvider with ChangeNotifier {
|
||||
|
||||
Future<void> create({
|
||||
required Describable describable,
|
||||
required ChainAsset asset,
|
||||
required ChainNetwork chain,
|
||||
required CurrencyCode currency,
|
||||
required String? ownerRef,
|
||||
}) async {
|
||||
final org = _organizations;
|
||||
@@ -195,7 +198,8 @@ class WalletsProvider with ChangeNotifier {
|
||||
await _service.create(
|
||||
organizationRef: org.current.id,
|
||||
describable: describable,
|
||||
asset: asset,
|
||||
chain: chain,
|
||||
currency: currency,
|
||||
ownerRef: ownerRef,
|
||||
);
|
||||
await loadWalletsWithBalances();
|
||||
|
||||
@@ -43,7 +43,7 @@ class LedgerService {
|
||||
required String organizationRef,
|
||||
required Describable describable,
|
||||
required String? ownerRef,
|
||||
required Currency currency,
|
||||
required CurrencyCode currency,
|
||||
}) async => AuthorizationService.getPOSTResponse(
|
||||
_objectType,
|
||||
'/$organizationRef',
|
||||
|
||||
@@ -13,12 +13,10 @@ class PaymentDocumentsService {
|
||||
String organizationRef,
|
||||
String gatewayService,
|
||||
String operationRef,
|
||||
String paymentRef,
|
||||
) async {
|
||||
final query = <String, String>{
|
||||
'gateway_service': gatewayService,
|
||||
'operation_ref': operationRef,
|
||||
'payment_ref': paymentRef,
|
||||
};
|
||||
final queryString = Uri(queryParameters: query).query;
|
||||
final url = '/documents/operation/$organizationRef?$queryString';
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import 'package:pshared/data/mapper/wallet/ui.dart';
|
||||
import 'package:pshared/models/currency.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/wallet/chain_asset.dart';
|
||||
import 'package:pshared/service/wallet.dart' as shared_wallet_service;
|
||||
import 'package:pshared/utils/money.dart';
|
||||
|
||||
|
||||
abstract class WalletsService {
|
||||
@@ -12,7 +12,8 @@ abstract class WalletsService {
|
||||
Future<void> create({
|
||||
required String organizationRef,
|
||||
required Describable describable,
|
||||
required ChainAsset asset,
|
||||
required ChainNetwork chain,
|
||||
required CurrencyCode currency,
|
||||
required String? ownerRef,
|
||||
});
|
||||
}
|
||||
@@ -30,19 +31,21 @@ class ApiWalletsService implements WalletsService {
|
||||
organizationRef: organizationRef,
|
||||
walletRef: walletRef,
|
||||
);
|
||||
return parseMoneyAmount(balance.available?.amount);
|
||||
return balance.available?.toDouble() ?? 0;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> create({
|
||||
required String organizationRef,
|
||||
required Describable describable,
|
||||
required ChainAsset asset,
|
||||
required ChainNetwork chain,
|
||||
required CurrencyCode currency,
|
||||
required String? ownerRef,
|
||||
}) => shared_wallet_service.WalletService.create(
|
||||
organizationRef: organizationRef,
|
||||
describable: describable,
|
||||
asset: asset,
|
||||
chain: chain,
|
||||
currency: currency,
|
||||
ownerRef: ownerRef,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
import 'package:pshared/api/requests/wallet/create.dart';
|
||||
import 'package:pshared/api/responses/wallet_balance.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/wallet/chain_asset.dart';
|
||||
import 'package:pshared/data/mapper/payment/enums.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/payment/chain_network.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/service/authorization/service.dart';
|
||||
import 'package:pshared/service/services.dart';
|
||||
import 'package:pshared/utils/currency.dart';
|
||||
|
||||
|
||||
class WalletService {
|
||||
@@ -37,13 +40,19 @@ class WalletService {
|
||||
static Future<void> create({
|
||||
required String organizationRef,
|
||||
required Describable describable,
|
||||
required ChainAsset asset,
|
||||
required ChainNetwork chain,
|
||||
required CurrencyCode currency,
|
||||
required String? ownerRef,
|
||||
}) async => AuthorizationService.getPOSTResponse(
|
||||
_objectType,
|
||||
'/$organizationRef',
|
||||
CreateWalletRequest(
|
||||
asset: asset.toDTO(),
|
||||
asset: ChainAssetDTO(
|
||||
chain: chainNetworkToValue(chain),
|
||||
tokenSymbol:
|
||||
money2CurrencyFromCode(currencyCodeToString(currency))?.isoCode ??
|
||||
currencyCodeToString(currency),
|
||||
),
|
||||
describable: describable.toDTO(),
|
||||
ownerRef: ownerRef,
|
||||
).toJson(),
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
const nonBreakingSpace = '\u00A0';
|
||||
final Currency _usdtCurrency = Currency.create(
|
||||
'USDT',
|
||||
6,
|
||||
symbol: '₮',
|
||||
isIso: false,
|
||||
country: 'Digital',
|
||||
unit: 'Tether',
|
||||
name: 'Tether',
|
||||
);
|
||||
|
||||
String withTrailingNonBreakingSpace(String value) {
|
||||
return '$value$nonBreakingSpace';
|
||||
}
|
||||
final Currency _usdcCurrency = Currency.create(
|
||||
'USDC',
|
||||
6,
|
||||
symbol: r'($)',
|
||||
isIso: false,
|
||||
country: 'Digital',
|
||||
unit: 'USD Coin',
|
||||
name: 'USD Coin',
|
||||
);
|
||||
|
||||
String joinWithNonBreakingSpace(String left, String right) {
|
||||
return '$left$nonBreakingSpace$right';
|
||||
}
|
||||
final Map<String, Currency> _commonCurrenciesByCode =
|
||||
<String, Currency>{
|
||||
for (final currency in CommonCurrencies().asList())
|
||||
currency.isoCode: currency,
|
||||
_usdtCurrency.isoCode: _usdtCurrency,
|
||||
_usdcCurrency.isoCode: _usdcCurrency,
|
||||
};
|
||||
|
||||
String currencyCodeToSymbol(Currency currencyCode) {
|
||||
switch (currencyCode) {
|
||||
case Currency.usd:
|
||||
return '\$';
|
||||
case Currency.usdt:
|
||||
return '₮';
|
||||
case Currency.usdc:
|
||||
return '\$';
|
||||
case Currency.rub:
|
||||
return '₽';
|
||||
case Currency.eur:
|
||||
return '€';
|
||||
String currencyCodeToSymbol(CurrencyCode currencyCode) {
|
||||
final symbol = currencySymbolFromCode(currencyCodeToString(currencyCode));
|
||||
if (symbol == null || symbol.trim().isEmpty) {
|
||||
return currencyCodeToString(currencyCode);
|
||||
}
|
||||
return symbol;
|
||||
}
|
||||
|
||||
String amountToString(double amount) {
|
||||
return amount.toStringAsFixed(2);
|
||||
String currencyToString(CurrencyCode currencyCode, double amount) {
|
||||
final code = currencyCodeToString(currencyCode);
|
||||
final currency = money2CurrencyFromCode(code);
|
||||
if (currency == null) {
|
||||
return '$amount $code';
|
||||
}
|
||||
final money = Money.fromNumWithCurrency(amount, currency);
|
||||
return money.toString();
|
||||
}
|
||||
|
||||
String currencyToString(Currency currencyCode, double amount) {
|
||||
return joinWithNonBreakingSpace(
|
||||
currencyCodeToSymbol(currencyCode),
|
||||
amountToString(amount),
|
||||
);
|
||||
}
|
||||
CurrencyCode currencyStringToCode(String currencyCode) {
|
||||
final normalized = currencyCode.trim().toUpperCase();
|
||||
for (final value in CurrencyCode.values) {
|
||||
if (currencyCodeToString(value) == normalized) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
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');
|
||||
}
|
||||
}
|
||||
|
||||
String currencyCodeToString(Currency currencyCode) {
|
||||
switch (currencyCode) {
|
||||
case Currency.usd:
|
||||
return 'USD';
|
||||
case Currency.usdt:
|
||||
return 'USDT';
|
||||
case Currency.usdc:
|
||||
return 'USDC';
|
||||
case Currency.rub:
|
||||
return 'RUB';
|
||||
case Currency.eur:
|
||||
return 'EUR';
|
||||
}
|
||||
}
|
||||
|
||||
IconData iconForCurrencyType(Currency currencyCode) {
|
||||
switch (currencyCode) {
|
||||
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 currencyCodeToString(CurrencyCode currencyCode) {
|
||||
return currencyCode.name.toUpperCase();
|
||||
}
|
||||
|
||||
String? currencySymbolFromCode(String? code) {
|
||||
final normalized = code?.trim();
|
||||
if (normalized == null || normalized.isEmpty) return null;
|
||||
try {
|
||||
return currencyCodeToSymbol(currencyStringToCode(normalized.toUpperCase()));
|
||||
} catch (_) {
|
||||
return null;
|
||||
}
|
||||
final currency = money2CurrencyFromCode(code);
|
||||
if (currency == null) return null;
|
||||
final symbol = currency.symbol.trim();
|
||||
return symbol.isEmpty ? null : symbol;
|
||||
}
|
||||
|
||||
Currency? money2CurrencyFromCode(String? code) {
|
||||
final normalized = code?.trim().toUpperCase();
|
||||
if (normalized == null || normalized.isEmpty) return null;
|
||||
return _commonCurrenciesByCode[normalized] ?? Currencies().find(normalized);
|
||||
}
|
||||
|
||||
@@ -1,41 +1,35 @@
|
||||
import 'package:pshared/models/money.dart';
|
||||
import 'package:money2/money2.dart';
|
||||
import 'package:pshared/utils/currency.dart';
|
||||
|
||||
const String _decimalMoneyPattern = '0.##################';
|
||||
|
||||
double parseMoneyAmount(String? raw, {double fallback = 0}) {
|
||||
final trimmed = raw?.trim();
|
||||
if (trimmed == null || trimmed.isEmpty) return fallback;
|
||||
return double.tryParse(trimmed) ?? fallback;
|
||||
Money? parseMoneyWithCurrency(String? amount, Currency? currency) {
|
||||
if (currency == null) return null;
|
||||
final value = _normalizeMoneyAmount(amount);
|
||||
if (value == null || value.isEmpty) return null;
|
||||
|
||||
try {
|
||||
return Money.parseWithCurrency(
|
||||
value,
|
||||
currency,
|
||||
pattern: _decimalMoneyPattern,
|
||||
);
|
||||
} catch (_) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
String formatMoneyDisplay(
|
||||
Money? money, {
|
||||
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';
|
||||
Money? parseMoneyWithCurrencyCode(String? amount, String? currencyCode) {
|
||||
return parseMoneyWithCurrency(amount, money2CurrencyFromCode(currencyCode));
|
||||
}
|
||||
|
||||
extension MoneyAmountX on Money {
|
||||
double get amountValue => parseMoneyAmount(amount);
|
||||
String? _normalizeMoneyAmount(String? value) {
|
||||
final normalized = value?.trim();
|
||||
if (normalized == null || normalized.isEmpty) return null;
|
||||
|
||||
if (normalized.contains(',') && !normalized.contains('.')) {
|
||||
return normalized.replaceAll(',', '.');
|
||||
}
|
||||
|
||||
return normalized;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import 'package:money2/money2.dart';
|
||||
|
||||
import 'package:pshared/models/payment/currency_pair.dart';
|
||||
import 'package:pshared/models/payment/fx/intent.dart';
|
||||
import 'package:pshared/models/payment/fx/side.dart';
|
||||
import 'package:pshared/models/money.dart';
|
||||
|
||||
|
||||
class FxIntentHelper {
|
||||
@@ -37,11 +38,15 @@ class FxIntentHelper {
|
||||
case FxSide.unspecified:
|
||||
break;
|
||||
}
|
||||
if (amount.currency == pair.base && pair.quote.isNotEmpty) return pair.quote;
|
||||
if (amount.currency == pair.quote && pair.base.isNotEmpty) return pair.base;
|
||||
if (amount.currency.isoCode == pair.base && pair.quote.isNotEmpty) {
|
||||
return pair.quote;
|
||||
}
|
||||
if (amount.currency.isoCode == pair.quote && pair.base.isNotEmpty) {
|
||||
return pair.base;
|
||||
}
|
||||
if (pair.quote.isNotEmpty) return pair.quote;
|
||||
if (pair.base.isNotEmpty) return pair.base;
|
||||
}
|
||||
return amount.currency;
|
||||
return amount.currency.isoCode;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ class PaymentQuotationCurrencyResolver {
|
||||
PaymentMethodData? paymentData,
|
||||
}) {
|
||||
final quoteCurrency = _normalizeCurrency(
|
||||
quote?.amounts?.destinationSettlement?.currency,
|
||||
quote?.amounts?.destinationSettlement?.currency.isoCode,
|
||||
);
|
||||
if (quoteCurrency != null) return quoteCurrency;
|
||||
|
||||
|
||||
@@ -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/quote/quote.dart';
|
||||
import 'package:pshared/utils/currency.dart';
|
||||
import 'package:pshared/utils/money.dart';
|
||||
|
||||
|
||||
Money? quoteFeeTotal(PaymentQuote? quote) {
|
||||
final preferredCurrency =
|
||||
quote?.amounts?.sourcePrincipal?.currency ??
|
||||
quote?.amounts?.sourceDebitTotal?.currency;
|
||||
return quoteFeeTotalFromLines(
|
||||
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 preferredCurrency = _normalizeCurrency(
|
||||
preferredSourceCurrency ?? quote?.amounts?.sourcePrincipal?.currency,
|
||||
preferredSourceCurrency ??
|
||||
quote?.amounts?.sourcePrincipal?.currency.isoCode,
|
||||
);
|
||||
|
||||
if (sourceDebitTotal == null) {
|
||||
@@ -31,10 +31,9 @@ Money? quoteSourceDebitTotal(
|
||||
);
|
||||
}
|
||||
|
||||
final debitCurrency = _normalizeCurrency(sourceDebitTotal.currency);
|
||||
if (preferredCurrency == null ||
|
||||
debitCurrency == null ||
|
||||
debitCurrency == preferredCurrency) {
|
||||
_normalizeCurrency(sourceDebitTotal.currency.isoCode) ==
|
||||
preferredCurrency) {
|
||||
return sourceDebitTotal;
|
||||
}
|
||||
|
||||
@@ -52,22 +51,18 @@ Money? quoteFeeTotalFromLines(
|
||||
if (lines == null || lines.isEmpty) return null;
|
||||
|
||||
final normalizedPreferred = _normalizeCurrency(preferredCurrency);
|
||||
final totalsByCurrency = <String, double>{};
|
||||
final totalsByCurrency = <String, Money>{};
|
||||
|
||||
for (final line in lines) {
|
||||
final money = line.amount;
|
||||
if (money == null) continue;
|
||||
final parsedAmount = line.amount;
|
||||
if (parsedAmount == null) continue;
|
||||
|
||||
final currency = _normalizeCurrency(money.currency);
|
||||
if (currency == null) continue;
|
||||
|
||||
final amount = parseMoneyAmount(money.amount, fallback: double.nan);
|
||||
if (amount.isNaN) continue;
|
||||
|
||||
final sign = _lineSign(line.side);
|
||||
final signedAmount = sign * amount.abs();
|
||||
totalsByCurrency[currency] =
|
||||
(totalsByCurrency[currency] ?? 0) + signedAmount;
|
||||
final currencyCode = parsedAmount.currency.isoCode;
|
||||
final signedAmount = _isCreditLine(line.side) ? -parsedAmount : parsedAmount;
|
||||
final current = totalsByCurrency[currencyCode];
|
||||
totalsByCurrency[currencyCode] = current == null
|
||||
? signedAmount
|
||||
: current + signedAmount;
|
||||
}
|
||||
|
||||
if (totalsByCurrency.isEmpty) return null;
|
||||
@@ -77,85 +72,59 @@ Money? quoteFeeTotalFromLines(
|
||||
totalsByCurrency.containsKey(normalizedPreferred)
|
||||
? normalizedPreferred
|
||||
: totalsByCurrency.keys.first;
|
||||
final total = totalsByCurrency[selectedCurrency];
|
||||
if (total == null) return null;
|
||||
|
||||
return Money(amount: amountToString(total), currency: selectedCurrency);
|
||||
return totalsByCurrency[selectedCurrency];
|
||||
}
|
||||
|
||||
List<Money> aggregateMoneyByCurrency(Iterable<Money?> values) {
|
||||
final totals = <String, double>{};
|
||||
final totals = <String, Money>{};
|
||||
for (final value in values) {
|
||||
if (value == null) continue;
|
||||
|
||||
final currency = _normalizeCurrency(value.currency);
|
||||
if (currency == null) continue;
|
||||
|
||||
final amount = parseMoneyAmount(value.amount, fallback: double.nan);
|
||||
if (amount.isNaN) continue;
|
||||
|
||||
totals[currency] = (totals[currency] ?? 0) + amount;
|
||||
final currency = value.currency.isoCode;
|
||||
final current = totals[currency];
|
||||
totals[currency] = current == null ? value : current + value;
|
||||
}
|
||||
|
||||
return totals.entries
|
||||
.map(
|
||||
(entry) =>
|
||||
Money(amount: amountToString(entry.value), currency: entry.key),
|
||||
)
|
||||
.toList();
|
||||
return totals.values.toList();
|
||||
}
|
||||
|
||||
Money? _rebuildSourceDebitTotal(
|
||||
PaymentQuote? quote, {
|
||||
String? preferredSourceCurrency,
|
||||
}) {
|
||||
final sourcePrincipal = quote?.amounts?.sourcePrincipal;
|
||||
if (sourcePrincipal == null) return null;
|
||||
final principal = quote?.amounts?.sourcePrincipal;
|
||||
if (principal == null) return null;
|
||||
|
||||
final principalCurrency = _normalizeCurrency(sourcePrincipal.currency);
|
||||
if (principalCurrency == null) return null;
|
||||
final principalCurrency = principal.currency.isoCode;
|
||||
if (preferredSourceCurrency != null &&
|
||||
principalCurrency != preferredSourceCurrency) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final principalAmount = parseMoneyAmount(
|
||||
sourcePrincipal.amount,
|
||||
fallback: double.nan,
|
||||
);
|
||||
if (principalAmount.isNaN) return null;
|
||||
|
||||
double totalAmount = principalAmount;
|
||||
var totalAmount = principal;
|
||||
final fee = quoteFeeTotalFromLines(
|
||||
quote?.fees?.lines,
|
||||
preferredCurrency: principalCurrency,
|
||||
);
|
||||
if (fee != null && _normalizeCurrency(fee.currency) == principalCurrency) {
|
||||
final feeAmount = parseMoneyAmount(fee.amount, fallback: double.nan);
|
||||
if (!feeAmount.isNaN) {
|
||||
totalAmount += feeAmount;
|
||||
}
|
||||
if (fee != null && fee.currency.isoCode == principalCurrency) {
|
||||
totalAmount += fee;
|
||||
}
|
||||
|
||||
return Money(
|
||||
amount: amountToString(totalAmount),
|
||||
currency: principalCurrency,
|
||||
);
|
||||
return totalAmount;
|
||||
}
|
||||
|
||||
double _lineSign(String? side) {
|
||||
bool _isCreditLine(String? side) {
|
||||
final normalized = side?.trim().toLowerCase() ?? '';
|
||||
switch (normalized) {
|
||||
case 'entry_side_credit':
|
||||
case 'credit':
|
||||
return -1;
|
||||
return true;
|
||||
default:
|
||||
return 1;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
String? _normalizeCurrency(String? currency) {
|
||||
final normalized = currency?.trim().toUpperCase();
|
||||
final normalized = currency?.trim();
|
||||
if (normalized == null || normalized.isEmpty) return null;
|
||||
return normalized;
|
||||
return money2CurrencyFromCode(normalized)?.isoCode ?? normalized.toUpperCase();
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ dependencies:
|
||||
uuid: ^4.5.1
|
||||
image: ^4.5.4
|
||||
shared_preferences: ^2.5.3
|
||||
money2: ^6.3.0
|
||||
|
||||
dev_dependencies:
|
||||
flutter_lints: ^6.0.0
|
||||
|
||||
@@ -348,7 +348,7 @@ RouteBase payoutShellRoute() => ShellRoute(
|
||||
path: PayoutRoutes.reportPaymentPath,
|
||||
pageBuilder: (_, state) => NoTransitionPage(
|
||||
child: PaymentDetailsPage(
|
||||
paymentRef:
|
||||
paymentId:
|
||||
state.uri.queryParameters[PayoutRoutes.reportPaymentIdQuery] ??
|
||||
'',
|
||||
),
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,11 @@
|
||||
import 'package:money2/money2.dart';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:pshared/controllers/payment/source.dart';
|
||||
import 'package:pshared/models/payment/settlement_mode.dart';
|
||||
import 'package:pshared/provider/payment/amount.dart';
|
||||
import 'package:pshared/utils/currency.dart';
|
||||
import 'package:pshared/utils/money.dart';
|
||||
|
||||
import 'package:pweb/models/payment/amount/mode.dart';
|
||||
|
||||
@@ -22,7 +23,7 @@ class PaymentAmountFieldController extends ChangeNotifier {
|
||||
|
||||
PaymentAmountFieldController({required double? initialAmount})
|
||||
: textController = TextEditingController(
|
||||
text: initialAmount == null ? '' : amountToString(initialAmount),
|
||||
text: initialAmount == null ? '' : initialAmount.toString(),
|
||||
);
|
||||
|
||||
PaymentAmountMode get mode => _mode;
|
||||
@@ -122,18 +123,14 @@ class PaymentAmountFieldController extends ChangeNotifier {
|
||||
};
|
||||
|
||||
double? _parseAmount(String value) {
|
||||
final parsed = parseMoneyAmount(
|
||||
value.replaceAll(',', '.'),
|
||||
fallback: double.nan,
|
||||
);
|
||||
return parsed.isNaN ? null : parsed;
|
||||
return double.tryParse(value.replaceAll(',', '.').trim());
|
||||
}
|
||||
|
||||
void _syncTextWithAmount(double? amount) {
|
||||
final parsedText = _parseAmount(textController.text);
|
||||
if (parsedText == amount) return;
|
||||
|
||||
final nextText = amount == null ? '' : amountToString(amount);
|
||||
final nextText = amount == null ? '' : _formatAmount(amount);
|
||||
_isSyncingText = true;
|
||||
textController.value = TextEditingValue(
|
||||
text: nextText,
|
||||
@@ -142,6 +139,12 @@ class PaymentAmountFieldController extends ChangeNotifier {
|
||||
_isSyncingText = false;
|
||||
}
|
||||
|
||||
String _formatAmount(double amount) {
|
||||
final currency = money2CurrencyFromCode(activeCurrencyCode);
|
||||
if (currency == null) return amount.toString();
|
||||
return Money.fromNumWithCurrency(amount, currency).toDecimal().toString();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_provider?.removeListener(_handleProviderChanged);
|
||||
|
||||
@@ -9,14 +9,14 @@ import 'package:pweb/utils/report/operations/document_rule.dart';
|
||||
|
||||
|
||||
class PaymentDetailsController extends ChangeNotifier {
|
||||
PaymentDetailsController({required String paymentRef})
|
||||
: _paymentRef = paymentRef;
|
||||
PaymentDetailsController({required String paymentId})
|
||||
: _paymentId = paymentId;
|
||||
|
||||
PaymentsProvider? _payments;
|
||||
String _paymentRef;
|
||||
String _paymentId;
|
||||
Payment? _payment;
|
||||
|
||||
String get paymentId => _paymentRef;
|
||||
String get paymentId => _paymentId;
|
||||
Payment? get payment => _payment;
|
||||
bool get isLoading => _payments?.isLoading ?? false;
|
||||
Exception? get error => _payments?.error;
|
||||
@@ -44,8 +44,8 @@ class PaymentDetailsController extends ChangeNotifier {
|
||||
operationDocumentRequest(operation) != null;
|
||||
|
||||
void update(PaymentsProvider provider, String paymentId) {
|
||||
if (_paymentRef != paymentId) {
|
||||
_paymentRef = paymentId;
|
||||
if (_paymentId != paymentId) {
|
||||
_paymentId = paymentId;
|
||||
}
|
||||
|
||||
if (!identical(_payments, provider)) {
|
||||
@@ -60,7 +60,7 @@ class PaymentDetailsController extends ChangeNotifier {
|
||||
}
|
||||
|
||||
void _rebuild() {
|
||||
_payment = _findPayment(_payments?.payments ?? const [], _paymentRef);
|
||||
_payment = _findPayment(_payments?.payments ?? const [], _paymentId);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
|
||||
@@ -29,5 +29,4 @@ class RecentPaymentsController extends ChangeNotifier {
|
||||
_recent = sortOperations(operations).take(5).toList();
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2,8 +2,9 @@ import 'dart:async';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
import 'package:money2/money2.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/chain_network.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/services/payments/csv_input.dart';
|
||||
|
||||
|
||||
class MultiplePayoutsController extends ChangeNotifier {
|
||||
final CsvInputService _csvInput;
|
||||
MultiplePayoutsProvider? _provider;
|
||||
|
||||
@@ -31,10 +31,7 @@ import 'package:pweb/app/app.dart';
|
||||
import 'package:pweb/pages/invitations/widgets/list/view_model.dart';
|
||||
import 'package:pweb/app/timeago.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/wallet_transactions.dart';
|
||||
import 'package:pweb/providers/account.dart';
|
||||
import 'package:pweb/providers/locale.dart';
|
||||
|
||||
@@ -142,18 +139,6 @@ void main() async {
|
||||
create: (_) => WalletsController(),
|
||||
update: (_, wallets, controller) => controller!..update(wallets),
|
||||
),
|
||||
ChangeNotifierProvider(
|
||||
create: (_) => WalletTransactionsProvider(
|
||||
MockWalletTransactionsService(),
|
||||
),
|
||||
),
|
||||
ChangeNotifierProxyProvider<
|
||||
WalletTransactionsProvider,
|
||||
WalletTransactionsController
|
||||
>(
|
||||
create: (_) => WalletTransactionsController(),
|
||||
update: (_, provider, controller) => controller!..update(provider),
|
||||
),
|
||||
],
|
||||
child: const PayApp(),
|
||||
),
|
||||
|
||||
@@ -29,7 +29,7 @@ class WalletTransaction {
|
||||
final WalletTransactionType type;
|
||||
final OperationStatus status;
|
||||
final double amount;
|
||||
final Currency currency;
|
||||
final CurrencyCode currency;
|
||||
final DateTime date;
|
||||
final String description;
|
||||
final String? counterparty;
|
||||
@@ -56,7 +56,7 @@ class WalletTransaction {
|
||||
WalletTransactionType? type,
|
||||
OperationStatus? status,
|
||||
double? amount,
|
||||
Currency? currency,
|
||||
CurrencyCode? currency,
|
||||
DateTime? date,
|
||||
String? description,
|
||||
String? counterparty,
|
||||
|
||||
@@ -2,6 +2,6 @@ import 'package:pshared/models/currency.dart';
|
||||
import 'package:pshared/models/payment/chain_network.dart';
|
||||
|
||||
|
||||
const Currency managedCurrencyDefault = Currency.usdt;
|
||||
const Currency ledgerCurrencyDefault = Currency.rub;
|
||||
const CurrencyCode managedCurrencyDefault = CurrencyCode.usdt;
|
||||
const CurrencyCode ledgerCurrencyDefault = CurrencyCode.rub;
|
||||
const ChainNetwork managedNetworkDefault = ChainNetwork.tronMainnet;
|
||||
|
||||
@@ -8,11 +8,9 @@ import 'package:pshared/models/currency.dart';
|
||||
import 'package:pshared/models/describable.dart';
|
||||
import 'package:pshared/models/payment/chain_network.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/ledger.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/constants.dart';
|
||||
@@ -42,9 +40,9 @@ class _AddBalanceDialogState extends State<AddBalanceDialog> {
|
||||
|
||||
PaymentType _assetType = PaymentType.managedWallet;
|
||||
String? _ownerRef;
|
||||
Currency _managedCurrency = managedCurrencyDefault;
|
||||
CurrencyCode _managedCurrency = managedCurrencyDefault;
|
||||
ChainNetwork _network = managedNetworkDefault;
|
||||
Currency _ledgerCurrency = ledgerCurrencyDefault;
|
||||
CurrencyCode _ledgerCurrency = ledgerCurrencyDefault;
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
@@ -60,7 +58,7 @@ class _AddBalanceDialogState extends State<AddBalanceDialog> {
|
||||
|
||||
void _setOwnerRef(String? value) => setState(() => _ownerRef = value);
|
||||
|
||||
void _setManagedCurrency(Currency? value) {
|
||||
void _setManagedCurrency(CurrencyCode? value) {
|
||||
if (value == null) return;
|
||||
setState(() => _managedCurrency = value);
|
||||
}
|
||||
@@ -70,7 +68,7 @@ class _AddBalanceDialogState extends State<AddBalanceDialog> {
|
||||
setState(() => _network = value);
|
||||
}
|
||||
|
||||
void _setLedgerCurrency(Currency? value) {
|
||||
void _setLedgerCurrency(CurrencyCode? value) {
|
||||
if (value == null) return;
|
||||
setState(() => _ledgerCurrency = value);
|
||||
}
|
||||
@@ -102,7 +100,8 @@ class _AddBalanceDialogState extends State<AddBalanceDialog> {
|
||||
if (_assetType == PaymentType.managedWallet) {
|
||||
await context.read<WalletsProvider>().create(
|
||||
describable: newDescribable(name: name, description: description),
|
||||
asset: ChainAsset(chain: _network, tokenSymbol: currencyCodeToString(_managedCurrency)),
|
||||
chain: _network,
|
||||
currency: _managedCurrency,
|
||||
ownerRef: owner?.id,
|
||||
);
|
||||
} else {
|
||||
|
||||
@@ -23,12 +23,12 @@ class AddBalanceForm extends StatelessWidget {
|
||||
final ValueChanged<String?> onOwnerChanged;
|
||||
final TextEditingController nameController;
|
||||
final TextEditingController descriptionController;
|
||||
final Currency managedCurrency;
|
||||
final CurrencyCode managedCurrency;
|
||||
final ChainNetwork network;
|
||||
final Currency ledgerCurrency;
|
||||
final ValueChanged<Currency?> onManagedCurrencyChanged;
|
||||
final CurrencyCode ledgerCurrency;
|
||||
final ValueChanged<CurrencyCode?> onManagedCurrencyChanged;
|
||||
final ValueChanged<ChainNetwork?> onNetworkChanged;
|
||||
final ValueChanged<Currency?> onLedgerCurrencyChanged;
|
||||
final ValueChanged<CurrencyCode?> onLedgerCurrencyChanged;
|
||||
final bool showEmployeesLoading;
|
||||
|
||||
const AddBalanceForm({
|
||||
|
||||
@@ -4,7 +4,7 @@ import 'package:pshared/models/currency.dart';
|
||||
import 'package:pshared/utils/currency.dart';
|
||||
|
||||
|
||||
DropdownMenuItem<Currency> currencyItem(Currency currency) => DropdownMenuItem(
|
||||
DropdownMenuItem<CurrencyCode> currencyItem(CurrencyCode currency) => DropdownMenuItem(
|
||||
value: currency,
|
||||
child: Text(currencyCodeToString(currency)),
|
||||
);
|
||||
@@ -10,8 +10,8 @@ import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||
|
||||
|
||||
class LedgerFields extends StatelessWidget {
|
||||
final Currency currency;
|
||||
final ValueChanged<Currency?>? onCurrencyChanged;
|
||||
final CurrencyCode currency;
|
||||
final ValueChanged<CurrencyCode?>? onCurrencyChanged;
|
||||
|
||||
const LedgerFields({
|
||||
super.key,
|
||||
@@ -20,7 +20,7 @@ class LedgerFields extends StatelessWidget {
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => DropdownButtonFormField<Currency>(
|
||||
Widget build(BuildContext context) => DropdownButtonFormField<CurrencyCode>(
|
||||
initialValue: currency,
|
||||
decoration: getInputDecoration(context, AppLocalizations.of(context)!.currency, true),
|
||||
items: [
|
||||
|
||||
@@ -12,9 +12,9 @@ import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||
|
||||
|
||||
class ManagedWalletFields extends StatelessWidget {
|
||||
final Currency currency;
|
||||
final CurrencyCode currency;
|
||||
final ChainNetwork network;
|
||||
final ValueChanged<Currency?>? onCurrencyChanged;
|
||||
final ValueChanged<CurrencyCode?>? onCurrencyChanged;
|
||||
final ValueChanged<ChainNetwork?>? onNetworkChanged;
|
||||
|
||||
const ManagedWalletFields({
|
||||
@@ -31,7 +31,7 @@ class ManagedWalletFields extends StatelessWidget {
|
||||
return Column(
|
||||
spacing: 12,
|
||||
children: [
|
||||
DropdownButtonFormField<Currency>(
|
||||
DropdownButtonFormField<CurrencyCode>(
|
||||
initialValue: currency,
|
||||
decoration: getInputDecoration(context, l10n.currency, true),
|
||||
items: [
|
||||
|
||||
@@ -3,7 +3,6 @@ import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.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/utils/currency.dart';
|
||||
|
||||
@@ -28,12 +27,10 @@ class BalanceAmount extends StatelessWidget {
|
||||
final textTheme = Theme.of(context).textTheme;
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
final currencyBalance = currencyCodeToSymbol(wallet.currency);
|
||||
final formattedBalance = formatMoneyUi(
|
||||
final formattedBalance = formatAmountUi(
|
||||
context,
|
||||
Money(
|
||||
amount: amountToString(wallet.balance),
|
||||
amount: wallet.balance,
|
||||
currency: currencyCodeToString(wallet.currency),
|
||||
),
|
||||
);
|
||||
final wallets = context.watch<WalletsController>();
|
||||
final isMasked = wallets.isBalanceMasked(wallet.id);
|
||||
|
||||
@@ -36,9 +36,7 @@ class PaymentAmountField extends StatelessWidget {
|
||||
decoration: InputDecoration(
|
||||
labelText: loc.amount,
|
||||
border: const OutlineInputBorder(),
|
||||
prefixText: symbol == null
|
||||
? null
|
||||
: withTrailingNonBreakingSpace(symbol),
|
||||
prefixText: symbol == null ? null : '$symbol ',
|
||||
),
|
||||
onChanged: ui.handleChanged,
|
||||
),
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:pshared/utils/currency.dart';
|
||||
|
||||
import 'package:pweb/controllers/payouts/multiple_payouts.dart';
|
||||
import 'package:pweb/models/dashboard/summary_values.dart';
|
||||
import 'package:pweb/pages/dashboard/payouts/summary/widget.dart';
|
||||
@@ -28,21 +26,12 @@ class SourceQuoteSummary extends StatelessWidget {
|
||||
values: PaymentSummaryValues(
|
||||
fee: controller.aggregateFeeAmount == null
|
||||
? l10n.noFee
|
||||
: formatMoneyUiWithL10n(
|
||||
l10n,
|
||||
controller.aggregateFeeAmount,
|
||||
separator: nonBreakingSpace,
|
||||
),
|
||||
recipientReceives: formatMoneyUiWithL10n(
|
||||
l10n,
|
||||
: formatMoneyUi(context, controller.aggregateFeeAmount),
|
||||
recipientReceives: formatMoneyUi(
|
||||
context,
|
||||
controller.aggregateSettlementAmount,
|
||||
separator: nonBreakingSpace,
|
||||
),
|
||||
total: formatMoneyUiWithL10n(
|
||||
l10n,
|
||||
controller.aggregateDebitAmount,
|
||||
separator: nonBreakingSpace,
|
||||
),
|
||||
total: formatMoneyUi(context, controller.aggregateDebitAmount),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -19,7 +19,10 @@ class UploadHistorySection extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ChangeNotifierProxyProvider<PaymentsProvider, RecentPaymentsController>(
|
||||
return ChangeNotifierProxyProvider<
|
||||
PaymentsProvider,
|
||||
RecentPaymentsController
|
||||
>(
|
||||
create: (_) => RecentPaymentsController(),
|
||||
update: (_, payments, controller) => controller!..update(payments),
|
||||
child: const _RecentPaymentsView(),
|
||||
|
||||
@@ -8,7 +8,6 @@ import 'package:pweb/pages/dashboard/payouts/summary/row.dart';
|
||||
|
||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||
|
||||
|
||||
class PaymentFeeRow extends StatelessWidget {
|
||||
const PaymentFeeRow({super.key});
|
||||
|
||||
@@ -19,7 +18,7 @@ class PaymentFeeRow extends StatelessWidget {
|
||||
final l10 = AppLocalizations.of(context)!;
|
||||
return PaymentSummaryRow(
|
||||
labelFactory: l10.fee,
|
||||
asset: fee,
|
||||
money: fee,
|
||||
value: fee == null ? l10.noFee : null,
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
);
|
||||
|
||||
@@ -8,7 +8,6 @@ import 'package:pweb/pages/dashboard/payouts/summary/row.dart';
|
||||
|
||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||
|
||||
|
||||
class PaymentRecipientReceivesRow extends StatelessWidget {
|
||||
const PaymentRecipientReceivesRow({super.key});
|
||||
|
||||
@@ -16,7 +15,7 @@ class PaymentRecipientReceivesRow extends StatelessWidget {
|
||||
Widget build(BuildContext context) => Consumer<QuotationProvider>(
|
||||
builder: (context, provider, _) => PaymentSummaryRow(
|
||||
labelFactory: AppLocalizations.of(context)!.recipientWillReceive,
|
||||
asset: provider.recipientGets,
|
||||
money: provider.recipientGets,
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
);
|
||||
|
||||
@@ -1,27 +1,25 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:pshared/models/asset.dart';
|
||||
import 'package:money2/money2.dart';
|
||||
|
||||
import 'package:pweb/utils/money_display.dart';
|
||||
|
||||
|
||||
class PaymentSummaryRow extends StatelessWidget {
|
||||
final String Function(String) labelFactory;
|
||||
final Asset? asset;
|
||||
final Money? money;
|
||||
final String? value;
|
||||
final TextStyle? style;
|
||||
|
||||
const PaymentSummaryRow({
|
||||
super.key,
|
||||
required this.labelFactory,
|
||||
required this.asset,
|
||||
required this.money,
|
||||
this.value,
|
||||
this.style,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final formatted = value ?? formatAssetUi(context, asset);
|
||||
final formatted = value ?? formatMoneyUi(context, money);
|
||||
return Text(labelFactory(formatted), style: style);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ import 'package:pweb/pages/dashboard/payouts/summary/row.dart';
|
||||
|
||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||
|
||||
|
||||
class PaymentTotalRow extends StatelessWidget {
|
||||
const PaymentTotalRow({super.key});
|
||||
|
||||
@@ -16,8 +15,10 @@ class PaymentTotalRow extends StatelessWidget {
|
||||
Widget build(BuildContext context) => Consumer<QuotationProvider>(
|
||||
builder: (context, provider, _) => PaymentSummaryRow(
|
||||
labelFactory: AppLocalizations.of(context)!.total,
|
||||
asset: provider.total,
|
||||
style: Theme.of(context).textTheme.titleMedium!.copyWith(fontWeight: FontWeight.w600),
|
||||
money: provider.total,
|
||||
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
Reference in New Issue
Block a user