Fully separated payment quotation and orchestration flows

This commit is contained in:
Stephan D
2026-02-11 17:25:44 +01:00
parent 9b8f59e05a
commit e116535926
112 changed files with 3204 additions and 8686 deletions

View File

@@ -6,6 +6,7 @@ import (
ledgerclient "github.com/tech/sendico/ledger/client"
"github.com/tech/sendico/payments/orchestrator/internal/service/orchestrator"
feesv1 "github.com/tech/sendico/pkg/proto/billing/fees/v1"
quotationv1 "github.com/tech/sendico/pkg/proto/payments/quotation/v1"
)
type orchestratorDeps struct {
@@ -13,6 +14,7 @@ type orchestratorDeps struct {
ledgerClient ledgerclient.Client
mntxClient mntxclient.Client
oracleClient oracleclient.Client
quotationClient quotationv1.QuotationServiceClient
gatewayInvokeResolver orchestrator.GatewayInvokeResolver
}
@@ -30,6 +32,7 @@ func (i *Imp) initDependencies(_ *config) *orchestratorDeps {
deps.ledgerClient = &discoveryLedgerClient{resolver: i.discoveryClients}
deps.oracleClient = &discoveryOracleClient{resolver: i.discoveryClients}
deps.mntxClient = &discoveryMntxClient{resolver: i.discoveryClients}
deps.quotationClient = &discoveryQuotationClient{resolver: i.discoveryClients}
deps.gatewayInvokeResolver = discoveryGatewayInvokeResolver{resolver: i.discoveryClients}
return deps
}
@@ -48,8 +51,9 @@ func (i *Imp) buildServiceOptions(cfg *config, deps *orchestratorDeps) []orchest
if deps.mntxClient != nil {
opts = append(opts, orchestrator.WithMntxGateway(deps.mntxClient))
}
if deps.oracleClient != nil {
opts = append(opts, orchestrator.WithOracleClient(deps.oracleClient))
if deps.quotationClient != nil {
opts = append(opts, orchestrator.WithQuotationService(deps.quotationClient))
}
if deps.gatewayInvokeResolver != nil {
opts = append(opts, orchestrator.WithGatewayInvokeResolver(deps.gatewayInvokeResolver))

View File

@@ -19,6 +19,7 @@ import (
"github.com/tech/sendico/pkg/mlogger"
"github.com/tech/sendico/pkg/mservice"
feesv1 "github.com/tech/sendico/pkg/proto/billing/fees/v1"
quotationv1 "github.com/tech/sendico/pkg/proto/payments/quotation/v1"
"go.uber.org/zap"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
@@ -32,6 +33,7 @@ var (
ledgerServiceNames = []string{"LEDGER", string(mservice.Ledger)}
oracleServiceNames = []string{"FX_ORACLE", string(mservice.FXOracle)}
mntxServiceNames = []string{"CARD_PAYOUT_RAIL_GATEWAY", string(mservice.MntxGateway)}
quoteServiceNames = []string{"PAYMENT_QUOTATION", "payment_quotation"}
)
type discoveryEndpoint struct {
@@ -53,6 +55,9 @@ type discoveryClientResolver struct {
feesConn *grpc.ClientConn
feesEndpoint discoveryEndpoint
quoteConn *grpc.ClientConn
quoteEndpoint discoveryEndpoint
ledgerClient ledgerclient.Client
ledgerEndpoint discoveryEndpoint
@@ -88,6 +93,10 @@ func (r *discoveryClientResolver) Close() {
_ = r.feesConn.Close()
r.feesConn = nil
}
if r.quoteConn != nil {
_ = r.quoteConn.Close()
r.quoteConn = nil
}
if r.ledgerClient != nil {
_ = r.ledgerClient.Close()
r.ledgerClient = nil
@@ -128,6 +137,11 @@ func (r *discoveryClientResolver) MntxAvailable() bool {
return ok
}
func (r *discoveryClientResolver) QuotationAvailable() bool {
_, ok := r.findEntry("quotation", quoteServiceNames, "", "")
return ok
}
func (r *discoveryClientResolver) FeesClient(ctx context.Context) (feesv1.FeeEngineClient, error) {
entry, ok := r.findEntry("fees", feesServiceNames, "", "")
if !ok {
@@ -159,6 +173,37 @@ func (r *discoveryClientResolver) FeesClient(ctx context.Context) (feesv1.FeeEng
return feesv1.NewFeeEngineClient(r.feesConn), nil
}
func (r *discoveryClientResolver) QuotationClient(ctx context.Context) (quotationv1.QuotationServiceClient, error) {
entry, ok := r.findEntry("quotation", quoteServiceNames, "", "")
if !ok {
return nil, merrors.NoData("discovery: quotation service unavailable")
}
endpoint, err := parseDiscoveryEndpoint(entry.InvokeURI)
if err != nil {
r.logMissing("quotation", "invalid quotation invoke uri", entry.InvokeURI, err)
return nil, err
}
r.mu.Lock()
defer r.mu.Unlock()
if r.quoteConn == nil || r.quoteEndpoint.key() != endpoint.key() || r.quoteEndpoint.address != endpoint.address {
if r.quoteConn != nil {
_ = r.quoteConn.Close()
r.quoteConn = nil
}
conn, dialErr := dialGrpc(ctx, endpoint)
if dialErr != nil {
r.logMissing("quotation", "failed to dial quotation service", endpoint.raw, dialErr)
return nil, dialErr
}
r.quoteConn = conn
r.quoteEndpoint = endpoint
}
return quotationv1.NewQuotationServiceClient(r.quoteConn), nil
}
func (r *discoveryClientResolver) LedgerClient(ctx context.Context) (ledgerclient.Client, error) {
entry, ok := r.findEntry("ledger", ledgerServiceNames, "", "")
if !ok {

View File

@@ -13,6 +13,7 @@ import (
chainv1 "github.com/tech/sendico/pkg/proto/gateway/chain/v1"
mntxv1 "github.com/tech/sendico/pkg/proto/gateway/mntx/v1"
ledgerv1 "github.com/tech/sendico/pkg/proto/ledger/v1"
quotationv1 "github.com/tech/sendico/pkg/proto/payments/quotation/v1"
"google.golang.org/grpc"
)
@@ -51,6 +52,33 @@ func (c *discoveryFeeClient) ValidateFeeToken(ctx context.Context, req *feesv1.V
return client.ValidateFeeToken(ctx, req, opts...)
}
type discoveryQuotationClient struct {
resolver *discoveryClientResolver
}
func (c *discoveryQuotationClient) Available() bool {
if c == nil || c.resolver == nil {
return false
}
return c.resolver.QuotationAvailable()
}
func (c *discoveryQuotationClient) QuotePayment(ctx context.Context, req *quotationv1.QuotePaymentRequest, opts ...grpc.CallOption) (*quotationv1.QuotePaymentResponse, error) {
client, err := c.resolver.QuotationClient(ctx)
if err != nil {
return nil, err
}
return client.QuotePayment(ctx, req, opts...)
}
func (c *discoveryQuotationClient) QuotePayments(ctx context.Context, req *quotationv1.QuotePaymentsRequest, opts ...grpc.CallOption) (*quotationv1.QuotePaymentsResponse, error) {
client, err := c.resolver.QuotationClient(ctx)
if err != nil {
return nil, err
}
return client.QuotePayments(ctx, req, opts...)
}
type discoveryLedgerClient struct {
resolver *discoveryClientResolver
}