Merge pull request 'ledger account reference removed' (#87) from fees-86 into main
Some checks failed
ci/woodpecker/push/ledger Pipeline is pending
ci/woodpecker/push/mntx_gateway Pipeline is pending
ci/woodpecker/push/nats Pipeline is pending
ci/woodpecker/push/notification Pipeline is pending
ci/woodpecker/push/payments_orchestrator Pipeline is pending
ci/woodpecker/push/billing_fees Pipeline was successful
ci/woodpecker/push/chain_gateway Pipeline was successful
ci/woodpecker/push/bff Pipeline was successful
ci/woodpecker/push/db Pipeline was successful
ci/woodpecker/push/fx_ingestor Pipeline was successful
ci/woodpecker/push/fx_oracle Pipeline failed
ci/woodpecker/push/frontend Pipeline failed
Some checks failed
ci/woodpecker/push/ledger Pipeline is pending
ci/woodpecker/push/mntx_gateway Pipeline is pending
ci/woodpecker/push/nats Pipeline is pending
ci/woodpecker/push/notification Pipeline is pending
ci/woodpecker/push/payments_orchestrator Pipeline is pending
ci/woodpecker/push/billing_fees Pipeline was successful
ci/woodpecker/push/chain_gateway Pipeline was successful
ci/woodpecker/push/bff Pipeline was successful
ci/woodpecker/push/db Pipeline was successful
ci/woodpecker/push/fx_ingestor Pipeline was successful
ci/woodpecker/push/fx_oracle Pipeline failed
ci/woodpecker/push/frontend Pipeline failed
Reviewed-on: #87
This commit was merged in pull request #87.
This commit is contained in:
@@ -90,10 +90,6 @@ func (c *quoteCalculator) Compute(ctx context.Context, plan *model.FeePlan, inte
|
|||||||
}
|
}
|
||||||
|
|
||||||
ledgerAccountRef := strings.TrimSpace(rule.LedgerAccountRef)
|
ledgerAccountRef := strings.TrimSpace(rule.LedgerAccountRef)
|
||||||
if ledgerAccountRef == "" {
|
|
||||||
c.logger.Warn("fee rule missing ledger account reference", zap.String("rule_id", rule.RuleID))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
amount, scale, calcErr := c.calculateRuleAmount(baseAmount, baseScale, rule)
|
amount, scale, calcErr := c.calculateRuleAmount(baseAmount, baseScale, rule)
|
||||||
if calcErr != nil {
|
if calcErr != nil {
|
||||||
|
|||||||
@@ -38,23 +38,23 @@ func New(plans storage.PlansStore, logger *zap.Logger) *feeResolver {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *feeResolver) ResolveFeeRule(ctx context.Context, orgID *primitive.ObjectID, trigger model.Trigger, at time.Time, attrs map[string]string) (*model.FeePlan, *model.FeeRule, error) {
|
func (r *feeResolver) ResolveFeeRule(ctx context.Context, orgRef *primitive.ObjectID, trigger model.Trigger, at time.Time, attrs map[string]string) (*model.FeePlan, *model.FeeRule, error) {
|
||||||
if r.plans == nil {
|
if r.plans == nil {
|
||||||
return nil, nil, merrors.InvalidArgument("fees: plans store is required")
|
return nil, nil, merrors.InvalidArgument("fees: plans store is required")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try org-specific first if provided.
|
// Try org-specific first if provided.
|
||||||
if orgID != nil && !orgID.IsZero() {
|
if orgRef != nil && !orgRef.IsZero() {
|
||||||
if plan, err := r.getOrgPlan(ctx, *orgID, at); err == nil {
|
if plan, err := r.getOrgPlan(ctx, *orgRef, at); err == nil {
|
||||||
if rule, selErr := selectRule(plan, trigger, at, attrs); selErr == nil {
|
if rule, selErr := selectRule(plan, trigger, at, attrs); selErr == nil {
|
||||||
return plan, rule, nil
|
return plan, rule, nil
|
||||||
} else if !errors.Is(selErr, ErrNoFeeRuleFound) {
|
} else if !errors.Is(selErr, ErrNoFeeRuleFound) {
|
||||||
r.logger.Warn("failed selecting rule for org plan", zap.Error(selErr), zap.String("org_ref", orgID.Hex()))
|
r.logger.Warn("failed selecting rule for org plan", zap.Error(selErr), zap.String("org_ref", orgRef.Hex()))
|
||||||
return nil, nil, selErr
|
return nil, nil, selErr
|
||||||
}
|
}
|
||||||
r.logger.Debug("no matching rule in org plan; falling back to global", zap.String("org_ref", orgID.Hex()))
|
r.logger.Debug("no matching rule in org plan; falling back to global", zap.String("org_ref", orgRef.Hex()))
|
||||||
} else if !errors.Is(err, storage.ErrFeePlanNotFound) {
|
} else if !errors.Is(err, storage.ErrFeePlanNotFound) {
|
||||||
r.logger.Warn("failed resolving org fee plan", zap.Error(err), zap.String("org_ref", orgID.Hex()))
|
r.logger.Warn("failed resolving org fee plan", zap.Error(err), zap.String("org_ref", orgRef.Hex()))
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,8 +32,8 @@ func TestResolver_GlobalFallbackWhenOrgMissing(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("expected fallback to global, got error: %v", err)
|
t.Fatalf("expected fallback to global, got error: %v", err)
|
||||||
}
|
}
|
||||||
if !plan.GetOrganizationRef().IsZero() {
|
if plan.OrganizationRef != nil && !plan.OrganizationRef.IsZero() {
|
||||||
t.Fatalf("expected global plan, got orgRef %s", plan.GetOrganizationRef().Hex())
|
t.Fatalf("expected global plan, got orgRef %s", plan.OrganizationRef.Hex())
|
||||||
}
|
}
|
||||||
if rule.RuleID != "global_capture" {
|
if rule.RuleID != "global_capture" {
|
||||||
t.Fatalf("unexpected rule selected: %s", rule.RuleID)
|
t.Fatalf("unexpected rule selected: %s", rule.RuleID)
|
||||||
@@ -59,8 +59,7 @@ func TestResolver_OrgOverridesGlobal(t *testing.T) {
|
|||||||
{RuleID: "org_capture", Trigger: model.TriggerCapture, Priority: 10, Percentage: "0.03", EffectiveFrom: now.Add(-time.Hour)},
|
{RuleID: "org_capture", Trigger: model.TriggerCapture, Priority: 10, Percentage: "0.03", EffectiveFrom: now.Add(-time.Hour)},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
orgPlan.SetOrganizationRef(org)
|
orgPlan.OrganizationRef = &org
|
||||||
|
|
||||||
store := &memoryPlansStore{plans: []*model.FeePlan{globalPlan, orgPlan}}
|
store := &memoryPlansStore{plans: []*model.FeePlan{globalPlan, orgPlan}}
|
||||||
resolver := New(store, zap.NewNop())
|
resolver := New(store, zap.NewNop())
|
||||||
|
|
||||||
@@ -95,7 +94,7 @@ func TestResolver_SelectsHighestPriority(t *testing.T) {
|
|||||||
{RuleID: "high", Trigger: model.TriggerCapture, Priority: 200, Percentage: "0.03", EffectiveFrom: now.Add(-time.Hour)},
|
{RuleID: "high", Trigger: model.TriggerCapture, Priority: 200, Percentage: "0.03", EffectiveFrom: now.Add(-time.Hour)},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
plan.SetOrganizationRef(org)
|
plan.OrganizationRef = &org
|
||||||
|
|
||||||
store := &memoryPlansStore{plans: []*model.FeePlan{plan}}
|
store := &memoryPlansStore{plans: []*model.FeePlan{plan}}
|
||||||
resolver := New(store, zap.NewNop())
|
resolver := New(store, zap.NewNop())
|
||||||
@@ -136,7 +135,7 @@ func TestResolver_EffectiveDateFiltering(t *testing.T) {
|
|||||||
{RuleID: "expired", Trigger: model.TriggerCapture, Priority: 100, Percentage: "0.05", EffectiveFrom: past, EffectiveTo: &past},
|
{RuleID: "expired", Trigger: model.TriggerCapture, Priority: 100, Percentage: "0.05", EffectiveFrom: past, EffectiveTo: &past},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
orgPlan.SetOrganizationRef(org)
|
orgPlan.OrganizationRef = &org
|
||||||
|
|
||||||
globalPlan := &model.FeePlan{
|
globalPlan := &model.FeePlan{
|
||||||
Active: true,
|
Active: true,
|
||||||
@@ -221,7 +220,7 @@ func TestResolver_MultipleActivePlansConflict(t *testing.T) {
|
|||||||
{RuleID: "r1", Trigger: model.TriggerCapture, Priority: 10, Percentage: "0.05", EffectiveFrom: now.Add(-time.Hour)},
|
{RuleID: "r1", Trigger: model.TriggerCapture, Priority: 10, Percentage: "0.05", EffectiveFrom: now.Add(-time.Hour)},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
p1.SetOrganizationRef(org)
|
p1.OrganizationRef = &org
|
||||||
p2 := &model.FeePlan{
|
p2 := &model.FeePlan{
|
||||||
Active: true,
|
Active: true,
|
||||||
EffectiveFrom: now.Add(-30 * time.Minute),
|
EffectiveFrom: now.Add(-30 * time.Minute),
|
||||||
@@ -229,7 +228,7 @@ func TestResolver_MultipleActivePlansConflict(t *testing.T) {
|
|||||||
{RuleID: "r2", Trigger: model.TriggerCapture, Priority: 20, Percentage: "0.03", EffectiveFrom: now.Add(-time.Hour)},
|
{RuleID: "r2", Trigger: model.TriggerCapture, Priority: 20, Percentage: "0.03", EffectiveFrom: now.Add(-time.Hour)},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
p2.SetOrganizationRef(org)
|
p2.OrganizationRef = &org
|
||||||
|
|
||||||
store := &memoryPlansStore{plans: []*model.FeePlan{p1, p2}}
|
store := &memoryPlansStore{plans: []*model.FeePlan{p1, p2}}
|
||||||
resolver := New(store, zap.NewNop())
|
resolver := New(store, zap.NewNop())
|
||||||
@@ -263,7 +262,7 @@ func (m *memoryPlansStore) GetActivePlan(ctx context.Context, orgRef primitive.O
|
|||||||
func (m *memoryPlansStore) FindActiveOrgPlan(_ context.Context, orgRef primitive.ObjectID, at time.Time) (*model.FeePlan, error) {
|
func (m *memoryPlansStore) FindActiveOrgPlan(_ context.Context, orgRef primitive.ObjectID, at time.Time) (*model.FeePlan, error) {
|
||||||
var matches []*model.FeePlan
|
var matches []*model.FeePlan
|
||||||
for _, plan := range m.plans {
|
for _, plan := range m.plans {
|
||||||
if plan == nil || plan.GetOrganizationRef() != orgRef {
|
if plan == nil || plan.OrganizationRef == nil || plan.OrganizationRef.IsZero() || (*plan.OrganizationRef != orgRef) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if !plan.Active {
|
if !plan.Active {
|
||||||
@@ -289,7 +288,7 @@ func (m *memoryPlansStore) FindActiveOrgPlan(_ context.Context, orgRef primitive
|
|||||||
func (m *memoryPlansStore) FindActiveGlobalPlan(_ context.Context, at time.Time) (*model.FeePlan, error) {
|
func (m *memoryPlansStore) FindActiveGlobalPlan(_ context.Context, at time.Time) (*model.FeePlan, error) {
|
||||||
var matches []*model.FeePlan
|
var matches []*model.FeePlan
|
||||||
for _, plan := range m.plans {
|
for _, plan := range m.plans {
|
||||||
if plan == nil || !plan.GetOrganizationRef().IsZero() {
|
if plan == nil || ((plan.OrganizationRef != nil) && !plan.OrganizationRef.IsZero()) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if !plan.Active {
|
if !plan.Active {
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ func TestQuoteFees_ComputesDerivedLines(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
plan.SetID(primitive.NewObjectID())
|
plan.SetID(primitive.NewObjectID())
|
||||||
plan.SetOrganizationRef(orgRef)
|
plan.OrganizationRef = &orgRef
|
||||||
|
|
||||||
service := NewService(
|
service := NewService(
|
||||||
zap.NewNop(),
|
zap.NewNop(),
|
||||||
@@ -163,7 +163,7 @@ func TestQuoteFees_FiltersByAttributesAndDates(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
plan.SetID(primitive.NewObjectID())
|
plan.SetID(primitive.NewObjectID())
|
||||||
plan.SetOrganizationRef(orgRef)
|
plan.OrganizationRef = &orgRef
|
||||||
|
|
||||||
service := NewService(
|
service := NewService(
|
||||||
zap.NewNop(),
|
zap.NewNop(),
|
||||||
@@ -224,7 +224,7 @@ func TestQuoteFees_RoundingDown(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
plan.SetID(primitive.NewObjectID())
|
plan.SetID(primitive.NewObjectID())
|
||||||
plan.SetOrganizationRef(orgRef)
|
plan.OrganizationRef = &orgRef
|
||||||
|
|
||||||
service := NewService(
|
service := NewService(
|
||||||
zap.NewNop(),
|
zap.NewNop(),
|
||||||
@@ -277,7 +277,7 @@ func TestQuoteFees_UsesInjectedCalculator(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
plan.SetID(primitive.NewObjectID())
|
plan.SetID(primitive.NewObjectID())
|
||||||
plan.SetOrganizationRef(orgRef)
|
plan.OrganizationRef = &orgRef
|
||||||
|
|
||||||
result := &types.CalculationResult{
|
result := &types.CalculationResult{
|
||||||
Lines: []*feesv1.DerivedPostingLine{
|
Lines: []*feesv1.DerivedPostingLine{
|
||||||
@@ -353,7 +353,7 @@ func TestQuoteFees_PopulatesFxUsed(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
plan.SetID(primitive.NewObjectID())
|
plan.SetID(primitive.NewObjectID())
|
||||||
plan.SetOrganizationRef(orgRef)
|
plan.OrganizationRef = &orgRef
|
||||||
|
|
||||||
fakeOracle := &oracleclient.Fake{
|
fakeOracle := &oracleclient.Fake{
|
||||||
LatestRateFn: func(ctx context.Context, req oracleclient.LatestRateParams) (*oracleclient.RateSnapshot, error) {
|
LatestRateFn: func(ctx context.Context, req oracleclient.LatestRateParams) (*oracleclient.RateSnapshot, error) {
|
||||||
@@ -452,7 +452,7 @@ func (s *stubPlansStore) FindActiveOrgPlan(_ context.Context, orgRef primitive.O
|
|||||||
if s.plan == nil {
|
if s.plan == nil {
|
||||||
return nil, storage.ErrFeePlanNotFound
|
return nil, storage.ErrFeePlanNotFound
|
||||||
}
|
}
|
||||||
if s.plan.GetOrganizationRef() != orgRef {
|
if (s.plan.OrganizationRef != nil) && (*s.plan.OrganizationRef != orgRef) {
|
||||||
return nil, storage.ErrFeePlanNotFound
|
return nil, storage.ErrFeePlanNotFound
|
||||||
}
|
}
|
||||||
if !s.plan.Active {
|
if !s.plan.Active {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
|
|
||||||
"github.com/tech/sendico/pkg/db/storable"
|
"github.com/tech/sendico/pkg/db/storable"
|
||||||
"github.com/tech/sendico/pkg/model"
|
"github.com/tech/sendico/pkg/model"
|
||||||
|
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -25,14 +26,14 @@ const (
|
|||||||
|
|
||||||
// FeePlan describes a collection of fee rules for an organisation.
|
// FeePlan describes a collection of fee rules for an organisation.
|
||||||
type FeePlan struct {
|
type FeePlan struct {
|
||||||
storable.Base `bson:",inline" json:",inline"`
|
storable.Base `bson:",inline" json:",inline"`
|
||||||
model.OrganizationBoundBase `bson:",inline" json:",inline"`
|
model.Describable `bson:",inline" json:",inline"`
|
||||||
model.Describable `bson:",inline" json:",inline"`
|
OrganizationRef *primitive.ObjectID `bson:"organizationRef,omitempty" json:"organizationRef,omitempty"`
|
||||||
Active bool `bson:"active" json:"active"`
|
Active bool `bson:"active" json:"active"`
|
||||||
EffectiveFrom time.Time `bson:"effectiveFrom" json:"effectiveFrom"`
|
EffectiveFrom time.Time `bson:"effectiveFrom" json:"effectiveFrom"`
|
||||||
EffectiveTo *time.Time `bson:"effectiveTo,omitempty" json:"effectiveTo,omitempty"`
|
EffectiveTo *time.Time `bson:"effectiveTo,omitempty" json:"effectiveTo,omitempty"`
|
||||||
Rules []FeeRule `bson:"rules,omitempty" json:"rules,omitempty"`
|
Rules []FeeRule `bson:"rules,omitempty" json:"rules,omitempty"`
|
||||||
Metadata map[string]string `bson:"metadata,omitempty" json:"metadata,omitempty"`
|
Metadata map[string]string `bson:"metadata,omitempty" json:"metadata,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Collection implements storable.Storable.
|
// Collection implements storable.Storable.
|
||||||
|
|||||||
@@ -61,3 +61,6 @@ card_gateways:
|
|||||||
monetix:
|
monetix:
|
||||||
funding_address: "wallet_funding_monetix"
|
funding_address: "wallet_funding_monetix"
|
||||||
fee_address: "wallet_fee_monetix"
|
fee_address: "wallet_fee_monetix"
|
||||||
|
|
||||||
|
fee_ledger_accounts:
|
||||||
|
monetix: "ledger:fees:monetix"
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ type config struct {
|
|||||||
Gateway clientConfig `yaml:"gateway"`
|
Gateway clientConfig `yaml:"gateway"`
|
||||||
Oracle clientConfig `yaml:"oracle"`
|
Oracle clientConfig `yaml:"oracle"`
|
||||||
CardGateways map[string]cardGatewayRouteConfig `yaml:"card_gateways"`
|
CardGateways map[string]cardGatewayRouteConfig `yaml:"card_gateways"`
|
||||||
|
FeeAccounts map[string]string `yaml:"fee_ledger_accounts"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type clientConfig struct {
|
type clientConfig struct {
|
||||||
@@ -159,6 +160,9 @@ func (i *Imp) Start() error {
|
|||||||
if routes := buildCardGatewayRoutes(cfg.CardGateways); len(routes) > 0 {
|
if routes := buildCardGatewayRoutes(cfg.CardGateways); len(routes) > 0 {
|
||||||
opts = append(opts, orchestrator.WithCardGatewayRoutes(routes))
|
opts = append(opts, orchestrator.WithCardGatewayRoutes(routes))
|
||||||
}
|
}
|
||||||
|
if feeAccounts := buildFeeLedgerAccounts(cfg.FeeAccounts); len(feeAccounts) > 0 {
|
||||||
|
opts = append(opts, orchestrator.WithFeeLedgerAccounts(feeAccounts))
|
||||||
|
}
|
||||||
return orchestrator.NewService(logger, repo, opts...), nil
|
return orchestrator.NewService(logger, repo, opts...), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -323,3 +327,19 @@ func buildCardGatewayRoutes(src map[string]cardGatewayRouteConfig) map[string]or
|
|||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func buildFeeLedgerAccounts(src map[string]string) map[string]string {
|
||||||
|
if len(src) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
result := make(map[string]string, len(src))
|
||||||
|
for key, account := range src {
|
||||||
|
k := strings.ToLower(strings.TrimSpace(key))
|
||||||
|
v := strings.TrimSpace(account)
|
||||||
|
if k == "" || v == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
result[k] = v
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|||||||
@@ -383,6 +383,22 @@ func feeBreakdownFromQuote(quote *orchestratorv1.PaymentQuote) []*chainv1.Servic
|
|||||||
return breakdown
|
return breakdown
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func assignLedgerAccounts(lines []*feesv1.DerivedPostingLine, account string) []*feesv1.DerivedPostingLine {
|
||||||
|
if account == "" || len(lines) == 0 {
|
||||||
|
return lines
|
||||||
|
}
|
||||||
|
for _, line := range lines {
|
||||||
|
if line == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if strings.TrimSpace(line.GetLedgerAccountRef()) != "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
line.LedgerAccountRef = account
|
||||||
|
}
|
||||||
|
return lines
|
||||||
|
}
|
||||||
|
|
||||||
func moneyEquals(a, b *moneyv1.Money) bool {
|
func moneyEquals(a, b *moneyv1.Money) bool {
|
||||||
if a == nil || b == nil {
|
if a == nil || b == nil {
|
||||||
return false
|
return false
|
||||||
|
|||||||
@@ -113,6 +113,24 @@ func WithCardGatewayRoutes(routes map[string]CardGatewayRoute) Option {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithFeeLedgerAccounts maps gateway identifiers to ledger accounts used for fees.
|
||||||
|
func WithFeeLedgerAccounts(routes map[string]string) Option {
|
||||||
|
return func(s *Service) {
|
||||||
|
if len(routes) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s.deps.feeLedgerAccounts = make(map[string]string, len(routes))
|
||||||
|
for k, v := range routes {
|
||||||
|
key := strings.ToLower(strings.TrimSpace(k))
|
||||||
|
val := strings.TrimSpace(v)
|
||||||
|
if key == "" || val == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
s.deps.feeLedgerAccounts[key] = val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// WithClock overrides the default clock.
|
// WithClock overrides the default clock.
|
||||||
func WithClock(clock clockpkg.Clock) Option {
|
func WithClock(clock clockpkg.Clock) Option {
|
||||||
return func(s *Service) {
|
return func(s *Service) {
|
||||||
|
|||||||
@@ -52,7 +52,9 @@ func (s *Service) buildPaymentQuote(ctx context.Context, orgRef string, req *orc
|
|||||||
} else if amount != nil {
|
} else if amount != nil {
|
||||||
feeCurrency = amount.GetCurrency()
|
feeCurrency = amount.GetCurrency()
|
||||||
}
|
}
|
||||||
feeTotal := extractFeeTotal(feeQuote.GetLines(), feeCurrency)
|
feeLines := cloneFeeLines(feeQuote.GetLines())
|
||||||
|
s.assignFeeLedgerAccounts(intent, feeLines)
|
||||||
|
feeTotal := extractFeeTotal(feeLines, feeCurrency)
|
||||||
|
|
||||||
var networkFee *chainv1.EstimateTransferFeeResponse
|
var networkFee *chainv1.EstimateTransferFeeResponse
|
||||||
if shouldEstimateNetworkFee(intent) {
|
if shouldEstimateNetworkFee(intent) {
|
||||||
@@ -69,7 +71,7 @@ func (s *Service) buildPaymentQuote(ctx context.Context, orgRef string, req *orc
|
|||||||
DebitAmount: debitAmount,
|
DebitAmount: debitAmount,
|
||||||
ExpectedSettlementAmount: settlementAmount,
|
ExpectedSettlementAmount: settlementAmount,
|
||||||
ExpectedFeeTotal: feeTotal,
|
ExpectedFeeTotal: feeTotal,
|
||||||
FeeLines: cloneFeeLines(feeQuote.GetLines()),
|
FeeLines: feeLines,
|
||||||
FeeRules: cloneFeeRules(feeQuote.GetApplied()),
|
FeeRules: cloneFeeRules(feeQuote.GetApplied()),
|
||||||
FxQuote: fxQuote,
|
FxQuote: fxQuote,
|
||||||
NetworkFee: networkFee,
|
NetworkFee: networkFee,
|
||||||
@@ -207,3 +209,53 @@ func (s *Service) requestFXQuote(ctx context.Context, orgRef string, req *orches
|
|||||||
}
|
}
|
||||||
return quoteToProto(quote), nil
|
return quoteToProto(quote), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Service) feeLedgerAccountForIntent(intent *orchestratorv1.PaymentIntent) string {
|
||||||
|
if intent == nil || len(s.deps.feeLedgerAccounts) == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
key := s.gatewayKeyFromIntent(intent)
|
||||||
|
if key == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return strings.TrimSpace(s.deps.feeLedgerAccounts[key])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) assignFeeLedgerAccounts(intent *orchestratorv1.PaymentIntent, lines []*feesv1.DerivedPostingLine) {
|
||||||
|
account := s.feeLedgerAccountForIntent(intent)
|
||||||
|
key := s.gatewayKeyFromIntent(intent)
|
||||||
|
|
||||||
|
missing := 0
|
||||||
|
for _, line := range lines {
|
||||||
|
if line == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if strings.TrimSpace(line.GetLedgerAccountRef()) == "" {
|
||||||
|
missing++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if missing == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if account == "" {
|
||||||
|
s.logger.Debug("no fee ledger account mapping found", zap.String("gateway", key), zap.Int("missing_lines", missing))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
assignLedgerAccounts(lines, account)
|
||||||
|
s.logger.Debug("applied fee ledger account mapping", zap.String("gateway", key), zap.String("ledger_account", account), zap.Int("lines", missing))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) gatewayKeyFromIntent(intent *orchestratorv1.PaymentIntent) string {
|
||||||
|
if intent == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
key := strings.TrimSpace(intent.GetAttributes()["gateway"])
|
||||||
|
if key == "" {
|
||||||
|
if dest := intent.GetDestination(); dest != nil && dest.GetCard() != nil {
|
||||||
|
key = defaultCardGateway
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return strings.ToLower(key)
|
||||||
|
}
|
||||||
|
|||||||
@@ -41,12 +41,13 @@ type Service struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type serviceDependencies struct {
|
type serviceDependencies struct {
|
||||||
fees feesDependency
|
fees feesDependency
|
||||||
ledger ledgerDependency
|
ledger ledgerDependency
|
||||||
gateway gatewayDependency
|
gateway gatewayDependency
|
||||||
oracle oracleDependency
|
oracle oracleDependency
|
||||||
mntx mntxDependency
|
mntx mntxDependency
|
||||||
cardRoutes map[string]CardGatewayRoute
|
cardRoutes map[string]CardGatewayRoute
|
||||||
|
feeLedgerAccounts map[string]string
|
||||||
}
|
}
|
||||||
|
|
||||||
type handlerSet struct {
|
type handlerSet struct {
|
||||||
|
|||||||
Reference in New Issue
Block a user