What ISO 20022 Message Pays a Merchant for Two Invoices — With Full Lifecycle Tracking?

The Question

“What type should I use for a remittance? I need to send a payment to a merchant paying for two invoices. Just make up values and show me an example. Include optional fields. Show notes on how it might deviate for different use cases. Then, show me the structure of the database model used to persist everything tracking the state of the payment through its lifecycle. Make sure the database names correspond to the ISO domain model names. Show me a state diagram or business process diagram showing how processing happens during the lifetime of a single payment. Show the C# implementation of a MassTransit Saga State Machine.”

One question. No spec PDFs. No schema browser. Watch what happens next.


Step 1 — The MCP Server Identifies the Right Message

The AI queries the Beneficial Strategies ISO 20022 MCP Server live. It navigates the business process catalogue to the Payments Initiation area and pulls the latest message set.

The server returns:

Message Version Name Role
pain.001 001.001.12 CustomerCreditTransferInitiationV12 Initiating party instructs their bank to move funds to a creditor

This is the right answer. pain.001 is the canonical ISO 20022 message a company sends to its bank to initiate a credit transfer. It is explicitly designed to carry remittance information — structured references back to the invoices being settled — so the merchant’s accounts receivable system can automatically match the payment to the right open items.

The MCP server then drills into the message structure. It navigates:

pain.001.001.12
  → PaymentInformation (PaymentInstruction44)
    → CreditTransferTransactionInformation (CreditTransferTransaction61)
      → RemittanceInformation (RemittanceInformation22)
        → Structured (StructuredRemittanceInformation18)
          → ReferredDocumentInformation (ReferredDocumentInformation8)   ← one per invoice
          → ReferredDocumentAmount (RemittanceAmount4)
          → CreditorReferenceInformation
          → AdditionalRemittanceInformation

The spec confirms that ReferredDocumentInformation can repeat — one entry per invoice. Each entry carries a document type code (CINV = Commercial Invoice), an invoice number, and the related date. The total payment amount sits at the transaction level; the remittance block tells the merchant which invoices it covers.


Step 2 — Two Quick Clarifying Questions

Before generating anything, the AI asks:

“Should the C# model use records (immutable, init-only) or classes with mutable properties? Records are better for value-object semantics; classes are easier if you need ORM entity tracking.”

Answer: Classes — this will be persisted via EF Core.

“Should I include a UETR (Unique End-to-End Transaction Reference, ISO 20022 UUID field)? It’s optional in the base spec but required by SWIFT gpi and recommended for lifecycle correlation.”

Answer: Yes, include it.

Those two decisions shape the database schema and the saga correlation strategy. Everything else is automatic.


Step 3 — Everything Is Generated Automatically

Message Structure

classDiagram
    direction TB
    class CustomerCreditTransferInitiationV12 {
        XmlTag: CstmrCdtTrfInitn
    }
    class GroupHeader93 {
        MessageIdentification: Max35Text
        CreationDateTime: ISODateTime
        NumberOfTransactions: Max15NumericText
        ControlSum: DecimalNumber
        InitiatingParty: PartyIdentification135
    }
    class PaymentInstruction44 {
        PaymentInformationIdentification: Max35Text
        PaymentMethod: PaymentMethod3Code
        RequestedExecutionDate: DateAndDateTime2Choice
        Debtor: PartyIdentification135
        DebtorAccount: CashAccount40
        DebtorAgent: BranchAndFinancialInstitutionIdentification6
    }
    class CreditTransferTransaction61 {
        PaymentIdentification: PaymentIdentification6
        InstructedAmount: ActiveOrHistoricCurrencyAndAmount
        ChargeBearer: ChargeBearerType1Code
        Creditor: PartyIdentification135
        CreditorAccount: CashAccount40
        CreditorAgent: BranchAndFinancialInstitutionIdentification6
        Purpose: Purpose2Choice
    }
    class RemittanceInformation22 {
        Unstructured: Max140Text [0..1]
    }
    class StructuredRemittanceInformation18 {
        AdditionalRemittanceInformation: Max140Text [0..3]
    }
    class ReferredDocumentInformation8 {
        Type: ReferredDocumentType4
        Number: Max35Text
        RelatedDate: DateAndType1
    }
    class RemittanceAmount4 {
        RemittanceAmountAndType: RemittanceAmountAndType2
        AdjustmentAmountAndReason: DocumentAdjustment1
    }
    CustomerCreditTransferInitiationV12 *-- "1" GroupHeader93 : GrpHdr
    CustomerCreditTransferInitiationV12 *-- "1..*" PaymentInstruction44 : PmtInf
    PaymentInstruction44 *-- "1..*" CreditTransferTransaction61 : CdtTrfTxInf
    CreditTransferTransaction61 *-- "0..1" RemittanceInformation22 : RmtInf
    RemittanceInformation22 *-- "0..*" StructuredRemittanceInformation18 : Strd
    StructuredRemittanceInformation18 *-- "0..*" ReferredDocumentInformation8 : RfrdDocInf
    StructuredRemittanceInformation18 *-- "0..1" RemittanceAmount4 : RfrdDocAmt
  

Sample XML

Two invoices — $12,500 for office furniture and $3,750 for printer supplies — paid in a single pain.001. All optional remittance fields populated:

<?xml version="1.0" encoding="UTF-8"?>
<Document xmlns="urn:iso:std:iso:20022:tech:xsd:pain.001.001.12">
  <CstmrCdtTrfInitn>

    <!-- GroupHeader: message-level envelope -->
    <GrpHdr>
      <MsgId>ACME-PAY-2026-03-25-001</MsgId>
      <CreDtTm>2026-03-25T09:15:00</CreDtTm>
      <NbOfTxs>1</NbOfTxs>
      <CtrlSum>16250.00</CtrlSum>
      <InitgPty>
        <Nm>Acme Corp</Nm>
        <Id>
          <OrgId>
            <AnyBIC>ACMEUSNYXXX</AnyBIC>
          </OrgId>
        </Id>
      </InitgPty>
    </GrpHdr>

    <!-- PaymentInformation: debit side — who pays, from which account, when -->
    <PmtInf>
      <PmtInfId>PMTINF-20260325-0001</PmtInfId>
      <PmtMtd>TRF</PmtMtd>           <!-- TRF = Credit Transfer -->
      <BtchBookg>false</BtchBookg>   <!-- OPTIONAL: individual booking per transaction -->
      <NbOfTxs>1</NbOfTxs>
      <CtrlSum>16250.00</CtrlSum>
      <PmtTpInf>
        <!-- OPTIONAL: payment type hints -->
        <InstrPrty>NORM</InstrPrty>  <!-- Normal priority -->
        <SvcLvl>
          <Cd>SEPA</Cd>              <!-- SEPA Credit Transfer (use NURG/URGP for other rails) -->
        </SvcLvl>
        <CtgyPurp>
          <Cd>SUPP</Cd>              <!-- Supplier Payment -->
        </CtgyPurp>
      </PmtTpInf>
      <ReqdExctnDt>
        <Dt>2026-03-27</Dt>
      </ReqdExctnDt>
      <!-- OPTIONAL: future-date pooling adjustment -->
      <PoolgAdjstmntDt>2026-03-27</PoolgAdjstmntDt>
      <Dbtr>
        <Nm>Acme Corp</Nm>
        <PstlAdr>
          <Ctry>US</Ctry>
          <AdrLine>100 Commerce Blvd, New York, NY 10001</AdrLine>
        </PstlAdr>
      </Dbtr>
      <DbtrAcct>
        <Id><IBAN>US29ACME0001234500001234</IBAN></Id>
        <Ccy>USD</Ccy>
      </DbtrAcct>
      <DbtrAgt>
        <FinInstnId>
          <BICFI>CHASUS33XXX</BICFI>  <!-- JPMorgan Chase -->
        </FinInstnId>
      </DbtrAgt>
      <!-- OPTIONAL: instruction to debtor agent -->
      <InstrForDbtrAgt>HOLD</InstrForDbtrAgt>

      <!-- CreditTransferTransactionInformation: credit side + remittance -->
      <CdtTrfTxInf>
        <PmtId>
          <InstrId>INSTR-20260325-0001</InstrId>
          <EndToEndId>E2E-ACME-APEX-20260325-001</EndToEndId>
          <!-- OPTIONAL: UETR (SWIFT gpi / recommended for lifecycle tracking) -->
          <UETR>550e8400-e29b-41d4-a716-446655440000</UETR>
        </PmtId>
        <Amt>
          <InstdAmt Ccy="USD">16250.00</InstdAmt>
        </Amt>
        <ChrgBr>SLEV</ChrgBr>  <!-- Follow service-level charge rules -->
        <!-- OPTIONAL: intermediary agent (correspondent bank) -->
        <IntrmyAgt1>
          <FinInstnId>
            <BICFI>BNPAFRPPXXX</BICFI>
          </FinInstnId>
        </IntrmyAgt1>
        <CdtrAgt>
          <FinInstnId>
            <BICFI>GEBABEBB XXX</BICFI>  <!-- BNP Paribas Fortis Belgium -->
          </FinInstnId>
        </CdtrAgt>
        <Cdtr>
          <Nm>Apex Supplies Ltd</Nm>
          <PstlAdr>
            <Ctry>BE</Ctry>
            <AdrLine>45 Rue du Commerce, B-1000 Brussels</AdrLine>
          </PstlAdr>
        </Cdtr>
        <CdtrAcct>
          <Id><IBAN>BE71096123456769</IBAN></Id>
        </CdtrAcct>
        <!-- OPTIONAL: ultimate creditor (when payee differs from account holder) -->
        <UltmtCdtr>
          <Nm>Apex Supplies Ltd — Accounts Receivable</Nm>
        </UltmtCdtr>
        <Purp>
          <Cd>SUPP</Cd>  <!-- Supplier Payment -->
        </Purp>

        <!-- RemittanceInformation: tell Apex which invoices this covers -->
        <RmtInf>

          <!-- Structured block 1: Invoice INV-2026-0441 -->
          <Strd>
            <RfrdDocInf>
              <Tp>
                <CdOrPrtry>
                  <Cd>CINV</Cd>  <!-- Commercial Invoice -->
                </CdOrPrtry>
              </Tp>
              <Nb>INV-2026-0441</Nb>
              <RltdDt>
                <Tp>
                  <Cd>INDA</Cd>  <!-- Invoice Date -->
                </Tp>
                <Dt>2026-02-28</Dt>
              </RltdDt>
            </RfrdDocInf>
            <RfrdDocAmt>
              <DuePyblAmt Ccy="USD">12500.00</DuePyblAmt>
            </RfrdDocAmt>
            <CdtrRefInf>
              <Tp>
                <CdOrPrtry>
                  <Cd>SCOR</Cd>  <!-- Structured Creditor Reference -->
                </CdOrPrtry>
              </Tp>
              <Ref>RF18539007547034</Ref>  <!-- ISO 11649 creditor reference -->
            </CdtrRefInf>
            <AddtlRmtInf>Office furniture — PO-2026-0112</AddtlRmtInf>
          </Strd>

          <!-- Structured block 2: Invoice INV-2026-0458 -->
          <Strd>
            <RfrdDocInf>
              <Tp>
                <CdOrPrtry>
                  <Cd>CINV</Cd>
                </CdOrPrtry>
              </Tp>
              <Nb>INV-2026-0458</Nb>
              <RltdDt>
                <Tp>
                  <Cd>INDA</Cd>
                </Tp>
                <Dt>2026-03-10</Dt>
              </RltdDt>
            </RfrdDocInf>
            <RfrdDocAmt>
              <DuePyblAmt Ccy="USD">3750.00</DuePyblAmt>
            </RfrdDocAmt>
            <CdtrRefInf>
              <Tp>
                <CdOrPrtry>
                  <Cd>SCOR</Cd>
                </CdOrPrtry>
              </Tp>
              <Ref>RF71234400112987</Ref>
            </CdtrRefInf>
            <AddtlRmtInf>Printer supplies — PO-2026-0118</AddtlRmtInf>
          </Strd>

        </RmtInf>
      </CdtTrfTxInf>
    </PmtInf>

  </CstmrCdtTrfInitn>
</Document>

Deviation Notes

Scenario What changes
Domestic US ACH SvcLvl/CdACH; IBANOthr account ID with routing + account number; BICFI → ABA routing number in ClrSysMmbId
SWIFT gpi cross-border Add SvcLvl/Cd = G001; UETR is mandatory (not optional); intermediary agents often required
Single invoice, no structured remittance Replace <Strd> blocks with <Ustrd>INV-2026-0441</Ustrd> — simpler, but machine-matching is lost
Credit note offsetting an invoice Add a second RfrdDocInf with Cd = CREN (Credit Note) and a negative AdjstmntAmtAndRsn in RfrdDocAmt
Batch booking Set <BtchBookg>true</BtchBookg> and add multiple CdtTrfTxInf blocks — one per transaction; the bank books a single net debit
Requested collection date Replace ReqdExctnDt with ReqdColltnDt if debtor agent is collecting on behalf
FedNow / RTP instant payment SvcLvl/CdNURG; execution is immediate; structured remittance carries through via ISO 20022 native format

Database Model

Every table name and column name is taken directly from the ISO 20022 component hierarchy returned by the MCP server. The saga correlation key is EndToEndIdentification — present in both pain.001 and every downstream pain.002 status report.

erDiagram
    CreditTransferTransaction {
        uuid    Id PK
        string  InstructionIdentification
        string  EndToEndIdentification UK
        uuid    UETR UK
        string  InstructedAmount
        string  InstructedCurrency
        string  ChargeBearer
        string  Purpose
        date    RequestedExecutionDate
        string  Status
        datetime CreatedAt
        datetime UpdatedAt
    }
    PaymentInstruction {
        uuid    Id PK
        string  PaymentInformationIdentification UK
        string  PaymentMethod
        date    RequestedExecutionDate
        string  DebtorName
        string  DebtorIBAN
        string  DebtorAgentBIC
        string  MessageIdentification
        datetime CreatedAt
    }
    StructuredRemittanceInformation {
        uuid    Id PK
        uuid    CreditTransferTransactionId FK
        int     SequenceNumber
        string  AdditionalRemittanceInformation
    }
    ReferredDocumentInformation {
        uuid    Id PK
        uuid    StructuredRemittanceInformationId FK
        string  DocumentTypeCode
        string  Number
        date    RelatedDate
        string  RelatedDateTypeCode
        string  CreditorReference
        string  CreditorReferenceTypeCode
    }
    RemittanceAmount {
        uuid    Id PK
        uuid    ReferredDocumentInformationId FK
        decimal DuePayableAmount
        string  Currency
        decimal AdjustmentAmount
        string  AdjustmentReasonCode
    }
    PaymentStatusReport {
        uuid    Id PK
        uuid    CreditTransferTransactionId FK
        string  OriginalEndToEndIdentification
        string  TransactionStatus
        string  StatusReasonCode
        string  StatusReasonAdditionalInformation
        datetime AcceptanceDateTime
        datetime CreatedAt
    }

    PaymentInstruction          ||--o{ CreditTransferTransaction          : "CdtTrfTxInf"
    CreditTransferTransaction   ||--o{ StructuredRemittanceInformation    : "RmtInf/Strd"
    StructuredRemittanceInformation ||--o{ ReferredDocumentInformation    : "RfrdDocInf"
    ReferredDocumentInformation ||--o| RemittanceAmount                   : "RfrdDocAmt"
    CreditTransferTransaction   ||--o{ PaymentStatusReport                : "pain.002"
  

Payment Lifecycle State Diagram

The saga tracks a single CreditTransferTransaction from initiation through settlement, consuming pain.002 status reports from the bank at each step.

stateDiagram-v2
    [*] --> Created : PaymentInitiated

    Created --> Submitted : pain.001 accepted by bank API
    Submitted --> Acknowledged : pain.002 ACCP (Accepted Customer Profile)
    Acknowledged --> Processing : pain.002 ACWC (Accepted With Change) or ACSP
    Processing --> Cleared : pain.002 ACSC (Accepted Settlement Completed)
    Cleared --> Settled : Interbank credit confirmed
    Settled --> Completed : Merchant AR reconciled
    Completed --> [*]

    Submitted --> Rejected : pain.002 RJCT
    Acknowledged --> Rejected : pain.002 RJCT
    Processing --> Rejected : pain.002 RJCT
    Rejected --> [*]

    Cleared --> Returned : pacs.004 Return received
    Settled --> Returned : pacs.004 Return received
    Returned --> [*]

    note right of Acknowledged
        pain.002 AcceptanceDateTime
        recorded here for SLA tracking
    end note

    note right of Cleared
        UETR enables gpi tracker
        lookup at this stage
    end note
  

C# MassTransit Saga State Machine

Every state, event, and correlation property maps directly to the ISO 20022 domain model. The EndToEndIdentification is the saga correlation key — it threads through pain.001, every pain.002, and can be used to pull the UETR for gpi tracking.

using MassTransit;

namespace Payments.Sagas;

// ── Saga state (persisted by EF Core / Redis / etc.) ──────────────────────────

public class CreditTransferSagaState : SagaStateMachineInstance, ISagaVersion
{
    public Guid CorrelationId { get; set; }                 // MassTransit internal key
    public int  Version       { get; set; }                 // optimistic concurrency

    public string CurrentState { get; set; } = null!;

    // ISO 20022 PaymentIdentification fields
    public string InstructionIdentification  { get; set; } = null!;
    public string EndToEndIdentification     { get; set; } = null!;  // correlation key
    public Guid?  UETR                       { get; set; }           // gpi tracking

    // Amounts
    public decimal InstructedAmount   { get; set; }
    public string  InstructedCurrency { get; set; } = null!;

    // Parties
    public string DebtorName           { get; set; } = null!;
    public string CreditorName         { get; set; } = null!;
    public string CreditorIBAN         { get; set; } = null!;

    // Timestamps — map to ISO pain.002 AcceptanceDateTime
    public DateTime  InitiatedAt        { get; set; }
    public DateTime? AcknowledgedAt     { get; set; }   // pain.002 ACCP
    public DateTime? ClearedAt          { get; set; }   // pain.002 ACSC
    public DateTime? CompletedAt        { get; set; }
    public DateTime? RejectedAt         { get; set; }
    public DateTime? ReturnedAt         { get; set; }

    // Latest status reason (pain.002 StatusReasonInformation/Reason/Code)
    public string? LastStatusReasonCode { get; set; }
}

// ── Events (messages flowing into the saga) ───────────────────────────────────

// Published when the initiating party submits a pain.001
public record PaymentInitiated(
    Guid    CorrelationId,
    string  InstructionIdentification,
    string  EndToEndIdentification,
    Guid?   UETR,
    decimal InstructedAmount,
    string  InstructedCurrency,
    string  DebtorName,
    string  CreditorName,
    string  CreditorIBAN,
    DateTime InitiatedAt
);

// Published when pain.002 is received from the bank
public record PaymentStatusReportReceived(
    string  OriginalEndToEndIdentification,      // correlation key
    string  TransactionStatus,                    // ACCP, ACSP, ACSC, RJCT, ACWC, …
    string? StatusReasonCode,
    DateTime? AcceptanceDateTime
);

// Published when pacs.004 return is received
public record PaymentReturnReceived(
    string  OriginalEndToEndIdentification,
    string  ReturnReasonCode,
    DateTime ReturnedAt
);

// ── State machine ─────────────────────────────────────────────────────────────

public class CreditTransferSagaMachine
    : MassTransitStateMachine<CreditTransferSagaState>
{
    // States
    public State Submitted    { get; private set; } = null!;
    public State Acknowledged { get; private set; } = null!;
    public State Processing   { get; private set; } = null!;
    public State Cleared      { get; private set; } = null!;
    public State Settled      { get; private set; } = null!;
    public State Completed    { get; private set; } = null!;
    public State Rejected     { get; private set; } = null!;
    public State Returned     { get; private set; } = null!;

    // Events
    public Event<PaymentInitiated>             PaymentInitiated            { get; private set; } = null!;
    public Event<PaymentStatusReportReceived>  PaymentStatusReportReceived { get; private set; } = null!;
    public Event<PaymentReturnReceived>        PaymentReturnReceived       { get; private set; } = null!;

    public CreditTransferSagaMachine()
    {
        // Persist CurrentState in the CreditTransferSagaState.CurrentState column
        InstanceState(x => x.CurrentState);

        // Correlate pain.002 and pacs.004 returns by EndToEndIdentification
        Event(() => PaymentInitiated, e =>
            e.CorrelateById(ctx => ctx.Message.CorrelationId));

        Event(() => PaymentStatusReportReceived, e =>
            e.CorrelateBy(state => state.EndToEndIdentification,
                          ctx  => ctx.Message.OriginalEndToEndIdentification));

        Event(() => PaymentReturnReceived, e =>
            e.CorrelateBy(state => state.EndToEndIdentification,
                          ctx  => ctx.Message.OriginalEndToEndIdentification));

        // ── Transitions ──────────────────────────────────────────────────────

        Initially(
            When(PaymentInitiated)
                .Then(ctx =>
                {
                    ctx.Saga.InstructionIdentification  = ctx.Message.InstructionIdentification;
                    ctx.Saga.EndToEndIdentification     = ctx.Message.EndToEndIdentification;
                    ctx.Saga.UETR                       = ctx.Message.UETR;
                    ctx.Saga.InstructedAmount           = ctx.Message.InstructedAmount;
                    ctx.Saga.InstructedCurrency         = ctx.Message.InstructedCurrency;
                    ctx.Saga.DebtorName                 = ctx.Message.DebtorName;
                    ctx.Saga.CreditorName               = ctx.Message.CreditorName;
                    ctx.Saga.CreditorIBAN               = ctx.Message.CreditorIBAN;
                    ctx.Saga.InitiatedAt                = ctx.Message.InitiatedAt;
                })
                .TransitionTo(Submitted)
        );

        During(Submitted,
            When(PaymentStatusReportReceived, IsPain002Status("ACCP"))
                .Then(ctx =>
                {
                    ctx.Saga.AcknowledgedAt = ctx.Message.AcceptanceDateTime ?? DateTime.UtcNow;
                    ctx.Saga.LastStatusReasonCode = ctx.Message.StatusReasonCode;
                })
                .TransitionTo(Acknowledged),

            When(PaymentStatusReportReceived, IsPain002Status("RJCT"))
                .Then(ctx =>
                {
                    ctx.Saga.RejectedAt = DateTime.UtcNow;
                    ctx.Saga.LastStatusReasonCode = ctx.Message.StatusReasonCode;
                })
                .TransitionTo(Rejected)
                .Finalize()
        );

        During(Acknowledged,
            When(PaymentStatusReportReceived, IsPain002Status("ACSP"))
                .Then(ctx => ctx.Saga.LastStatusReasonCode = ctx.Message.StatusReasonCode)
                .TransitionTo(Processing),

            When(PaymentStatusReportReceived, IsPain002Status("ACWC"))
                .Then(ctx => ctx.Saga.LastStatusReasonCode = ctx.Message.StatusReasonCode)
                .TransitionTo(Processing),

            When(PaymentStatusReportReceived, IsPain002Status("ACSC"))
                .Then(ctx =>
                {
                    ctx.Saga.ClearedAt = ctx.Message.AcceptanceDateTime ?? DateTime.UtcNow;
                    ctx.Saga.LastStatusReasonCode = ctx.Message.StatusReasonCode;
                })
                .TransitionTo(Cleared),

            When(PaymentStatusReportReceived, IsPain002Status("RJCT"))
                .Then(ctx =>
                {
                    ctx.Saga.RejectedAt = DateTime.UtcNow;
                    ctx.Saga.LastStatusReasonCode = ctx.Message.StatusReasonCode;
                })
                .TransitionTo(Rejected)
                .Finalize()
        );

        During(Processing,
            When(PaymentStatusReportReceived, IsPain002Status("ACSC"))
                .Then(ctx =>
                {
                    ctx.Saga.ClearedAt = ctx.Message.AcceptanceDateTime ?? DateTime.UtcNow;
                    ctx.Saga.LastStatusReasonCode = ctx.Message.StatusReasonCode;
                })
                .TransitionTo(Cleared),

            When(PaymentStatusReportReceived, IsPain002Status("RJCT"))
                .Then(ctx =>
                {
                    ctx.Saga.RejectedAt = DateTime.UtcNow;
                    ctx.Saga.LastStatusReasonCode = ctx.Message.StatusReasonCode;
                })
                .TransitionTo(Rejected)
                .Finalize()
        );

        During(Cleared,
            When(PaymentReturnReceived)
                .Then(ctx =>
                {
                    ctx.Saga.ReturnedAt = ctx.Message.ReturnedAt;
                    ctx.Saga.LastStatusReasonCode = ctx.Message.ReturnReasonCode;
                })
                .TransitionTo(Returned)
                .Finalize()
        );

        // Mark Rejected and Returned as terminal — clean up saga repository
        SetCompletedWhenFinalized();
    }

    // Helper: filter pain.002 status events by TransactionStatus code
    private static EventMessageFilter<CreditTransferSagaState, PaymentStatusReportReceived>
        IsPain002Status(string code) =>
            filter => filter.Message.TransactionStatus == code;
}

EF Core Saga Repository Registration

// Program.cs / Startup
services.AddMassTransit(x =>
{
    x.AddSagaStateMachine<CreditTransferSagaMachine, CreditTransferSagaState>()
        .EntityFrameworkRepository(r =>
        {
            r.ConcurrencyMode = ConcurrencyMode.Optimistic;
            r.AddDbContext<DbContext, PaymentsDbContext>((provider, opts) =>
                opts.UseNpgsql(connectionString));
        });

    x.UsingRabbitMq((ctx, cfg) => cfg.ConfigureEndpoints(ctx));
});

What Just Happened?

One question. No ISO spec PDFs opened. No message catalog searched. No state machine scaffolded by hand.

The Beneficial Strategies ISO 20022 MCP Server queried the live specification, navigated twelve levels deep into the pain.001.001.12 component hierarchy, confirmed the exact repeating structure of StructuredRemittanceInformation18 and ReferredDocumentInformation8, and produced:

The only decisions you made were “use classes” and “include the UETR.”

That’s what the Beneficial Strategies AI MCP Server does for your payment engineering team. See what’s included in each plan.