Merge pull request 'Billing docs improvement + build opt' (#696) from docs-693 into main
All checks were successful
ci/woodpecker/push/billing_documents Pipeline was successful
ci/woodpecker/push/bff Pipeline was successful
ci/woodpecker/push/billing_fees Pipeline was successful
ci/woodpecker/push/callbacks Pipeline was successful
ci/woodpecker/push/discovery Pipeline was successful
ci/woodpecker/push/fx_ingestor Pipeline was successful
ci/woodpecker/push/frontend Pipeline was successful
ci/woodpecker/push/fx_oracle Pipeline was successful
ci/woodpecker/push/gateway_chain Pipeline was successful
ci/woodpecker/push/gateway_mntx Pipeline was successful
ci/woodpecker/push/gateway_tgsettle Pipeline was successful
ci/woodpecker/push/gateway_tron Pipeline was successful
ci/woodpecker/push/ledger Pipeline was successful
ci/woodpecker/push/notification Pipeline was successful
ci/woodpecker/push/payments_methods Pipeline was successful
ci/woodpecker/push/payments_orchestrator Pipeline was successful
ci/woodpecker/push/payments_quotation Pipeline was successful

Reviewed-on: #696
This commit was merged in pull request #696.
This commit is contained in:
2026-03-06 15:20:31 +00:00
79 changed files with 597 additions and 503 deletions

View File

@@ -1,2 +1,18 @@
ci/dev/mongo.key*
# VCS / editor files
.git
.vscode
.DS_Store
**/.DS_Store
# Local caches and temporary artifacts
.cache
.gocache
**/.gocache
**/tmp/
**/tmp/**
# Frontend local build artifacts (rebuilt in Docker)
frontend/**/.dart_tool
frontend/**/build

View File

@@ -20,7 +20,7 @@ BACKEND_SERVICES := \
dev-tron-gateway-vault-agent \
dev-tron-gateway \
dev-aurora-gateway \
dev-tgsettle-gateway \
dev-chsettle-gateway \
dev-notification \
dev-callbacks-vault-agent \
dev-callbacks \
@@ -61,7 +61,7 @@ help:
@echo " make build-core Build core services (discovery, ledger, fees, documents)"
@echo " make build-fx Build FX services (oracle, ingestor)"
@echo " make build-payments Build payment orchestrator"
@echo " make build-gateways Build gateway services (chain, tron, aurora, tgsettle)"
@echo " make build-gateways Build gateway services (chain, tron, aurora, chsettle)"
@echo " make build-api Build API services (notification, callbacks, bff)"
@echo " make build-frontend Build Flutter web frontend"
@echo ""
@@ -247,7 +247,7 @@ services-up:
dev-chain-gateway \
dev-tron-gateway \
dev-aurora-gateway \
dev-tgsettle-gateway \
dev-chsettle-gateway \
dev-notification \
dev-callbacks \
dev-bff \
@@ -292,7 +292,7 @@ list-services:
@echo " - dev-chain-gateway :50070, :9404 (EVM Blockchain Gateway)"
@echo " - dev-tron-gateway :50071, :9408 (TRON Blockchain Gateway)"
@echo " - dev-aurora-gateway :50075, :9405, :8084 (Card Payouts Simulator)"
@echo " - dev-tgsettle-gateway :50080, :9406 (Telegram Settlements)"
@echo " - dev-chsettle-gateway :50080, :9406 (Chimera Settlements Simulator)"
@echo " - dev-notification :8081 (Notifications)"
@echo " - dev-callbacks :9420 (Webhook Callbacks)"
@echo " - dev-bff :8080 (Backend for Frontend)"
@@ -322,7 +322,7 @@ build-payments:
build-gateways:
@echo "$(GREEN)Building gateway services...$(NC)"
@$(COMPOSE) build dev-chain-gateway dev-tron-gateway dev-aurora-gateway dev-tgsettle-gateway
@$(COMPOSE) build dev-chain-gateway dev-tron-gateway dev-aurora-gateway dev-chsettle-gateway
build-api:
@echo "$(GREEN)Building API services...$(NC)"

View File

@@ -25,14 +25,15 @@ Financial services platform providing payment orchestration, ledger accounting,
| Gateway Chain | `api/gateway/chain/` | EVM blockchain gateway |
| Gateway TRON | `api/gateway/tron/` | TRON blockchain gateway |
| Gateway Aurora | `api/gateway/aurora/` | Card payouts simulator |
| Gateway ChimeraSettle | `api/gateway/chsettle/` | Dummy settlement simulator (fast/slow/success/fail/stuck) |
| Gateway MNTX | `api/gateway/mntx/` | Card payouts |
| Gateway TGSettle | `api/gateway/tgsettle/` | Telegram settlements with MNTX |
| Gateway TGSettle (legacy) | `api/gateway/tgsettle/` | Legacy Telegram settlement gateway (not used in dev compose) |
| Notification | `api/notification/` | Notifications |
| BFF | `api/edge/bff/` | Backend for frontend |
| Callbacks | `api/edge/callbacks/` | Webhook callbacks delivery |
| Frontend | `frontend/pweb/` | Flutter web UI |
Gateway note: current dev compose workflows (`make services-up`, `make build-gateways`) use Aurora for card-payout flows (`chain`, `tron`, `aurora`, `tgsettle`). The MNTX gateway codebase is retained separately for Monetix-specific integration.
Gateway note: current dev compose workflows (`make services-up`, `make build-gateways`) use (`chain`, `tron`, `aurora`, `chsettle`). ChimeraSettle is the settlement simulator for test flows; it supports deterministic behavior routing via explicit scenario override and amount buckets. TGSettle remains in-repo as legacy code and is not started by default in dev compose.
## Prerequisites
@@ -87,7 +88,7 @@ make list-services # Show service names, ports, and descriptions
make build-core # discovery, ledger, fees, documents
make build-fx # oracle, ingestor
make build-payments # orchestrator, quotation, methods
make build-gateways # chain, tron, aurora, tgsettle
make build-gateways # chain, tron, aurora, chsettle
make build-api # notification, callbacks, bff
make build-frontend # Flutter web UI
```

View File

@@ -24,8 +24,6 @@ database:
documents:
issuer:
legal_name: "Sendico Ltd"
legal_address: "12 Market Street, London, UK"
logo_path: "assets/logo.png"
templates:
acceptance_path: "templates/acceptance.tpl"

View File

@@ -24,8 +24,6 @@ database:
documents:
issuer:
legal_name: "Sendico Ltd"
legal_address: "12 Market Street, London, UK"
logo_path: "/app/assets/logo.png"
templates:
acceptance_path: "/app/templates/acceptance.tpl"

View File

@@ -8,14 +8,14 @@ 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.96.3
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
github.com/tech/sendico/pkg v0.1.0
go.mongodb.org/mongo-driver/v2 v2.5.0
go.uber.org/zap v1.27.1
google.golang.org/grpc v1.79.1
google.golang.org/grpc v1.79.2
gopkg.in/yaml.v3 v3.0.1
)
@@ -25,7 +25,7 @@ require (
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.19 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.19 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.5 // indirect
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.19 // indirect
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.20 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.6 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.11 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.19 // indirect

View File

@@ -20,8 +20,8 @@ github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.19 h1:AWeJMk33GTBf6J20XJ
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.19/go.mod h1:+GWrYoaAsV7/4pNHpwh1kiNLXkKaSoppxQq9lbH8Ejw=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.5 h1:clHU5fm//kWS1C2HgtgWxfQbFbx4b6rx+5jzhgX9HrI=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.5/go.mod h1:O3h0IK87yXci+kg6flUKzJnWeziQUKciKrLjcatSNcY=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.19 h1:3Y4oma5TiV7tT9wa8zRcdoXwZkGz9Q/wxbEUK7cMuAM=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.19/go.mod h1:V1K+TeJVD5JOk3D9e5tsX2KUdL7BlB+FV6cBhdobN8c=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.20 h1:qi3e/dmpdONhj1RyIZdi6DKKpDXS5Lb8ftr3p7cyHJc=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.20/go.mod h1:V1K+TeJVD5JOk3D9e5tsX2KUdL7BlB+FV6cBhdobN8c=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.6 h1:XAq62tBTJP/85lFD5oqOOe7YYgWxY9LvWq8plyDvDVg=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.6/go.mod h1:x0nZssQ3qZSnIcePWLvcoFisRXJzcTVvYpAAdYX8+GI=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.11 h1:BYf7XNsJMzl4mObARUBUib+j2tf0U//JAAtTnYqvqCw=
@@ -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.96.3 h1:+d0SsTvxtIJt4tSJ6wr+jrxEMDa6XeupjRv8H7Qitkk=
github.com/aws/aws-sdk-go-v2/service/s3 v1.96.3/go.mod h1:ROUNFvFWPwBlOu687WJNQ9cPvd2ccpFrnCiA1YGz50o=
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=
@@ -260,8 +260,8 @@ gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 h1:ggcbiqK8WWh6l1dnltU4BgWGIGo+EVYxCaAPih/zQXQ=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
google.golang.org/grpc v1.79.1 h1:zGhSi45ODB9/p3VAawt9a+O/MULLl9dpizzNNpq7flY=
google.golang.org/grpc v1.79.1/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
google.golang.org/grpc v1.79.2 h1:fRMD94s2tITpyJGtBBn7MkMseNpOZU8ZxgC3MMBaXRU=
google.golang.org/grpc v1.79.2/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

View File

@@ -0,0 +1,116 @@
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, Hong Kong"
)
const (
PDFTitleActOfAcceptance = "Act of Acceptance"
DocumentIntegrityHashPrefix = "Document integrity hash: "
)
// AcceptanceTemplateContent contains all static copy used by the acceptance act template.
type AcceptanceTemplateContent struct {
Title string
Subtitle string
MetaDateLabel string
MetaActNumberLabel string
SectionParties string
PartiesIntro string
PartyExecutorLabel string
PartyStatusLabel string
PartyStatusValue string
SectionBasis string
BasisLine1 string
BasisLine2 string
SectionServicesRendered string
ServicesRenderedLine1 string
ServicesRenderedLine2 string
SectionRemuneration string
RemunerationHeaderDesc string
RemunerationHeaderAmount string
RemunerationServicesDesc string
SectionConfirmation string
ConfirmationLine1 string
ConfirmationLine2 string
ConfirmationPaymentLine1 string
ConfirmationPaymentLine2 string
SectionSignatures string
SignatureCustomerLine string
SignatureExecutorLine string
}
var AcceptanceTemplate = AcceptanceTemplateContent{
Title: "ACT OF ACCEPTANCE OF SERVICES",
Subtitle: "under the Public Offer Agreement",
MetaDateLabel: "Date",
MetaActNumberLabel: "Act No",
SectionParties: "PARTIES",
PartiesIntro: "This Act is made between the following Parties.",
PartyExecutorLabel: "Executor",
PartyStatusLabel: "Status",
PartyStatusValue: "Individual",
SectionBasis: "BASIS",
BasisLine1: "This Act is issued pursuant to the Public Offer Agreement",
BasisLine2: "accepted by the Executor by joining the offer.",
SectionServicesRendered: "SERVICES RENDERED",
ServicesRenderedLine1: "The Executor has rendered services to the Customer",
ServicesRenderedLine2: "in accordance with the terms of the Public Offer Agreement.",
SectionRemuneration: "REMUNERATION",
RemunerationHeaderDesc: "Description",
RemunerationHeaderAmount: "Amount",
RemunerationServicesDesc: "Services rendered under the Public Offer Agreement",
SectionConfirmation: "CONFIRMATION",
ConfirmationLine1: "The Customer confirms that the services were rendered properly",
ConfirmationLine2: "and accepted without any claims.",
ConfirmationPaymentLine1: "The remuneration for the services was paid to the Executor",
ConfirmationPaymentLine2: "using the bank card details provided by the Executor.",
SectionSignatures: "SIGNATURES",
SignatureCustomerLine: "Customer ___________________________",
SignatureExecutorLine: "Executor ___________________________",
}
// OperationDocumentContent contains all static copy for operation documents.
type OperationDocumentContent struct {
Title string
Subtitle 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: "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",
}

View File

@@ -3,18 +3,24 @@ package documents
import (
"strings"
"github.com/tech/sendico/billing/documents/internal/content"
"github.com/tech/sendico/billing/documents/internal/docstore"
"github.com/tech/sendico/billing/documents/renderer"
)
// Config holds document service settings loaded from YAML.
type Config struct {
Issuer renderer.Issuer `yaml:"issuer"`
Issuer IssuerConfig `yaml:"issuer"`
Templates TemplateConfig `yaml:"templates"`
Protection ProtectionConfig `yaml:"protection"`
Storage docstore.Config `yaml:"storage"`
}
// IssuerConfig defines issuer settings that are environment-specific.
type IssuerConfig struct {
LogoPath string `yaml:"logo_path"`
}
// TemplateConfig defines document template locations.
type TemplateConfig struct {
AcceptancePath string `yaml:"acceptance_path"`
@@ -25,6 +31,14 @@ type ProtectionConfig struct {
OwnerPassword string `yaml:"owner_password"`
}
func (c Config) IssuerDetails() renderer.Issuer {
return renderer.Issuer{
LegalName: content.IssuerLegalName,
LegalAddress: content.IssuerLegalAddress,
LogoPath: c.Issuer.LogoPath,
}
}
func (c Config) AcceptanceTemplatePath() string {
if strings.TrimSpace(c.Templates.AcceptancePath) == "" {
return "templates/acceptance.tpl"

View File

@@ -7,7 +7,6 @@ import (
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
documentsv1 "github.com/tech/sendico/pkg/proto/billing/documents/v1"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
@@ -17,7 +16,6 @@ var (
requestsTotal *prometheus.CounterVec
requestLatency *prometheus.HistogramVec
batchSize prometheus.Histogram
documentBytes *prometheus.HistogramVec
)
@@ -44,16 +42,6 @@ func initMetrics() {
[]string{"call", "status", "doc_type"},
)
batchSize = promauto.NewHistogram(
prometheus.HistogramOpts{
Namespace: "billing",
Subsystem: "documents",
Name: "batch_size",
Help: "Number of payment references in batch resolution requests.",
Buckets: []float64{0, 1, 2, 5, 10, 20, 50, 100, 250, 500},
},
)
documentBytes = promauto.NewHistogramVec(
prometheus.HistogramOpts{
Namespace: "billing",
@@ -67,18 +55,14 @@ func initMetrics() {
})
}
func observeRequest(call string, docType documentsv1.DocumentType, statusLabel string, took time.Duration) {
typeLabel := docTypeLabel(docType)
requestsTotal.WithLabelValues(call, statusLabel, typeLabel).Inc()
requestLatency.WithLabelValues(call, statusLabel, typeLabel).Observe(took.Seconds())
func observeRequest(call string, documentKind, statusLabel string, took time.Duration) {
kind := docKindLabel(documentKind)
requestsTotal.WithLabelValues(call, statusLabel, kind).Inc()
requestLatency.WithLabelValues(call, statusLabel, kind).Observe(took.Seconds())
}
func observeBatchSize(size int) {
batchSize.Observe(float64(size))
}
func observeDocumentBytes(docType documentsv1.DocumentType, size int) {
documentBytes.WithLabelValues(docTypeLabel(docType)).Observe(float64(size))
func observeDocumentBytes(documentKind string, size int) {
documentBytes.WithLabelValues(docKindLabel(documentKind)).Observe(float64(size))
}
func statusFromError(err error) string {
@@ -100,10 +84,10 @@ func statusFromError(err error) string {
return strings.ToLower(code.String())
}
func docTypeLabel(docType documentsv1.DocumentType) string {
label := docType.String()
func docKindLabel(documentKind string) string {
label := strings.TrimSpace(documentKind)
if label == "" {
return "DOCUMENT_TYPE_UNSPECIFIED"
return "operation"
}
return label

View File

@@ -5,11 +5,11 @@ import (
"crypto/sha256"
"encoding/hex"
"fmt"
"path/filepath"
"strings"
"time"
"github.com/tech/sendico/billing/documents/internal/appversion"
"github.com/tech/sendico/billing/documents/internal/content"
"github.com/tech/sendico/billing/documents/internal/docstore"
"github.com/tech/sendico/billing/documents/renderer"
"github.com/tech/sendico/billing/documents/storage"
@@ -145,94 +145,6 @@ func (s *Service) Shutdown() {
}
}
func (s *Service) BatchResolveDocuments(ctx context.Context, req *documentsv1.BatchResolveDocumentsRequest) (resp *documentsv1.BatchResolveDocumentsResponse, err error) {
start := time.Now()
paymentRefs := 0
if req != nil {
paymentRefs = len(req.GetPaymentRefs())
}
logger := s.logger.With(zap.Int("payment_refs", paymentRefs))
defer func() {
statusLabel := statusFromError(err)
observeRequest("batch_resolve", documentsv1.DocumentType_DOCUMENT_TYPE_UNSPECIFIED, statusLabel, time.Since(start))
observeBatchSize(paymentRefs)
itemsCount := 0
if resp != nil {
itemsCount = len(resp.GetItems())
}
fields := []zap.Field{
zap.String("status", statusLabel),
zap.Duration("duration", time.Since(start)),
zap.Int("items", itemsCount),
}
if err != nil {
logger.Warn("BatchResolveDocuments failed", append(fields, zap.Error(err))...)
return
}
logger.Info("BatchResolveDocuments finished", fields...)
}()
_ = ctx
err = status.Error(codes.Unimplemented, "payment-level document flow removed; use GetOperationDocument")
return nil, err
}
func (s *Service) GetDocument(ctx context.Context, req *documentsv1.GetDocumentRequest) (resp *documentsv1.GetDocumentResponse, err error) {
start := time.Now()
docType := documentsv1.DocumentType_DOCUMENT_TYPE_UNSPECIFIED
paymentRef := ""
if req != nil {
docType = req.GetType()
paymentRef = strings.TrimSpace(req.GetPaymentRef())
}
logger := s.logger.With(
zap.String("payment_ref", paymentRef),
zap.String("document_type", docTypeLabel(docType)),
)
defer func() {
statusLabel := statusFromError(err)
observeRequest("get_document", docType, statusLabel, time.Since(start))
if resp != nil {
observeDocumentBytes(docType, len(resp.GetContent()))
}
contentBytes := 0
if resp != nil {
contentBytes = len(resp.GetContent())
}
fields := []zap.Field{
zap.String("status", statusLabel),
zap.Duration("duration", time.Since(start)),
zap.Int("content_bytes", contentBytes),
}
if err != nil {
logger.Warn("GetDocument failed", append(fields, zap.Error(err))...)
return
}
logger.Info("GetDocument finished", fields...)
}()
_ = ctx
err = status.Error(codes.Unimplemented, "payment-level document flow removed; use GetOperationDocument")
return nil, err
}
func (s *Service) GetOperationDocument(_ context.Context, req *documentsv1.GetOperationDocumentRequest) (resp *documentsv1.GetDocumentResponse, err error) {
start := time.Now()
organizationRef := ""
@@ -253,11 +165,10 @@ func (s *Service) GetOperationDocument(_ context.Context, req *documentsv1.GetOp
defer func() {
statusLabel := statusFromError(err)
docType := documentsv1.DocumentType_DOCUMENT_TYPE_UNSPECIFIED
observeRequest("get_operation_document", docType, statusLabel, time.Since(start))
observeRequest("get_operation_document", "operation", statusLabel, time.Since(start))
if resp != nil {
observeDocumentBytes(docType, len(resp.GetContent()))
observeDocumentBytes("operation", len(resp.GetContent()))
}
contentBytes := 0
@@ -342,12 +253,6 @@ func (e serviceError) Error() string {
return string(e)
}
var (
errStorageUnavailable = serviceError("documents: storage not initialised")
errDocStoreUnavailable = serviceError("documents: document store not initialised")
errTemplateUnavailable = serviceError("documents: template renderer not initialised")
)
func (s *Service) generateActPDF(snapshot model.ActSnapshot) ([]byte, string, error) {
blocks, err := s.template.Render(snapshot)
if err != nil {
@@ -363,7 +268,7 @@ func (s *Service) generateOperationPDF(snapshot operationSnapshot) ([]byte, stri
func (s *Service) renderPDFWithIntegrity(blocks []renderer.Block) ([]byte, string, error) {
generated := renderer.Renderer{
Issuer: s.config.Issuer,
Issuer: s.config.IssuerDetails(),
OwnerPassword: s.config.Protection.OwnerPassword,
}
@@ -427,39 +332,41 @@ func operationSnapshotFromRequest(req *documentsv1.GetOperationDocumentRequest)
}
func buildOperationBlocks(snapshot operationSnapshot) []renderer.Block {
documentCopy := content.OperationDocument
rows := [][]string{
{"Organization", snapshot.OrganizationRef},
{"Gateway Service", snapshot.GatewayService},
{"Operation Ref", snapshot.OperationRef},
{"Payment Ref", safeValue(snapshot.PaymentRef)},
{"Code", safeValue(snapshot.OperationCode)},
{"State", safeValue(snapshot.OperationState)},
{"Label", safeValue(snapshot.OperationLabel)},
{"Started At (UTC)", formatSnapshotTime(snapshot.StartedAt)},
{"Completed At (UTC)", formatSnapshotTime(snapshot.CompletedAt)},
{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{"Amount", strings.TrimSpace(strings.TrimSpace(snapshot.Amount) + " " + strings.TrimSpace(snapshot.Currency))})
rows = append(rows, []string{documentCopy.RowAmount, strings.TrimSpace(strings.TrimSpace(snapshot.Amount) + " " + strings.TrimSpace(snapshot.Currency))})
}
blocks := []renderer.Block{
{
Tag: renderer.TagTitle,
Lines: []string{"OPERATION BILLING DOCUMENT"},
Lines: []string{documentCopy.Title},
},
{
Tag: renderer.TagSubtitle,
Lines: []string{"Gateway operation statement"},
Lines: []string{documentCopy.Subtitle},
},
{
Tag: renderer.TagMeta,
Lines: []string{
"Document Type: Operation",
documentCopy.MetaDocumentType,
},
},
{
Tag: renderer.TagSection,
Lines: []string{"OPERATION DETAILS"},
Lines: []string{documentCopy.SectionOperation},
},
{
Tag: renderer.TagKV,
@@ -469,12 +376,12 @@ func buildOperationBlocks(snapshot operationSnapshot) []renderer.Block {
if snapshot.FailureCode != "" || snapshot.FailureReason != "" {
blocks = append(blocks,
renderer.Block{Tag: renderer.TagSection, Lines: []string{"FAILURE DETAILS"}},
renderer.Block{Tag: renderer.TagSection, Lines: []string{documentCopy.SectionFailure}},
renderer.Block{
Tag: renderer.TagKV,
Rows: [][]string{
{"Failure Code", safeValue(snapshot.FailureCode)},
{"Failure Reason", safeValue(snapshot.FailureReason)},
{documentCopy.RowFailureCode, safeValue(snapshot.FailureCode)},
{documentCopy.RowFailureReason, safeValue(snapshot.FailureReason)},
},
},
)
@@ -485,7 +392,7 @@ func buildOperationBlocks(snapshot operationSnapshot) []renderer.Block {
func formatSnapshotTime(value time.Time) string {
if value.IsZero() {
return "n/a"
return content.OperationDocument.MissingValuePlaceholder
}
return value.UTC().Format(time.RFC3339)
@@ -494,7 +401,7 @@ func formatSnapshotTime(value time.Time) string {
func safeValue(value string) string {
trimmed := strings.TrimSpace(value)
if trimmed == "" {
return "n/a"
return content.OperationDocument.MissingValuePlaceholder
}
return trimmed
@@ -535,50 +442,3 @@ func sanitizeFilenameComponent(value string) string {
return strings.Trim(b.String(), "_")
}
func toProtoTypes(types []model.DocumentType) []documentsv1.DocumentType {
if len(types) == 0 {
return nil
}
result := make([]documentsv1.DocumentType, 0, len(types))
for _, t := range types {
result = append(result, t.Proto())
}
return result
}
func documentStoragePath(paymentRef string, docType documentsv1.DocumentType) string {
suffix := "document.pdf"
switch docType {
case documentsv1.DocumentType_DOCUMENT_TYPE_ACT:
suffix = "act.pdf"
case documentsv1.DocumentType_DOCUMENT_TYPE_INVOICE:
suffix = "invoice.pdf"
case documentsv1.DocumentType_DOCUMENT_TYPE_RECEIPT:
suffix = "receipt.pdf"
case documentsv1.DocumentType_DOCUMENT_TYPE_UNSPECIFIED:
// default suffix used
}
return filepath.ToSlash(filepath.Join("documents", paymentRef, suffix))
}
func documentFilename(docType documentsv1.DocumentType, paymentRef string) string {
name := "document"
switch docType {
case documentsv1.DocumentType_DOCUMENT_TYPE_ACT:
name = "act"
case documentsv1.DocumentType_DOCUMENT_TYPE_INVOICE:
name = "invoice"
case documentsv1.DocumentType_DOCUMENT_TYPE_RECEIPT:
name = "receipt"
case documentsv1.DocumentType_DOCUMENT_TYPE_UNSPECIFIED:
// default name used
}
return fmt.Sprintf("%s_%s.pdf", name, paymentRef)
}

View File

@@ -7,6 +7,7 @@ import (
"time"
"github.com/shopspring/decimal"
"github.com/tech/sendico/billing/documents/internal/content"
"github.com/tech/sendico/billing/documents/renderer"
"github.com/tech/sendico/billing/documents/storage"
"github.com/tech/sendico/billing/documents/storage/model"
@@ -59,10 +60,6 @@ type memDocStore struct {
loadCount int
}
func newMemDocStore() *memDocStore {
return &memDocStore{data: map[string][]byte{}}
}
func (m *memDocStore) Save(_ context.Context, key string, data []byte) error {
m.saveCount++
copyData := make([]byte, len(data))
@@ -112,15 +109,7 @@ func TestGenerateActPDF_IdempotentAndHashed(t *testing.T) {
},
}
cfg := Config{
Issuer: renderer.Issuer{
LegalName: "Sendico Ltd",
LegalAddress: "12 Market Street, London, UK",
},
}
svc := NewService(zap.NewNop(), nil, nil,
WithConfig(cfg),
WithTemplateRenderer(tmpl),
)
@@ -164,7 +153,7 @@ func TestGenerateActPDF_IdempotentAndHashed(t *testing.T) {
}
func extractFooterHash(pdf []byte) string {
prefix := []byte("Document integrity hash: ")
prefix := []byte(content.DocumentIntegrityHashPrefix)
idx := bytes.Index(pdf, prefix)
if idx == -1 {
@@ -191,11 +180,7 @@ func isHexDigit(b byte) bool {
}
func TestGetOperationDocument_GeneratesPDF(t *testing.T) {
svc := NewService(zap.NewNop(), nil, nil, WithConfig(Config{
Issuer: renderer.Issuer{
LegalName: "Sendico Ltd",
},
}))
svc := NewService(zap.NewNop(), nil, nil)
resp, err := svc.GetOperationDocument(context.Background(), &documentsv1.GetOperationDocumentRequest{
OrganizationRef: "org-1",

View File

@@ -9,6 +9,7 @@ import (
"time"
"github.com/shopspring/decimal"
"github.com/tech/sendico/billing/documents/internal/content"
"github.com/tech/sendico/billing/documents/renderer"
"github.com/tech/sendico/billing/documents/storage/model"
)
@@ -17,6 +18,11 @@ type templateRenderer struct {
tpl *template.Template
}
type acceptanceTemplateData struct {
model.ActSnapshot
Content content.AcceptanceTemplateContent
}
func newTemplateRenderer(path string) (*templateRenderer, error) {
data, err := os.ReadFile(path)
if err != nil {
@@ -38,7 +44,12 @@ func newTemplateRenderer(path string) (*templateRenderer, error) {
func (r *templateRenderer) Render(snapshot model.ActSnapshot) ([]renderer.Block, error) {
var buf bytes.Buffer
if err := r.tpl.Execute(&buf, snapshot); err != nil {
data := acceptanceTemplateData{
ActSnapshot: snapshot,
Content: content.AcceptanceTemplate,
}
if err := r.tpl.Execute(&buf, data); err != nil {
return nil, fmt.Errorf("execute template: %w", err)
}

View File

@@ -7,6 +7,7 @@ import (
"time"
"github.com/shopspring/decimal"
"github.com/tech/sendico/billing/documents/internal/content"
"github.com/tech/sendico/billing/documents/renderer"
"github.com/tech/sendico/billing/documents/storage/model"
)
@@ -42,7 +43,7 @@ func TestTemplateRenderer_Render(t *testing.T) {
t.Fatalf("expected title block")
}
if !slices.Contains(title.Lines, "ACT OF ACCEPTANCE OF SERVICES") {
if !slices.Contains(title.Lines, content.AcceptanceTemplate.Title) {
t.Fatalf("expected title content not found")
}
@@ -54,7 +55,7 @@ func TestTemplateRenderer_Render(t *testing.T) {
foundExecutor := false
for _, row := range kv.Rows {
if len(row) >= 2 && row[0] == "Executor" && row[1] == snapshot.ExecutorFullName {
if len(row) >= 2 && row[0] == content.AcceptanceTemplate.PartyExecutorLabel && row[1] == snapshot.ExecutorFullName {
foundExecutor = true
break

View File

@@ -6,6 +6,7 @@ import (
"strings"
"github.com/jung-kurt/gofpdf"
"github.com/tech/sendico/billing/documents/internal/content"
)
const (
@@ -28,7 +29,7 @@ func (r Renderer) Render(blocks []Block, footerHash string) ([]byte, error) {
pdf.SetAutoPageBreak(true, pageMarginBottom)
pdf.SetCompression(false)
pdf.SetAuthor(r.Issuer.LegalName, false)
pdf.SetTitle("Act of Acceptance", false)
pdf.SetTitle(content.PDFTitleActOfAcceptance, false)
owner := strings.TrimSpace(r.OwnerPassword)
if owner != "" {
@@ -39,7 +40,7 @@ func (r Renderer) Render(blocks []Block, footerHash string) ([]byte, error) {
pdf.SetY(-15)
pdf.SetFont("Helvetica", "", 8)
footer := "Document integrity hash: " + footerHash
footer := content.DocumentIntegrityHashPrefix + footerHash
pdf.CellFormat(0, 5, footer, "", 0, "L", false, 0, "")
})

View File

@@ -6,6 +6,8 @@ import (
"strings"
"testing"
"unicode/utf16"
"github.com/tech/sendico/billing/documents/internal/content"
)
func TestRenderer_RenderContainsText(t *testing.T) {
@@ -31,7 +33,7 @@ func TestRenderer_RenderContainsText(t *testing.T) {
t.Fatalf("expected PDF bytes")
}
checks := []string{"Sendico Ltd", "Jane Doe", "100 USD", "Document integrity hash"}
checks := []string{"Sendico Ltd", "Jane Doe", "100 USD", strings.TrimSpace(strings.TrimSuffix(content.DocumentIntegrityHashPrefix, ": "))}
for _, token := range checks {
if !containsPDFText(pdfBytes, token) {

View File

@@ -6,14 +6,13 @@ import (
"github.com/shopspring/decimal"
"github.com/tech/sendico/pkg/db/storable"
documentsv1 "github.com/tech/sendico/pkg/proto/billing/documents/v1"
)
const (
DocumentRecordsCollection = "document_records"
)
// DocumentType mirrors the protobuf enum but stores string names for Mongo compatibility.
// DocumentType represents document kinds cached in storage.
type DocumentType string
const (
@@ -23,24 +22,6 @@ const (
DocumentTypeReceipt DocumentType = "DOCUMENT_TYPE_RECEIPT"
)
// DocumentTypeFromProto converts a protobuf enum to the storage representation.
func DocumentTypeFromProto(t documentsv1.DocumentType) DocumentType {
if name, ok := documentsv1.DocumentType_name[int32(t)]; ok {
return DocumentType(name)
}
return DocumentTypeUnspecified
}
// Proto converts the storage representation to a protobuf enum.
func (t DocumentType) Proto() documentsv1.DocumentType {
if value, ok := documentsv1.DocumentType_value[string(t)]; ok {
return documentsv1.DocumentType(value)
}
return documentsv1.DocumentType_DOCUMENT_TYPE_UNSPECIFIED
}
// ActSnapshot captures the immutable data needed to generate an acceptance act.
type ActSnapshot struct {
PaymentID string `bson:"paymentId" json:"paymentId"`

View File

@@ -2,66 +2,66 @@
#title
ACT OF ACCEPTANCE OF SERVICES
{{ .Content.Title }}
#subtitle
under the Public Offer Agreement
{{ .Content.Subtitle }}
#meta
Date: {{ date .Date }}
Act No: {{ .PaymentID }}
{{ .Content.MetaDateLabel }}: {{ date .Date }}
{{ .Content.MetaActNumberLabel }}: {{ .PaymentID }}
#section
PARTIES
{{ .Content.SectionParties }}
#text
This Act is made between the following Parties.
{{ .Content.PartiesIntro }}
#kv
Executor | {{ .ExecutorFullName }}
Status | Individual
{{ .Content.PartyExecutorLabel }} | {{ .ExecutorFullName }}
{{ .Content.PartyStatusLabel }} | {{ .Content.PartyStatusValue }}
#section
BASIS
{{ .Content.SectionBasis }}
#text
This Act is issued pursuant to the Public Offer Agreement
accepted by the Executor by joining the offer.
{{ .Content.BasisLine1 }}
{{ .Content.BasisLine2 }}
#section
SERVICES RENDERED
{{ .Content.SectionServicesRendered }}
#text
The Executor has rendered services to the Customer
in accordance with the terms of the Public Offer Agreement.
{{ .Content.ServicesRenderedLine1 }}
{{ .Content.ServicesRenderedLine2 }}
#section
REMUNERATION
{{ .Content.SectionRemuneration }}
#table
Description | Amount
Services rendered under the Public Offer Agreement | {{ money .Amount .Currency }}
{{ .Content.RemunerationHeaderDesc }} | {{ .Content.RemunerationHeaderAmount }}
{{ .Content.RemunerationServicesDesc }} | {{ money .Amount .Currency }}
#section
CONFIRMATION
{{ .Content.SectionConfirmation }}
#text
The Customer confirms that the services were rendered properly
and accepted without any claims.
{{ .Content.ConfirmationLine1 }}
{{ .Content.ConfirmationLine2 }}
The remuneration for the services was paid to the Executor
using the bank card details provided by the Executor.
{{ .Content.ConfirmationPaymentLine1 }}
{{ .Content.ConfirmationPaymentLine2 }}
#section
SIGNATURES
{{ .Content.SectionSignatures }}
#sign
Customer ___________________________
{{ .Content.SignatureCustomerLine }}
Executor ___________________________
{{ .Content.SignatureExecutorLine }}

View File

@@ -10,7 +10,7 @@ require (
github.com/tech/sendico/fx/oracle v0.0.0
github.com/tech/sendico/pkg v0.1.0
go.uber.org/zap v1.27.1
google.golang.org/grpc v1.79.1
google.golang.org/grpc v1.79.2
gopkg.in/yaml.v3 v3.0.1
)

View File

@@ -210,8 +210,8 @@ gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 h1:ggcbiqK8WWh6l1dnltU4BgWGIGo+EVYxCaAPih/zQXQ=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
google.golang.org/grpc v1.79.1 h1:zGhSi45ODB9/p3VAawt9a+O/MULLl9dpizzNNpq7flY=
google.golang.org/grpc v1.79.1/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
google.golang.org/grpc v1.79.2 h1:fRMD94s2tITpyJGtBBn7MkMseNpOZU8ZxgC3MMBaXRU=
google.golang.org/grpc v1.79.2/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

View File

@@ -44,6 +44,6 @@ require (
golang.org/x/sys v0.41.0 // indirect
golang.org/x/text v0.34.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 // indirect
google.golang.org/grpc v1.79.1 // indirect
google.golang.org/grpc v1.79.2 // indirect
google.golang.org/protobuf v1.36.11 // indirect
)

View File

@@ -210,8 +210,8 @@ gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 h1:ggcbiqK8WWh6l1dnltU4BgWGIGo+EVYxCaAPih/zQXQ=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
google.golang.org/grpc v1.79.1 h1:zGhSi45ODB9/p3VAawt9a+O/MULLl9dpizzNNpq7flY=
google.golang.org/grpc v1.79.1/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
google.golang.org/grpc v1.79.2 h1:fRMD94s2tITpyJGtBBn7MkMseNpOZU8ZxgC3MMBaXRU=
google.golang.org/grpc v1.79.2/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

View File

@@ -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.96.3
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
@@ -38,7 +38,7 @@ require (
go.mongodb.org/mongo-driver/v2 v2.5.0
go.uber.org/zap v1.27.1
golang.org/x/net v0.51.0
google.golang.org/grpc v1.79.1
google.golang.org/grpc v1.79.2
google.golang.org/protobuf v1.36.11
gopkg.in/yaml.v3 v3.0.1
moul.io/chizap v1.0.3
@@ -59,7 +59,7 @@ require (
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.19 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.19 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.5 // indirect
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.19 // indirect
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.20 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.6 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.11 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.19 // indirect

View File

@@ -22,8 +22,8 @@ github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.19 h1:AWeJMk33GTBf6J20XJ
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.19/go.mod h1:+GWrYoaAsV7/4pNHpwh1kiNLXkKaSoppxQq9lbH8Ejw=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.5 h1:clHU5fm//kWS1C2HgtgWxfQbFbx4b6rx+5jzhgX9HrI=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.5/go.mod h1:O3h0IK87yXci+kg6flUKzJnWeziQUKciKrLjcatSNcY=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.19 h1:3Y4oma5TiV7tT9wa8zRcdoXwZkGz9Q/wxbEUK7cMuAM=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.19/go.mod h1:V1K+TeJVD5JOk3D9e5tsX2KUdL7BlB+FV6cBhdobN8c=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.20 h1:qi3e/dmpdONhj1RyIZdi6DKKpDXS5Lb8ftr3p7cyHJc=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.20/go.mod h1:V1K+TeJVD5JOk3D9e5tsX2KUdL7BlB+FV6cBhdobN8c=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.6 h1:XAq62tBTJP/85lFD5oqOOe7YYgWxY9LvWq8plyDvDVg=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.6/go.mod h1:x0nZssQ3qZSnIcePWLvcoFisRXJzcTVvYpAAdYX8+GI=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.11 h1:BYf7XNsJMzl4mObARUBUib+j2tf0U//JAAtTnYqvqCw=
@@ -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.96.3 h1:+d0SsTvxtIJt4tSJ6wr+jrxEMDa6XeupjRv8H7Qitkk=
github.com/aws/aws-sdk-go-v2/service/s3 v1.96.3/go.mod h1:ROUNFvFWPwBlOu687WJNQ9cPvd2ccpFrnCiA1YGz50o=
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=
@@ -403,8 +403,8 @@ google.golang.org/genproto/googleapis/api v0.0.0-20260226221140-a57be14db171 h1:
google.golang.org/genproto/googleapis/api v0.0.0-20260226221140-a57be14db171/go.mod h1:M5krXqk4GhBKvB596udGL3UyjL4I1+cTbK0orROM9ng=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 h1:ggcbiqK8WWh6l1dnltU4BgWGIGo+EVYxCaAPih/zQXQ=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
google.golang.org/grpc v1.79.1 h1:zGhSi45ODB9/p3VAawt9a+O/MULLl9dpizzNNpq7flY=
google.golang.org/grpc v1.79.1/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
google.golang.org/grpc v1.79.2 h1:fRMD94s2tITpyJGtBBn7MkMseNpOZU8ZxgC3MMBaXRU=
google.golang.org/grpc v1.79.2/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

View File

@@ -59,6 +59,6 @@ require (
golang.org/x/text v0.34.0 // indirect
golang.org/x/time v0.14.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 // indirect
google.golang.org/grpc v1.79.1 // indirect
google.golang.org/grpc v1.79.2 // indirect
google.golang.org/protobuf v1.36.11 // indirect
)

View File

@@ -245,8 +245,8 @@ gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 h1:ggcbiqK8WWh6l1dnltU4BgWGIGo+EVYxCaAPih/zQXQ=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
google.golang.org/grpc v1.79.1 h1:zGhSi45ODB9/p3VAawt9a+O/MULLl9dpizzNNpq7flY=
google.golang.org/grpc v1.79.1/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
google.golang.org/grpc v1.79.2 h1:fRMD94s2tITpyJGtBBn7MkMseNpOZU8ZxgC3MMBaXRU=
google.golang.org/grpc v1.79.2/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

View File

@@ -48,6 +48,6 @@ require (
golang.org/x/sys v0.41.0 // indirect
golang.org/x/text v0.34.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 // indirect
google.golang.org/grpc v1.79.1 // indirect
google.golang.org/grpc v1.79.2 // indirect
google.golang.org/protobuf v1.36.11 // indirect
)

View File

@@ -210,8 +210,8 @@ gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 h1:ggcbiqK8WWh6l1dnltU4BgWGIGo+EVYxCaAPih/zQXQ=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
google.golang.org/grpc v1.79.1 h1:zGhSi45ODB9/p3VAawt9a+O/MULLl9dpizzNNpq7flY=
google.golang.org/grpc v1.79.1/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
google.golang.org/grpc v1.79.2 h1:fRMD94s2tITpyJGtBBn7MkMseNpOZU8ZxgC3MMBaXRU=
google.golang.org/grpc v1.79.2/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

View File

@@ -13,7 +13,7 @@ require (
github.com/tech/sendico/pkg v0.1.0
go.mongodb.org/mongo-driver/v2 v2.5.0
go.uber.org/zap v1.27.1
google.golang.org/grpc v1.79.1
google.golang.org/grpc v1.79.2
google.golang.org/protobuf v1.36.11
gopkg.in/yaml.v3 v3.0.1
)

View File

@@ -210,8 +210,8 @@ gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 h1:ggcbiqK8WWh6l1dnltU4BgWGIGo+EVYxCaAPih/zQXQ=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
google.golang.org/grpc v1.79.1 h1:zGhSi45ODB9/p3VAawt9a+O/MULLl9dpizzNNpq7flY=
google.golang.org/grpc v1.79.1/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
google.golang.org/grpc v1.79.2 h1:fRMD94s2tITpyJGtBBn7MkMseNpOZU8ZxgC3MMBaXRU=
google.golang.org/grpc v1.79.2/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

View File

@@ -0,0 +1,101 @@
# ChimeraSettle Gateway (`chsettle`)
ChimeraSettle is a dummy settlement gateway for dev/test flows.
It simulates settlement outcomes deterministically, without real fund movement.
## What It Simulates
- Fast success
- Slow success
- Immediate failure
- Timeout-like failure
- Stuck pending/processing
- Retry-like progression before success
- Deterministic chaos bucket
## How Behavior Is Chosen
Selection order:
1. Explicit scenario override from request metadata:
- `chsettle_scenario`
- `scenario` (alias)
2. Amount bucket routing based on:
- `amount_minor % 1000`
3. Hash fallback using idempotency key (if amount cannot be parsed)
## Scenario Overrides
Accepted override values (aliases supported in code):
- `fast_success`
- `slow_success`
- `fail_immediate`
- `fail_timeout`
- `stuck_pending` (also `stuck`)
- `retry_then_success`
- `webhook_delayed_success`
- `slow_then_fail`
- `partial_progress_stuck`
- `chaos`
## Amount Bucket Map (`%1000`)
| Slot range | Scenario |
|---|---|
| `000-099` | `fast_success` |
| `100-199` | `slow_success` |
| `200-299` | `fail_immediate` |
| `300-399` | `fail_timeout` |
| `400-499` | `stuck_pending` |
| `500-599` | `retry_then_success` |
| `600-699` | `webhook_delayed_success` |
| `700-799` | `slow_then_fail` |
| `800-899` | `partial_progress_stuck` |
| `900-999` | `chaos` (deterministic by idempotency key) |
## Request Usage
### Through Connector `SubmitOperation`
Use operation param:
- `scenario` (optional)
or put it inside `metadata` as:
- `chsettle_scenario`
### Through Gateway `SubmitTransfer`
Set metadata key directly:
- `chsettle_scenario`
## Dev Environment
Compose service:
- `dev-chsettle-gateway`
Ports:
- gRPC: `50080`
- metrics: `9406`
Start only this gateway:
```bash
docker compose -f docker-compose.dev.yml --env-file .env.dev up -d dev-chsettle-gateway
```
## Logging
ChimeraSettle emits:
- `info` logs for scenario decisions and transition milestones
- `warn` logs for recoverable/non-fatal errors
- `error` logs for startup-fatal failures
- detailed `debug` logs for request execution tracing

View File

@@ -11,7 +11,7 @@ require (
github.com/tech/sendico/pkg v0.1.0
go.mongodb.org/mongo-driver/v2 v2.5.0
go.uber.org/zap v1.27.1
google.golang.org/grpc v1.79.1
google.golang.org/grpc v1.79.2
google.golang.org/protobuf v1.36.11
gopkg.in/yaml.v3 v3.0.1
)

View File

@@ -212,8 +212,8 @@ gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 h1:ggcbiqK8WWh6l1dnltU4BgWGIGo+EVYxCaAPih/zQXQ=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
google.golang.org/grpc v1.79.1 h1:zGhSi45ODB9/p3VAawt9a+O/MULLl9dpizzNNpq7flY=
google.golang.org/grpc v1.79.1/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
google.golang.org/grpc v1.79.2 h1:fRMD94s2tITpyJGtBBn7MkMseNpOZU8ZxgC3MMBaXRU=
google.golang.org/grpc v1.79.2/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

View File

@@ -51,6 +51,6 @@ require (
golang.org/x/sync v0.19.0 // indirect
golang.org/x/sys v0.41.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 // indirect
google.golang.org/grpc v1.79.1 // indirect
google.golang.org/grpc v1.79.2 // indirect
google.golang.org/protobuf v1.36.11 // indirect
)

View File

@@ -227,8 +227,8 @@ gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 h1:ggcbiqK8WWh6l1dnltU4BgWGIGo+EVYxCaAPih/zQXQ=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
google.golang.org/grpc v1.79.1 h1:zGhSi45ODB9/p3VAawt9a+O/MULLl9dpizzNNpq7flY=
google.golang.org/grpc v1.79.1/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
google.golang.org/grpc v1.79.2 h1:fRMD94s2tITpyJGtBBn7MkMseNpOZU8ZxgC3MMBaXRU=
google.golang.org/grpc v1.79.2/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

View File

@@ -11,7 +11,7 @@ require (
github.com/tech/sendico/pkg v0.1.0
go.mongodb.org/mongo-driver/v2 v2.5.0
go.uber.org/zap v1.27.1
google.golang.org/grpc v1.79.1
google.golang.org/grpc v1.79.2
google.golang.org/protobuf v1.36.11
gopkg.in/yaml.v3 v3.0.1
)

View File

@@ -212,8 +212,8 @@ gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 h1:ggcbiqK8WWh6l1dnltU4BgWGIGo+EVYxCaAPih/zQXQ=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
google.golang.org/grpc v1.79.1 h1:zGhSi45ODB9/p3VAawt9a+O/MULLl9dpizzNNpq7flY=
google.golang.org/grpc v1.79.1/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
google.golang.org/grpc v1.79.2 h1:fRMD94s2tITpyJGtBBn7MkMseNpOZU8ZxgC3MMBaXRU=
google.golang.org/grpc v1.79.2/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

View File

@@ -25,7 +25,7 @@ require (
github.com/tech/sendico/pkg v0.1.0
go.mongodb.org/mongo-driver/v2 v2.5.0
go.uber.org/zap v1.27.1
google.golang.org/grpc v1.79.1
google.golang.org/grpc v1.79.2
google.golang.org/protobuf v1.36.11
gopkg.in/yaml.v3 v3.0.1
)

View File

@@ -213,8 +213,8 @@ gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 h1:ggcbiqK8WWh6l1dnltU4BgWGIGo+EVYxCaAPih/zQXQ=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
google.golang.org/grpc v1.79.1 h1:zGhSi45ODB9/p3VAawt9a+O/MULLl9dpizzNNpq7flY=
google.golang.org/grpc v1.79.1/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
google.golang.org/grpc v1.79.2 h1:fRMD94s2tITpyJGtBBn7MkMseNpOZU8ZxgC3MMBaXRU=
google.golang.org/grpc v1.79.2/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

View File

@@ -19,7 +19,7 @@ require (
go.mongodb.org/mongo-driver/v2 v2.5.0
go.uber.org/zap v1.27.1
golang.org/x/crypto v0.48.0
google.golang.org/grpc v1.79.1
google.golang.org/grpc v1.79.2
google.golang.org/protobuf v1.36.11
)

View File

@@ -344,8 +344,8 @@ google.golang.org/genproto/googleapis/api v0.0.0-20251222181119-0a764e51fe1b h1:
google.golang.org/genproto/googleapis/api v0.0.0-20251222181119-0a764e51fe1b/go.mod h1:Xa7le7qx2vmqB/SzWUBa7KdMjpdpAHlh5QCSnjessQk=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 h1:ggcbiqK8WWh6l1dnltU4BgWGIGo+EVYxCaAPih/zQXQ=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
google.golang.org/grpc v1.79.1 h1:zGhSi45ODB9/p3VAawt9a+O/MULLl9dpizzNNpq7flY=
google.golang.org/grpc v1.79.1/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
google.golang.org/grpc v1.79.2 h1:fRMD94s2tITpyJGtBBn7MkMseNpOZU8ZxgC3MMBaXRU=
google.golang.org/grpc v1.79.2/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

View File

@@ -39,6 +39,7 @@ const (
ChainTransfers Type = "chain_transfers" // Represents chain transfers
ChainDeposits Type = "chain_deposits" // Represents chain deposits
Callbacks Type = "callbacks" // Represents webhook callback subscriptions
ChSettle Type = "chsettle_gateway" // Represents chimera settlement gateway
Notifications Type = "notifications" // Represents notifications sent to users
Organizations Type = "organizations" // Represents organizations in the system
Payments Type = "payments" // Represents payments service
@@ -66,7 +67,7 @@ func StringToSType(s string) (Type, error) {
ChainTransfers, ChainDeposits, Callbacks, MntxGateway, PaymentGateway, FXOracle, FeePlans, BillingDocuments, FilterProjects, Invitations, Invoices, Logo, Ledger,
LedgerAccounts, LedgerBalances, LedgerEntries, LedgerOutbox, LedgerParties, LedgerPlines, Notifications,
Organizations, Payments, PaymentRoutes, PaymentOrchestrator, PaymentMethods, Permissions, Policies, PolicyAssignements,
Recipients, RefreshTokens, Roles, Storage, Tenants, Workflows, Discovery:
Recipients, RefreshTokens, Roles, Storage, Tenants, Workflows, Discovery, ChSettle:
return Type(s), nil
default:
return "", merrors.InvalidArgument("invalid service type", s)

View File

@@ -5,94 +5,18 @@ package billing.documents.v1;
option go_package = "github.com/tech/sendico/pkg/proto/billing/documents/v1;documentsv1";
// ---------------------------
// ENUMS
// ---------------------------
// DocumentType defines supported accounting document kinds.
enum DocumentType {
DOCUMENT_TYPE_UNSPECIFIED = 0;
// Invoice issued for the payment
DOCUMENT_TYPE_INVOICE = 1;
// Service acceptance act (common in EU/RU accounting)
DOCUMENT_TYPE_ACT = 2;
// Simple receipt confirmation
DOCUMENT_TYPE_RECEIPT = 3;
}
// ---------------------------
// SERVICE
// ---------------------------
// DocumentService provides document metadata for payment lists
// and lazy document generation on demand.
// DocumentService provides operation-level document generation.
service DocumentService {
// BatchResolveDocuments is used by BFF when rendering
// a page of payments. This prevents N+1 calls by resolving
// document metadata for many payments in a single request.
rpc BatchResolveDocuments(BatchResolveDocumentsRequest)
returns (BatchResolveDocumentsResponse);
// GetDocument returns the actual PDF file.
// If the document was not generated before, the service
// generates it lazily, stores it, and returns it.
rpc GetDocument(GetDocumentRequest)
returns (GetDocumentResponse);
// GetOperationDocument returns a generated PDF file for
// a gateway operation snapshot provided by the caller.
rpc GetOperationDocument(GetOperationDocumentRequest)
returns (GetDocumentResponse);
}
// ---------------------------
// BATCH RESOLVE (for payment tables)
// ---------------------------
// BatchResolveDocumentsRequest contains a list of payment references
// for which document availability should be resolved.
message BatchResolveDocumentsRequest {
repeated string payment_refs = 1;
}
// DocumentMeta describes document availability for a single payment.
message DocumentMeta {
// Payment reference
string payment_ref = 1;
// Document types that are applicable for this payment
// based on business rules and payment snapshot.
repeated DocumentType available_types = 2;
// Document types that were already generated and stored.
// Other available types will be generated lazily when requested.
repeated DocumentType ready_types = 3;
}
// BatchResolveDocumentsResponse returns metadata for all requested payments.
message BatchResolveDocumentsResponse {
repeated DocumentMeta items = 1;
}
// ---------------------------
// GET DOCUMENT (lazy generation)
// ---------------------------
// GetDocumentRequest requests a specific document for a payment.
message GetDocumentRequest {
string payment_ref = 1;
// Type of document to retrieve (invoice, act, receipt, etc.)
DocumentType type = 2;
}
// GetDocumentResponse returns the generated PDF content.
message GetDocumentResponse {
// Raw PDF bytes

View File

@@ -16,7 +16,7 @@
- [x] Payments Orchestrator Service
- [x] Chain Gateway Service
- [x] MNTX Gateway Service
- [x] TGSettle Gateway Service
- [x] ChimeraSettle Gateway Service
- [x] Notification Service
- [x] BFF (Server) Service
- [x] Frontend (Flutter Web)
@@ -36,7 +36,7 @@
- [x] ci/dev/payments-orchestrator.dockerfile
- [x] ci/dev/chain-gateway.dockerfile
- [x] ci/dev/mntx-gateway.dockerfile
- [x] ci/dev/tgsettle-gateway.dockerfile
- [x] ci/dev/chsettle-gateway.dockerfile
- [x] ci/dev/notification.dockerfile
- [x] ci/dev/bff.dockerfile
- [x] ci/dev/frontend.dockerfile
@@ -93,7 +93,7 @@ Service Layer:
├─ dev-payments-orchestrator → [mongo, nats, ledger, billing-fees]
├─ dev-chain-gateway → [mongo, nats, discovery, vault]
├─ dev-mntx-gateway → [nats, discovery, vault]
├─ dev-tgsettle-gateway → [mongo, nats, discovery, vault]
├─ dev-chsettle-gateway → [mongo, nats, discovery, vault]
├─ dev-notification → [nats]
├─ dev-bff → [mongo, nats, ledger, payments, chain-gateway]
└─ dev-frontend → [bff]
@@ -120,7 +120,7 @@ Service Layer:
### 4. Gateways
- [ ] Chain gateway connects to Vault for keys
- [ ] MNTX gateway is reachable
- [ ] TGSettle gateway connects to Telegram
- [ ] ChimeraSettle gateway is reachable and scenario routing works
### 5. Frontend
- [ ] BFF serves API endpoints
@@ -129,8 +129,8 @@ Service Layer:
## 🐛 Known Considerations
1. **Vault Secrets**: Services using Vault (chain, mntx, tgsettle) need secrets populated
2. **External APIs**: MNTX and TGSettle need API credentials from Vault
1. **Vault Secrets**: Services using Vault (chain, mntx, chsettle) need secrets populated
2. **External APIs**: MNTX needs API credentials from Vault
3. **Blockchain**: Chain gateway needs blockchain node URLs and private keys
4. **Build Time**: Frontend build takes ~5-10 minutes (Flutter compile)
@@ -138,7 +138,7 @@ Service Layer:
- [ ] Populate Vault with blockchain keys for Chain Gateway
- [ ] Add MNTX API credentials to Vault
- [ ] Add TGSettle (Telegram) credentials to Vault
- [ ] Validate ChimeraSettle scenario overrides in test flows
- [ ] Seed test data into MongoDB
- [ ] Configure blockchain RPC endpoints
- [ ] Test full payment flow end-to-end

View File

@@ -11,7 +11,7 @@ Docker Compose + Makefile build system for local development.
**Services:**
- Discovery, Ledger, Billing Fees, Billing Documents, FX Oracle, Payments Orchestrator
- Chain Gateway, MNTX Gateway, TGSettle Gateway
- Chain Gateway, MNTX Gateway, ChimeraSettle Gateway
- FX Ingestor, Notification, BFF (Server), Callbacks, Frontend
## Quick Start
@@ -59,7 +59,7 @@ make status # Check service status
Examples:
- Blockchain private keys (Chain Gateway)
- External API keys (MNTX, TGSettle)
- External API keys (MNTX)
- Webhook signing secrets (Callbacks)
- Production-like secrets

View File

@@ -0,0 +1,36 @@
# Development Dockerfile for chsettle-gateway Service with Air hot reload
FROM golang:alpine AS builder
RUN apk add --no-cache bash git build-base protoc protobuf-dev && \
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest && \
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest && \
go install github.com/air-verse/air@latest
WORKDIR /src
COPY api/proto ./api/proto
COPY api/pkg ./api/pkg
COPY api/gateway/common ./api/gateway/common
COPY ci/scripts/proto/generate.sh ./ci/scripts/proto/
RUN bash ci/scripts/proto/generate.sh
# Runtime stage for development with Air
FROM golang:alpine
RUN apk add --no-cache bash git build-base && \
go install github.com/air-verse/air@latest
WORKDIR /src
# Copy generated proto and pkg from builder
COPY --from=builder /src/api/proto ./api/proto
COPY --from=builder /src/api/pkg ./api/pkg
COPY --from=builder /src/api/gateway/common ./api/gateway/common
# Source code will be mounted at runtime
WORKDIR /src/api/gateway/chsettle
EXPOSE 50080 9406
CMD ["air", "-c", ".air.toml", "--", "-config.file", "/app/config.yml", "-debug"]

View File

@@ -13,10 +13,7 @@ ENV GO111MODULE=on
ENV PATH="/go/bin:${PATH}"
WORKDIR /src
COPY . .
RUN apk add --no-cache bash git build-base protoc protobuf-dev \
&& go install google.golang.org/protobuf/cmd/protoc-gen-go@latest \
&& go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest \
&& bash ci/scripts/proto/generate.sh
RUN apk add --no-cache git build-base
WORKDIR /src/api/edge/bff
RUN --mount=type=cache,target=/root/.cache/go-build \
--mount=type=cache,target=/go/pkg/mod \

View File

@@ -13,10 +13,7 @@ ENV GO111MODULE=on
ENV PATH="/go/bin:${PATH}"
WORKDIR /src
COPY . .
RUN apk add --no-cache bash git build-base protoc protobuf-dev \
&& go install google.golang.org/protobuf/cmd/protoc-gen-go@latest \
&& go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest \
&& bash ci/scripts/proto/generate.sh
RUN apk add --no-cache git build-base
WORKDIR /src/api/billing/documents
RUN --mount=type=cache,target=/root/.cache/go-build \
--mount=type=cache,target=/go/pkg/mod \

View File

@@ -13,10 +13,7 @@ ENV GO111MODULE=on
ENV PATH="/go/bin:${PATH}"
WORKDIR /src
COPY . .
RUN apk add --no-cache bash git build-base protoc protobuf-dev \
&& go install google.golang.org/protobuf/cmd/protoc-gen-go@latest \
&& go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest \
&& bash ci/scripts/proto/generate.sh
RUN apk add --no-cache git build-base
WORKDIR /src/api/billing/fees
RUN --mount=type=cache,target=/root/.cache/go-build \
--mount=type=cache,target=/go/pkg/mod \

View File

@@ -13,10 +13,7 @@ ENV GO111MODULE=on
ENV PATH="/go/bin:${PATH}"
WORKDIR /src
COPY . .
RUN apk add --no-cache bash git build-base protoc protobuf-dev \
&& go install google.golang.org/protobuf/cmd/protoc-gen-go@latest \
&& go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest \
&& bash ci/scripts/proto/generate.sh
RUN apk add --no-cache git build-base
WORKDIR /src/api/edge/callbacks
RUN --mount=type=cache,target=/root/.cache/go-build \
--mount=type=cache,target=/go/pkg/mod \

View File

@@ -13,10 +13,7 @@ ENV GO111MODULE=on
ENV PATH="/go/bin:${PATH}"
WORKDIR /src
COPY . .
RUN apk add --no-cache bash git build-base protoc protobuf-dev \
&& go install google.golang.org/protobuf/cmd/protoc-gen-go@latest \
&& go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest \
&& bash ci/scripts/proto/generate.sh
RUN apk add --no-cache git build-base
WORKDIR /src/api/gateway/chain
RUN --mount=type=cache,target=/root/.cache/go-build \
--mount=type=cache,target=/go/pkg/mod \

View File

@@ -13,10 +13,7 @@ ENV GO111MODULE=on
ENV PATH="/go/bin:${PATH}"
WORKDIR /src
COPY . .
RUN apk add --no-cache bash git build-base protoc protobuf-dev \
&& go install google.golang.org/protobuf/cmd/protoc-gen-go@latest \
&& go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest \
&& bash ci/scripts/proto/generate.sh
RUN apk add --no-cache git build-base
WORKDIR /src/api/discovery
RUN --mount=type=cache,target=/root/.cache/go-build \
--mount=type=cache,target=/go/pkg/mod \

View File

@@ -13,10 +13,7 @@ ENV GO111MODULE=on
ENV PATH="/go/bin:${PATH}"
WORKDIR /src
COPY . .
RUN apk add --no-cache bash git build-base protoc protobuf-dev \
&& go install google.golang.org/protobuf/cmd/protoc-gen-go@latest \
&& go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest \
&& bash ci/scripts/proto/generate.sh
RUN apk add --no-cache git build-base
WORKDIR /src/api/fx/ingestor
RUN --mount=type=cache,target=/root/.cache/go-build \
--mount=type=cache,target=/go/pkg/mod \

View File

@@ -13,10 +13,7 @@ ENV GO111MODULE=on
ENV PATH="/go/bin:${PATH}"
WORKDIR /src
COPY . .
RUN apk add --no-cache bash git build-base protoc protobuf-dev \
&& go install google.golang.org/protobuf/cmd/protoc-gen-go@latest \
&& go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest \
&& bash ci/scripts/proto/generate.sh
RUN apk add --no-cache git build-base
WORKDIR /src/api/fx/oracle
RUN --mount=type=cache,target=/root/.cache/go-build \
--mount=type=cache,target=/go/pkg/mod \

View File

@@ -13,10 +13,7 @@ ENV GO111MODULE=on
ENV PATH="/go/bin:${PATH}"
WORKDIR /src
COPY . .
RUN apk add --no-cache bash git build-base protoc protobuf-dev \
&& go install google.golang.org/protobuf/cmd/protoc-gen-go@latest \
&& go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest \
&& bash ci/scripts/proto/generate.sh
RUN apk add --no-cache git build-base
WORKDIR /src/api/ledger
RUN --mount=type=cache,target=/root/.cache/go-build \
--mount=type=cache,target=/go/pkg/mod \

View File

@@ -13,10 +13,7 @@ ENV GO111MODULE=on
ENV PATH="/go/bin:${PATH}"
WORKDIR /src
COPY . .
RUN apk add --no-cache bash git build-base protoc protobuf-dev \
&& go install google.golang.org/protobuf/cmd/protoc-gen-go@latest \
&& go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest \
&& bash ci/scripts/proto/generate.sh
RUN apk add --no-cache git build-base
WORKDIR /src/api/gateway/mntx
RUN --mount=type=cache,target=/root/.cache/go-build \
--mount=type=cache,target=/go/pkg/mod \

View File

@@ -13,10 +13,7 @@ ENV GO111MODULE=on
ENV PATH="/go/bin:${PATH}"
WORKDIR /src
COPY . .
RUN apk add --no-cache bash git build-base protoc protobuf-dev \
&& go install google.golang.org/protobuf/cmd/protoc-gen-go@latest \
&& go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest \
&& bash ci/scripts/proto/generate.sh
RUN apk add --no-cache git build-base
WORKDIR /src/api/notification
RUN --mount=type=cache,target=/root/.cache/go-build \
--mount=type=cache,target=/go/pkg/mod \

View File

@@ -13,10 +13,7 @@ ENV GO111MODULE=on
ENV PATH="/go/bin:${PATH}"
WORKDIR /src
COPY . .
RUN apk add --no-cache bash git build-base protoc protobuf-dev \
&& go install google.golang.org/protobuf/cmd/protoc-gen-go@latest \
&& go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest \
&& bash ci/scripts/proto/generate.sh
RUN apk add --no-cache git build-base
WORKDIR /src/api/payments/methods
RUN --mount=type=cache,target=/root/.cache/go-build \
--mount=type=cache,target=/go/pkg/mod \

View File

@@ -13,10 +13,7 @@ ENV GO111MODULE=on
ENV PATH="/go/bin:${PATH}"
WORKDIR /src
COPY . .
RUN apk add --no-cache bash git build-base protoc protobuf-dev \
&& go install google.golang.org/protobuf/cmd/protoc-gen-go@latest \
&& go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest \
&& bash ci/scripts/proto/generate.sh
RUN apk add --no-cache git build-base
WORKDIR /src/api/payments/orchestrator
RUN --mount=type=cache,target=/root/.cache/go-build \
--mount=type=cache,target=/go/pkg/mod \

View File

@@ -13,10 +13,7 @@ ENV GO111MODULE=on
ENV PATH="/go/bin:${PATH}"
WORKDIR /src
COPY . .
RUN apk add --no-cache bash git build-base protoc protobuf-dev \
&& go install google.golang.org/protobuf/cmd/protoc-gen-go@latest \
&& go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest \
&& bash ci/scripts/proto/generate.sh
RUN apk add --no-cache git build-base
WORKDIR /src/api/payments/quotation
RUN --mount=type=cache,target=/root/.cache/go-build \
--mount=type=cache,target=/go/pkg/mod \

View File

@@ -13,10 +13,7 @@ ENV GO111MODULE=on
ENV PATH="/go/bin:${PATH}"
WORKDIR /src
COPY . .
RUN apk add --no-cache bash git build-base protoc protobuf-dev \
&& go install google.golang.org/protobuf/cmd/protoc-gen-go@latest \
&& go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest \
&& bash ci/scripts/proto/generate.sh
RUN apk add --no-cache git build-base
WORKDIR /src/api/gateway/tgsettle
RUN --mount=type=cache,target=/root/.cache/go-build \
--mount=type=cache,target=/go/pkg/mod \

View File

@@ -13,10 +13,7 @@ ENV GO111MODULE=on
ENV PATH="/go/bin:${PATH}"
WORKDIR /src
COPY . .
RUN apk add --no-cache bash git build-base protoc protobuf-dev \
&& go install google.golang.org/protobuf/cmd/protoc-gen-go@latest \
&& go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest \
&& bash ci/scripts/proto/generate.sh
RUN apk add --no-cache git build-base
WORKDIR /src/api/gateway/tron
RUN --mount=type=cache,target=/root/.cache/go-build \
--mount=type=cache,target=/go/pkg/mod \

View File

@@ -73,6 +73,9 @@ if [ ! -d "${BUILD_CONTEXT}" ]; then
BUILD_CONTEXT="/workspace"
fi
KANIKO_CACHE_REPO="${KANIKO_CACHE_REPO:-${REGISTRY_URL}/kaniko-cache}"
KANIKO_CACHE_TTL="${KANIKO_CACHE_TTL:-336h}"
/kaniko/executor \
--context "${BUILD_CONTEXT}" \
--dockerfile "${BFF_DOCKERFILE}" \
@@ -82,4 +85,8 @@ fi
--build-arg BUILD_BRANCH="${BUILD_BRANCH}" \
--build-arg BUILD_DATE="${BUILD_DATE}" \
--build-arg BUILD_USER="${BUILD_USER}" \
--single-snapshot
--cache=true \
--cache-copy-layers \
--cache-run-layers \
--cache-repo "${KANIKO_CACHE_REPO}" \
--cache-ttl "${KANIKO_CACHE_TTL}"

View File

@@ -73,6 +73,9 @@ if [ ! -d "${BUILD_CONTEXT}" ]; then
BUILD_CONTEXT="/workspace"
fi
KANIKO_CACHE_REPO="${KANIKO_CACHE_REPO:-${REGISTRY_URL}/kaniko-cache}"
KANIKO_CACHE_TTL="${KANIKO_CACHE_TTL:-336h}"
/kaniko/executor \
--context "${BUILD_CONTEXT}" \
--dockerfile "${DOCUMENTS_DOCKERFILE}" \
@@ -82,4 +85,8 @@ fi
--build-arg BUILD_BRANCH="${BUILD_BRANCH}" \
--build-arg BUILD_DATE="${BUILD_DATE}" \
--build-arg BUILD_USER="${BUILD_USER}" \
--single-snapshot
--cache=true \
--cache-copy-layers \
--cache-run-layers \
--cache-repo "${KANIKO_CACHE_REPO}" \
--cache-ttl "${KANIKO_CACHE_TTL}"

View File

@@ -73,6 +73,9 @@ if [ ! -d "${BUILD_CONTEXT}" ]; then
BUILD_CONTEXT="/workspace"
fi
KANIKO_CACHE_REPO="${KANIKO_CACHE_REPO:-${REGISTRY_URL}/kaniko-cache}"
KANIKO_CACHE_TTL="${KANIKO_CACHE_TTL:-336h}"
/kaniko/executor \
--context "${BUILD_CONTEXT}" \
--dockerfile "${FEES_DOCKERFILE}" \
@@ -82,4 +85,8 @@ fi
--build-arg BUILD_BRANCH="${BUILD_BRANCH}" \
--build-arg BUILD_DATE="${BUILD_DATE}" \
--build-arg BUILD_USER="${BUILD_USER}" \
--single-snapshot
--cache=true \
--cache-copy-layers \
--cache-run-layers \
--cache-repo "${KANIKO_CACHE_REPO}" \
--cache-ttl "${KANIKO_CACHE_TTL}"

View File

@@ -73,6 +73,9 @@ if [ ! -d "${BUILD_CONTEXT}" ]; then
BUILD_CONTEXT="/workspace"
fi
KANIKO_CACHE_REPO="${KANIKO_CACHE_REPO:-${REGISTRY_URL}/kaniko-cache}"
KANIKO_CACHE_TTL="${KANIKO_CACHE_TTL:-336h}"
/kaniko/executor \
--context "${BUILD_CONTEXT}" \
--dockerfile "${CALLBACKS_DOCKERFILE}" \
@@ -82,4 +85,8 @@ fi
--build-arg BUILD_BRANCH="${BUILD_BRANCH}" \
--build-arg BUILD_DATE="${BUILD_DATE}" \
--build-arg BUILD_USER="${BUILD_USER}" \
--single-snapshot
--cache=true \
--cache-copy-layers \
--cache-run-layers \
--cache-repo "${KANIKO_CACHE_REPO}" \
--cache-ttl "${KANIKO_CACHE_TTL}"

View File

@@ -90,6 +90,9 @@ if [ ! -f "${BUILD_CONTEXT}/${CHAIN_GATEWAY_DOCKERFILE}" ]; then
exit 68
fi
KANIKO_CACHE_REPO="${KANIKO_CACHE_REPO:-${REGISTRY_URL}/kaniko-cache}"
KANIKO_CACHE_TTL="${KANIKO_CACHE_TTL:-336h}"
/kaniko/executor \
--context "${BUILD_CONTEXT}" \
--dockerfile "${CHAIN_GATEWAY_DOCKERFILE}" \
@@ -99,4 +102,8 @@ fi
--build-arg BUILD_BRANCH="${BUILD_BRANCH}" \
--build-arg BUILD_DATE="${BUILD_DATE}" \
--build-arg BUILD_USER="${BUILD_USER}" \
--single-snapshot
--cache=true \
--cache-copy-layers \
--cache-run-layers \
--cache-repo "${KANIKO_CACHE_REPO}" \
--cache-ttl "${KANIKO_CACHE_TTL}"

View File

@@ -73,6 +73,9 @@ if [ ! -d "${BUILD_CONTEXT}" ]; then
BUILD_CONTEXT="/workspace"
fi
KANIKO_CACHE_REPO="${KANIKO_CACHE_REPO:-${REGISTRY_URL}/kaniko-cache}"
KANIKO_CACHE_TTL="${KANIKO_CACHE_TTL:-336h}"
/kaniko/executor \
--context "${BUILD_CONTEXT}" \
--dockerfile "${DISCOVERY_DOCKERFILE}" \
@@ -82,4 +85,8 @@ fi
--build-arg BUILD_BRANCH="${BUILD_BRANCH}" \
--build-arg BUILD_DATE="${BUILD_DATE}" \
--build-arg BUILD_USER="${BUILD_USER}" \
--single-snapshot
--cache=true \
--cache-copy-layers \
--cache-run-layers \
--cache-repo "${KANIKO_CACHE_REPO}" \
--cache-ttl "${KANIKO_CACHE_TTL}"

View File

@@ -73,6 +73,9 @@ if [ ! -d "${BUILD_CONTEXT}" ]; then
BUILD_CONTEXT="/workspace"
fi
KANIKO_CACHE_REPO="${KANIKO_CACHE_REPO:-${REGISTRY_URL}/kaniko-cache}"
KANIKO_CACHE_TTL="${KANIKO_CACHE_TTL:-336h}"
/kaniko/executor \
--context "${BUILD_CONTEXT}" \
--dockerfile "${FRONTEND_DOCKERFILE}" \
@@ -82,4 +85,8 @@ fi
--build-arg BUILD_BRANCH="${BUILD_BRANCH}" \
--build-arg BUILD_DATE="${BUILD_DATE}" \
--build-arg BUILD_USER="${BUILD_USER}" \
--single-snapshot
--cache=true \
--cache-copy-layers \
--cache-run-layers \
--cache-repo "${KANIKO_CACHE_REPO}" \
--cache-ttl "${KANIKO_CACHE_TTL}"

View File

@@ -73,6 +73,9 @@ if [ ! -d "${BUILD_CONTEXT}" ]; then
BUILD_CONTEXT="/workspace"
fi
KANIKO_CACHE_REPO="${KANIKO_CACHE_REPO:-${REGISTRY_URL}/kaniko-cache}"
KANIKO_CACHE_TTL="${KANIKO_CACHE_TTL:-336h}"
/kaniko/executor \
--context "${BUILD_CONTEXT}" \
--dockerfile "${FX_DOCKERFILE}" \
@@ -82,4 +85,8 @@ fi
--build-arg BUILD_BRANCH="${BUILD_BRANCH}" \
--build-arg BUILD_DATE="${BUILD_DATE}" \
--build-arg BUILD_USER="${BUILD_USER}" \
--single-snapshot
--cache=true \
--cache-copy-layers \
--cache-run-layers \
--cache-repo "${KANIKO_CACHE_REPO}" \
--cache-ttl "${KANIKO_CACHE_TTL}"

View File

@@ -73,6 +73,9 @@ if [ ! -d "${BUILD_CONTEXT}" ]; then
BUILD_CONTEXT="/workspace"
fi
KANIKO_CACHE_REPO="${KANIKO_CACHE_REPO:-${REGISTRY_URL}/kaniko-cache}"
KANIKO_CACHE_TTL="${KANIKO_CACHE_TTL:-336h}"
/kaniko/executor \
--context "${BUILD_CONTEXT}" \
--dockerfile "${LEDGER_DOCKERFILE}" \
@@ -82,4 +85,8 @@ fi
--build-arg BUILD_BRANCH="${BUILD_BRANCH}" \
--build-arg BUILD_DATE="${BUILD_DATE}" \
--build-arg BUILD_USER="${BUILD_USER}" \
--single-snapshot
--cache=true \
--cache-copy-layers \
--cache-run-layers \
--cache-repo "${KANIKO_CACHE_REPO}" \
--cache-ttl "${KANIKO_CACHE_TTL}"

View File

@@ -90,6 +90,9 @@ if [ ! -f "${BUILD_CONTEXT}/${MNTX_GATEWAY_DOCKERFILE}" ]; then
exit 68
fi
KANIKO_CACHE_REPO="${KANIKO_CACHE_REPO:-${REGISTRY_URL}/kaniko-cache}"
KANIKO_CACHE_TTL="${KANIKO_CACHE_TTL:-336h}"
/kaniko/executor \
--context "${BUILD_CONTEXT}" \
--dockerfile "${MNTX_GATEWAY_DOCKERFILE}" \
@@ -99,4 +102,8 @@ fi
--build-arg BUILD_BRANCH="${BUILD_BRANCH}" \
--build-arg BUILD_DATE="${BUILD_DATE}" \
--build-arg BUILD_USER="${BUILD_USER}" \
--single-snapshot
--cache=true \
--cache-copy-layers \
--cache-run-layers \
--cache-repo "${KANIKO_CACHE_REPO}" \
--cache-ttl "${KANIKO_CACHE_TTL}"

View File

@@ -73,6 +73,9 @@ if [ ! -d "${BUILD_CONTEXT}" ]; then
BUILD_CONTEXT="/workspace"
fi
KANIKO_CACHE_REPO="${KANIKO_CACHE_REPO:-${REGISTRY_URL}/kaniko-cache}"
KANIKO_CACHE_TTL="${KANIKO_CACHE_TTL:-336h}"
/kaniko/executor \
--context "${BUILD_CONTEXT}" \
--dockerfile "${NOTIFICATION_DOCKERFILE}" \
@@ -82,4 +85,8 @@ fi
--build-arg BUILD_BRANCH="${BUILD_BRANCH}" \
--build-arg BUILD_DATE="${BUILD_DATE}" \
--build-arg BUILD_USER="${BUILD_USER}" \
--single-snapshot
--cache=true \
--cache-copy-layers \
--cache-run-layers \
--cache-repo "${KANIKO_CACHE_REPO}" \
--cache-ttl "${KANIKO_CACHE_TTL}"

View File

@@ -73,6 +73,9 @@ if [ ! -d "${BUILD_CONTEXT}" ]; then
BUILD_CONTEXT="/workspace"
fi
KANIKO_CACHE_REPO="${KANIKO_CACHE_REPO:-${REGISTRY_URL}/kaniko-cache}"
KANIKO_CACHE_TTL="${KANIKO_CACHE_TTL:-336h}"
/kaniko/executor \
--context "${BUILD_CONTEXT}" \
--dockerfile "${PAYMENTS_METHODS_DOCKERFILE}" \
@@ -82,4 +85,8 @@ fi
--build-arg BUILD_BRANCH="${BUILD_BRANCH}" \
--build-arg BUILD_DATE="${BUILD_DATE}" \
--build-arg BUILD_USER="${BUILD_USER}" \
--single-snapshot
--cache=true \
--cache-copy-layers \
--cache-run-layers \
--cache-repo "${KANIKO_CACHE_REPO}" \
--cache-ttl "${KANIKO_CACHE_TTL}"

View File

@@ -73,6 +73,9 @@ if [ ! -d "${BUILD_CONTEXT}" ]; then
BUILD_CONTEXT="/workspace"
fi
KANIKO_CACHE_REPO="${KANIKO_CACHE_REPO:-${REGISTRY_URL}/kaniko-cache}"
KANIKO_CACHE_TTL="${KANIKO_CACHE_TTL:-336h}"
/kaniko/executor \
--context "${BUILD_CONTEXT}" \
--dockerfile "${PAYMENTS_DOCKERFILE}" \
@@ -82,4 +85,8 @@ fi
--build-arg BUILD_BRANCH="${BUILD_BRANCH}" \
--build-arg BUILD_DATE="${BUILD_DATE}" \
--build-arg BUILD_USER="${BUILD_USER}" \
--single-snapshot
--cache=true \
--cache-copy-layers \
--cache-run-layers \
--cache-repo "${KANIKO_CACHE_REPO}" \
--cache-ttl "${KANIKO_CACHE_TTL}"

View File

@@ -73,6 +73,9 @@ if [ ! -d "${BUILD_CONTEXT}" ]; then
BUILD_CONTEXT="/workspace"
fi
KANIKO_CACHE_REPO="${KANIKO_CACHE_REPO:-${REGISTRY_URL}/kaniko-cache}"
KANIKO_CACHE_TTL="${KANIKO_CACHE_TTL:-336h}"
/kaniko/executor \
--context "${BUILD_CONTEXT}" \
--dockerfile "${PAYMENTS_QUOTATION_DOCKERFILE}" \
@@ -82,4 +85,8 @@ fi
--build-arg BUILD_BRANCH="${BUILD_BRANCH}" \
--build-arg BUILD_DATE="${BUILD_DATE}" \
--build-arg BUILD_USER="${BUILD_USER}" \
--single-snapshot
--cache=true \
--cache-copy-layers \
--cache-run-layers \
--cache-repo "${KANIKO_CACHE_REPO}" \
--cache-ttl "${KANIKO_CACHE_TTL}"

View File

@@ -90,6 +90,9 @@ if [ ! -f "${BUILD_CONTEXT}/${TGSETTLE_GATEWAY_DOCKERFILE}" ]; then
exit 68
fi
KANIKO_CACHE_REPO="${KANIKO_CACHE_REPO:-${REGISTRY_URL}/kaniko-cache}"
KANIKO_CACHE_TTL="${KANIKO_CACHE_TTL:-336h}"
/kaniko/executor \
--context "${BUILD_CONTEXT}" \
--dockerfile "${TGSETTLE_GATEWAY_DOCKERFILE}" \
@@ -99,4 +102,8 @@ fi
--build-arg BUILD_BRANCH="${BUILD_BRANCH}" \
--build-arg BUILD_DATE="${BUILD_DATE}" \
--build-arg BUILD_USER="${BUILD_USER}" \
--single-snapshot
--cache=true \
--cache-copy-layers \
--cache-run-layers \
--cache-repo "${KANIKO_CACHE_REPO}" \
--cache-ttl "${KANIKO_CACHE_TTL}"

View File

@@ -90,6 +90,9 @@ if [ ! -f "${BUILD_CONTEXT}/${TRON_GATEWAY_DOCKERFILE}" ]; then
exit 68
fi
KANIKO_CACHE_REPO="${KANIKO_CACHE_REPO:-${REGISTRY_URL}/kaniko-cache}"
KANIKO_CACHE_TTL="${KANIKO_CACHE_TTL:-336h}"
/kaniko/executor \
--context "${BUILD_CONTEXT}" \
--dockerfile "${TRON_GATEWAY_DOCKERFILE}" \
@@ -99,4 +102,8 @@ fi
--build-arg BUILD_BRANCH="${BUILD_BRANCH}" \
--build-arg BUILD_DATE="${BUILD_DATE}" \
--build-arg BUILD_USER="${BUILD_USER}" \
--single-snapshot
--cache=true \
--cache-copy-layers \
--cache-run-layers \
--cache-repo "${KANIKO_CACHE_REPO}" \
--cache-ttl "${KANIKO_CACHE_TTL}"

View File

@@ -779,15 +779,15 @@ services:
VAULT_ADDR: ${VAULT_ADDR}
# --------------------------------------------------------------------------
# TGSettle Gateway Service (Telegram settlements)
# ChimeraSettle Gateway Service (simulated settlements)
# --------------------------------------------------------------------------
dev-tgsettle-gateway:
dev-chsettle-gateway:
<<: *common-env
build:
context: .
dockerfile: ci/dev/tgsettle-gateway.dockerfile
image: sendico-dev/tgsettle-gateway:latest
container_name: dev-tgsettle-gateway
dockerfile: ci/dev/chsettle-gateway.dockerfile
image: sendico-dev/chsettle-gateway:latest
container_name: dev-chsettle-gateway
restart: unless-stopped
depends_on:
dev-mongo-init: { condition: service_completed_successfully }
@@ -795,29 +795,29 @@ services:
dev-discovery: { condition: service_started }
dev-vault: { condition: service_healthy }
volumes:
- ./api/gateway/tgsettle:/src/api/gateway/tgsettle
- ./api/gateway/chsettle:/src/api/gateway/chsettle
- ./api/gateway/common:/src/api/gateway/common
- ./api/gateway/tgsettle/config.dev.yml:/app/config.yml:ro
- ./api/gateway/chsettle/config.dev.yml:/app/config.yml:ro
ports:
- "50080:50080"
- "9406:9406"
networks:
- sendico-dev
environment:
TGSETTLE_GATEWAY_MONGO_HOST: dev-mongo-1
TGSETTLE_GATEWAY_MONGO_PORT: 27017
TGSETTLE_GATEWAY_MONGO_DATABASE: tgsettle_gateway
TGSETTLE_GATEWAY_MONGO_USER: ${MONGO_USER}
TGSETTLE_GATEWAY_MONGO_PASSWORD: ${MONGO_PASSWORD}
TGSETTLE_GATEWAY_MONGO_AUTH_SOURCE: admin
TGSETTLE_GATEWAY_MONGO_REPLICA_SET: dev-rs
CHSETTLE_GATEWAY_MONGO_HOST: dev-mongo-1
CHSETTLE_GATEWAY_MONGO_PORT: 27017
CHSETTLE_GATEWAY_MONGO_DATABASE: chsettle_gateway
CHSETTLE_GATEWAY_MONGO_USER: ${MONGO_USER}
CHSETTLE_GATEWAY_MONGO_PASSWORD: ${MONGO_PASSWORD}
CHSETTLE_GATEWAY_MONGO_AUTH_SOURCE: admin
CHSETTLE_GATEWAY_MONGO_REPLICA_SET: dev-rs
NATS_HOST: dev-nats
NATS_PORT: 4222
NATS_USER: ${NATS_USER}
NATS_PASSWORD: ${NATS_PASSWORD}
NATS_URL: nats://${NATS_USER}:${NATS_PASSWORD}@dev-nats:4222
TGSETTLE_GATEWAY_GRPC_PORT: 50080
TGSETTLE_GATEWAY_METRICS_PORT: 9406
CHSETTLE_GATEWAY_GRPC_PORT: 50080
CHSETTLE_GATEWAY_METRICS_PORT: 9406
VAULT_ADDR: ${VAULT_ADDR}
# --------------------------------------------------------------------------