Skip to content

Commit b0b93fc

Browse files
authored
feat: update Starlark saga handlers for instrument-based field names (#1221)
* feat: update Starlark saga handlers for instrument-based field names Replace currency with instrument_code and account_identification with external_identifier across saga scripts and handler infrastructure: - handlers.yaml: add instrument_code param to position_keeping.initiate_log, financial_accounting.initiate_booking_log, capture_posting, and compensate_posting; keep currency as deprecated alias - deposit/withdrawal Starlark scripts: external_identifier replaces account_identification, instrument_code replaces currency - reconciliation_adjustment: pass instrument_code directly instead of via currency alias - dividend_distribution: use instrument_code throughout - stripe_payment_received: accept instrument_code with currency fallback - saga_handlers.go: accept instrument_code (preferred) or currency (deprecated alias) in all financial handlers; add optionalString helper - deposit/withdrawal orchestrators: pass external_identifier and instrument_code in saga RunnerInput - service_modules.go: propagate instrument_code in compensation params * fix: normalize instrument_code once after fallback in stripe saga Addresses CodeRabbit feedback: if both instrument_code and currency are present but empty, the previous logic left instrument_code blank. Now normalize once (strip + upper) after reading both fields, then default to GBP. Remove redundant .upper() calls at each handler invocation. --------- Co-authored-by: Ben Coombs <bjcoombs@users.noreply.github.com>
1 parent 6cd7171 commit b0b93fc

10 files changed

Lines changed: 142 additions & 83 deletions

File tree

services/control-plane/internal/stripe/sagas/stripe_payment_received.star

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@
2020
# - tenant_id: string - The tenant receiving the payment (e.g., "meridian-ops")
2121
# - party_id: string - The customer party identifier
2222
# - amount_cents: int - Payment amount in smallest currency unit (e.g., pence)
23-
# - currency: string - ISO 4217 currency code (e.g., "gbp")
23+
# - instrument_code: string - Instrument code (e.g., "GBP"). Replaces currency field.
24+
# - currency: string - Deprecated: use instrument_code instead. ISO 4217 currency code (e.g., "gbp")
2425
# - charge_id: string - Stripe Charge ID for reconciliation
2526
# - payment_intent_id: string - Stripe PaymentIntent ID
2627
# - stripe_event_id: string - Original Stripe event ID for audit trail
@@ -35,7 +36,12 @@ def execute_stripe_payment_received():
3536
tenant_id = input_data["tenant_id"]
3637
party_id = input_data["party_id"]
3738
amount_cents = input_data["amount_cents"]
38-
currency = input_data.get("currency", "GBP")
39+
# Accept instrument_code (preferred) or currency (deprecated alias).
40+
# Normalize once: strip whitespace, uppercase, default to "GBP".
41+
instrument_code = input_data.get("instrument_code", "") or input_data.get("currency", "")
42+
instrument_code = instrument_code.strip().upper()
43+
if instrument_code == "":
44+
instrument_code = "GBP"
3945
charge_id = input_data["charge_id"]
4046
payment_intent_id = input_data.get("payment_intent_id", "")
4147
stripe_event_id = input_data.get("stripe_event_id", "")
@@ -54,7 +60,7 @@ def execute_stripe_payment_received():
5460
debit_result = position_keeping.initiate_log(
5561
account_id=nostro_account,
5662
amount=amount,
57-
currency=currency.upper(),
63+
instrument_code=instrument_code,
5864
direction="DEBIT",
5965
description="Stripe payment received: " + charge_id,
6066
external_reference_id=charge_id,
@@ -66,7 +72,7 @@ def execute_stripe_payment_received():
6672
credit_result = position_keeping.initiate_log(
6773
account_id=prepaid_account,
6874
amount=amount,
69-
currency=currency.upper(),
75+
instrument_code=instrument_code,
7076
direction="CREDIT",
7177
description="Payment from Stripe: " + charge_id,
7278
external_reference_id=charge_id,
@@ -78,7 +84,7 @@ def execute_stripe_payment_received():
7884
"party_id": party_id,
7985
"prepaid_log_id": credit_result["log_id"],
8086
"amount_cents": amount_cents,
81-
"currency": currency,
87+
"instrument_code": instrument_code,
8288
"charge_id": charge_id,
8389
"payment_intent_id": payment_intent_id,
8490
"stripe_event_id": stripe_event_id,

services/current-account/service/deposit_orchestrator.go

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -180,13 +180,13 @@ func (o *DepositOrchestrator) Orchestrate(ctx context.Context, account domain.Cu
180180
SagaExecutionID: uuid.New(),
181181
CorrelationID: correlationUUID,
182182
Input: map[string]interface{}{
183-
"account_id": account.AccountID(),
184-
"account_identification": account.ExternalIdentifier(),
185-
"amount": amount.Amount().String(), // Decimal as string
186-
"currency": string(amount.Currency()),
187-
"transaction_id": transactionID,
188-
"clearing_account_id": clearingAccountID,
189-
"attributes": attributes,
183+
"account_id": account.AccountID(),
184+
"external_identifier": account.ExternalIdentifier(),
185+
"amount": amount.Amount().String(), // Decimal as string
186+
"instrument_code": account.InstrumentCode(),
187+
"transaction_id": transactionID,
188+
"clearing_account_id": clearingAccountID,
189+
"attributes": attributes,
190190
},
191191
}
192192

services/current-account/service/saga_handlers.go

Lines changed: 49 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,8 @@ func decimalToMoneyAmount(amount decimal.Decimal, currency string) *commonpb.Mon
190190
// Required params:
191191
// - account_id: string - The account identifier
192192
// - amount: decimal.Decimal - The transaction amount
193-
// - currency: string - Currency code (e.g., "GBP")
193+
// - instrument_code: string - Instrument code (e.g., "GBP", "kWh"). Replaces currency.
194+
// - currency: string - Deprecated: use instrument_code instead.
194195
// - direction: string - "DEBIT" or "CREDIT"
195196
// - transaction_id: string - The saga transaction ID
196197
//
@@ -224,9 +225,13 @@ func currentAccountPositionKeepingInitiateLog(ctx *saga.StarlarkContext, params
224225
return nil, wrapHandlerError(handlerName, err)
225226
}
226227

227-
currency, err := requireString(params, "currency")
228-
if err != nil {
229-
return nil, wrapHandlerError(handlerName, err)
228+
// Accept instrument_code (preferred) or currency (deprecated alias)
229+
currency := optionalString(params, "instrument_code")
230+
if currency == "" {
231+
currency = optionalString(params, "currency")
232+
}
233+
if currency == "" {
234+
return nil, wrapHandlerError(handlerName, fmt.Errorf("%w: instrument_code", errMissingParameter))
230235
}
231236

232237
direction, err := requireString(params, "direction")
@@ -423,7 +428,8 @@ func currentAccountPositionKeepingCancelLog(ctx *saga.StarlarkContext, params ma
423428
//
424429
// Required params:
425430
// - account_id: string - The account identifier
426-
// - currency: string - Currency code
431+
// - instrument_code: string - Instrument code (e.g., "GBP", "kWh"). Replaces currency.
432+
// - currency: string - Deprecated: use instrument_code instead.
427433
// - transaction_id: string - The saga transaction ID
428434
// - transaction_type: string - "WITHDRAWAL" or "DEPOSIT"
429435
func currentAccountFinAcctInitiateBookingLog(ctx *saga.StarlarkContext, params map[string]any) (any, error) {
@@ -439,9 +445,13 @@ func currentAccountFinAcctInitiateBookingLog(ctx *saga.StarlarkContext, params m
439445
return nil, wrapHandlerError(handlerName, err)
440446
}
441447

442-
currency, err := requireString(params, "currency")
443-
if err != nil {
444-
return nil, wrapHandlerError(handlerName, err)
448+
// Accept instrument_code (preferred) or currency (deprecated alias)
449+
currency := optionalString(params, "instrument_code")
450+
if currency == "" {
451+
currency = optionalString(params, "currency")
452+
}
453+
if currency == "" {
454+
return nil, wrapHandlerError(handlerName, fmt.Errorf("%w: instrument_code", errMissingParameter))
445455
}
446456

447457
transactionID, err := requireString(params, "transaction_id")
@@ -496,7 +506,8 @@ func currentAccountFinAcctInitiateBookingLog(ctx *saga.StarlarkContext, params m
496506
// - booking_log_id: string - The booking log ID
497507
// - account_id: string - The account to post to
498508
// - amount: decimal.Decimal - The posting amount
499-
// - currency: string - Currency code
509+
// - instrument_code: string - Instrument code (e.g., "GBP", "kWh"). Replaces currency.
510+
// - currency: string - Deprecated: use instrument_code instead.
500511
// - direction: string - "DEBIT" or "CREDIT"
501512
// - transaction_id: string - The saga transaction ID
502513
// - posting_type: string - "debit" or "credit" (for idempotency key suffix)
@@ -523,9 +534,13 @@ func currentAccountFinAcctCapturePosting(ctx *saga.StarlarkContext, params map[s
523534
return nil, wrapHandlerError(handlerName, err)
524535
}
525536

526-
currency, err := requireString(params, "currency")
527-
if err != nil {
528-
return nil, wrapHandlerError(handlerName, err)
537+
// Accept instrument_code (preferred) or currency (deprecated alias)
538+
currency := optionalString(params, "instrument_code")
539+
if currency == "" {
540+
currency = optionalString(params, "currency")
541+
}
542+
if currency == "" {
543+
return nil, wrapHandlerError(handlerName, fmt.Errorf("%w: instrument_code", errMissingParameter))
529544
}
530545

531546
direction, err := requireString(params, "direction")
@@ -657,7 +672,8 @@ func currentAccountFinAcctUpdateBookingLog(ctx *saga.StarlarkContext, params map
657672
// - booking_log_id: string - The booking log ID
658673
// - account_id: string - The account to post to
659674
// - amount: decimal.Decimal - The posting amount
660-
// - currency: string - Currency code
675+
// - instrument_code: string - Instrument code (e.g., "GBP", "kWh"). Replaces currency.
676+
// - currency: string - Deprecated: use instrument_code instead.
661677
// - direction: string - "DEBIT" or "CREDIT" (opposite of original)
662678
// - transaction_id: string - The saga transaction ID
663679
// - posting_type: string - "debit" or "credit" (original posting type being compensated)
@@ -684,9 +700,13 @@ func currentAccountFinAcctCompensatePosting(ctx *saga.StarlarkContext, params ma
684700
return nil, wrapHandlerError(handlerName, err)
685701
}
686702

687-
currency, err := requireString(params, "currency")
688-
if err != nil {
689-
return nil, wrapHandlerError(handlerName, err)
703+
// Accept instrument_code (preferred) or currency (deprecated alias)
704+
currency := optionalString(params, "instrument_code")
705+
if currency == "" {
706+
currency = optionalString(params, "currency")
707+
}
708+
if currency == "" {
709+
return nil, wrapHandlerError(handlerName, fmt.Errorf("%w: instrument_code", errMissingParameter))
690710
}
691711

692712
direction, err := requireString(params, "direction")
@@ -811,6 +831,19 @@ func currentAccountRepositorySave(ctx *saga.StarlarkContext, params map[string]a
811831

812832
// Helper functions for parameter extraction
813833

834+
// optionalString extracts a string parameter, returning empty string if absent or wrong type.
835+
func optionalString(params map[string]any, key string) string {
836+
val, ok := params[key]
837+
if !ok || val == nil {
838+
return ""
839+
}
840+
str, ok := val.(string)
841+
if !ok {
842+
return ""
843+
}
844+
return str
845+
}
846+
814847
func requireString(params map[string]any, key string) (string, error) {
815848
val, ok := params[key]
816849
if !ok {

services/current-account/service/withdrawal_orchestrator.go

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -182,13 +182,13 @@ func (o *WithdrawalOrchestrator) Orchestrate(ctx context.Context, account domain
182182
SagaExecutionID: uuid.New(),
183183
CorrelationID: correlationUUID,
184184
Input: map[string]interface{}{
185-
"account_id": account.AccountID(),
186-
"account_identification": account.ExternalIdentifier(),
187-
"amount": amount.Amount().String(), // Decimal as string
188-
"currency": string(amount.Currency()),
189-
"transaction_id": transactionID,
190-
"clearing_account_id": withdrawalClearingAccountID,
191-
"attributes": attributes,
185+
"account_id": account.AccountID(),
186+
"external_identifier": account.ExternalIdentifier(),
187+
"amount": amount.Amount().String(), // Decimal as string
188+
"instrument_code": account.InstrumentCode(),
189+
"transaction_id": transactionID,
190+
"clearing_account_id": withdrawalClearingAccountID,
191+
"attributes": attributes,
192192
},
193193
}
194194

services/reference-data/saga/defaults/deposit/v1.0.0.star

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# Saga: current_account_deposit
22
# Version: 1.0.0
33
# Previous: none
4-
# Changed: Migrated from invoke_handler() to typed service modules
4+
# Changed: Updated field names: account_identification -> external_identifier, currency -> instrument_code
55
# Author: Platform Team
66
# Date: 2026-01-27
77
#
@@ -22,9 +22,9 @@
2222
#
2323
# Input data (provided via input_data dictionary):
2424
# - account_id: string - Account identifier
25-
# - account_identification: string - Account identification for external services
25+
# - external_identifier: string - External account identifier (e.g., IBAN)
2626
# - amount: string - Decimal amount as string (e.g., "100.50")
27-
# - currency: string - Currency code (e.g., "GBP")
27+
# - instrument_code: string - Instrument code (e.g., "GBP", "kWh")
2828
# - transaction_id: string - Unique transaction identifier
2929
# - clearing_account_id: string - Clearing account for double-entry (optional)
3030

@@ -35,31 +35,31 @@ deposit_saga = saga(name="current_account_deposit")
3535
def execute_deposit():
3636
# Extract input data
3737
account_id = input_data["account_id"]
38-
account_identification = input_data["account_identification"]
38+
external_identifier = input_data["external_identifier"]
3939
amount = Decimal(input_data["amount"])
40-
currency = input_data["currency"]
40+
instrument_code = input_data["instrument_code"]
4141
transaction_id = input_data["transaction_id"]
4242
clearing_account_id = input_data.get("clearing_account_id", "")
43-
43+
4444
# Step 1: Log position in PositionKeeping service with CREDIT direction
4545
step(name="log_position")
4646
log_position_result = position_keeping.initiate_log(
47-
position_id=account_identification,
47+
position_id=external_identifier,
4848
amount=amount,
49-
currency=currency,
49+
instrument_code=instrument_code,
5050
direction="CREDIT",
5151
transaction_id=transaction_id,
5252
)
53-
53+
5454
# Step 2: Initiate booking log in FinancialAccounting service
5555
step(name="initiate_booking_log")
5656
booking_log_result = financial_accounting.initiate_booking_log(
5757
account_id=account_id,
58-
currency=currency,
58+
instrument_code=instrument_code,
5959
transaction_id=transaction_id,
6060
transaction_type="DEPOSIT",
6161
)
62-
62+
6363
# Step 3: Capture DEBIT posting to clearing account (if double-entry enabled)
6464
# For deposits: DEBIT the clearing account (where funds come from)
6565
if clearing_account_id != "":
@@ -68,38 +68,38 @@ def execute_deposit():
6868
booking_log_id=booking_log_result.booking_log_id,
6969
account_id=clearing_account_id,
7070
amount=amount,
71-
currency=currency,
71+
instrument_code=instrument_code,
7272
direction="DEBIT",
7373
transaction_id=transaction_id,
7474
posting_type="debit",
7575
)
76-
76+
7777
# Step 4: Capture CREDIT posting to customer account
7878
step(name="capture_credit_posting")
7979
credit_result = financial_accounting.capture_posting(
8080
booking_log_id=booking_log_result.booking_log_id,
8181
account_id=account_id,
8282
amount=amount,
83-
currency=currency,
83+
instrument_code=instrument_code,
8484
direction="CREDIT",
8585
transaction_id=transaction_id,
8686
posting_type="credit",
8787
)
88-
88+
8989
# Step 5: Finalize booking log (transition to POSTED)
9090
step(name="finalize_booking_log")
9191
finalize_result = financial_accounting.update_booking_log(
9292
booking_log_id=booking_log_result.booking_log_id,
9393
status="POSTED",
9494
)
95-
95+
9696
# Step 6: Save account metadata
9797
step(name="save_account")
9898
save_result = current_account.save(
9999
account_id=account_id,
100100
transaction_id=transaction_id,
101101
)
102-
102+
103103
# Output the saga result
104104
result = {
105105
"status": "COMPLETED",

services/reference-data/saga/defaults/dividend_distribution/v1.0.0.star

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
# Input data (provided via input_data dictionary):
2222
# - org_id: string - Syndicate organization party ID
2323
# - total_amount: string - Total dividend amount as decimal string
24-
# - currency: string - Currency code (e.g., "GBP")
24+
# - instrument_code: string - Instrument code (e.g., "GBP", "kWh")
2525
# - transaction_id: string - Unique transaction identifier
2626

2727
# Define the dividend distribution saga
@@ -31,7 +31,7 @@ def execute_distribution():
3131
# Extract input data
3232
org_id = input_data["org_id"]
3333
total_amount = Decimal(input_data["total_amount"])
34-
currency = input_data["currency"]
34+
instrument_code = input_data["instrument_code"]
3535
transaction_id = input_data["transaction_id"]
3636

3737
# Step 1: List all active syndicate participants
@@ -64,7 +64,7 @@ def execute_distribution():
6464
account_ref = build_org_account_ref(
6565
party_id=party_id,
6666
org_id=org_id,
67-
currency=currency,
67+
instrument_code=instrument_code,
6868
)
6969

7070
# Resolve the org-scoped account
@@ -75,7 +75,7 @@ def execute_distribution():
7575
log_result = position_keeping.initiate_log(
7676
position_id=account_id,
7777
amount=participant_amount,
78-
currency=currency,
78+
instrument_code=instrument_code,
7979
direction="CREDIT",
8080
transaction_id=transaction_id,
8181
)

services/reference-data/saga/defaults/reconciliation_adjustment/v1.0.0.star

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ def execute_adjustment():
5151
step(name="initiate_booking")
5252
booking_result = financial_accounting.initiate_booking_log(
5353
account_id=account_id,
54-
currency=instrument_code,
54+
instrument_code=instrument_code,
5555
transaction_id=transaction_id,
5656
transaction_type="RECONCILIATION_ADJUSTMENT",
5757
)
@@ -62,7 +62,7 @@ def execute_adjustment():
6262
booking_log_id=booking_result.booking_log_id,
6363
account_id=account_id,
6464
amount=adjustment_amount,
65-
currency=instrument_code,
65+
instrument_code=instrument_code,
6666
direction="DEBIT",
6767
transaction_id=transaction_id,
6868
posting_type="debit",
@@ -74,7 +74,7 @@ def execute_adjustment():
7474
booking_log_id=booking_result.booking_log_id,
7575
account_id=account_id,
7676
amount=adjustment_amount,
77-
currency=instrument_code,
77+
instrument_code=instrument_code,
7878
direction="CREDIT",
7979
transaction_id=transaction_id,
8080
posting_type="credit",

0 commit comments

Comments
 (0)