Skip to main content

Scenario: Record a customer payment and settle it against the invoice

Goal. A customer pays an invoice. Your bank, lockbox or payment-provider integration posts the receipt to D365 as a customer payment journal, settles the payment against the open invoice so both are closed, and reads the settlement back. Each call posts exactly once, even when your system resends it.

When to use. Cash application from an external system: bank statement matching, card or wallet payments, lockbox files. The open invoice usually comes from Sales order to invoice (a sales invoice such as CIV-00000744) or Free text invoice (such as FTI-00000122); this walkthrough settles a 500.00 USD free text invoice. A deposit taken before the order is invoiced is recorded as a prepayment marked against the sales order (step 3), and a payment recorded in error is reversed (step 4).

Prerequisites​

  1. Security. MGCLatticeGeneralLedgerServiceMaintain grants postCustomerPaymentJournal, settleCustomerTransactions and their batch variants (the payment journal service is in the General ledger service group). MGCLatticeIntegrationOperatorRole and MGCLatticeIntegrationAdministratorRole hold it, and their Lattice entity duties grant read access to LatticeCustomerTransactions and LatticeCustomerSettlements. Reversing a payment (reverseCustomerPayment) is high-risk: it needs MGCLatticePaymentReverseMaintain ("Payments - reverse customer and vendor payments (high-risk)"), in duty MGCLatticeIntegrationServiceHighRiskDuty of the integration administrator role only. The standard CustTransactions entity used below to read prepayment and cancellation flags needs D365's own privileges. See Security model.
  2. D365 configuration in the legal entity:
    • a journal name of type Customer payment with a voucher number sequence (USMF CustPay);
    • the bank account the money arrives in (USMF USMF OPER) and a customer method of payment with its bank transaction type (USMF CHECK);
    • the customer (USMF US-001) with a customer posting profile, and an open ledger period for the posting date;
    • an open (posted, not yet settled) invoice of that customer;
    • for a prepayment, the prepayment posting profile in the Accounts receivable parameters (Posting profile with prepayment journal voucher; USMF PRE, summary account 212160) and an open sales order of the customer. When the parameters calculate sales tax on prepayments (USMF does), the line takes the customer's sales tax group and the default item sales tax group, as the payment journal does;
    • for a reversal, the general ledger number sequences for reversal vouchers and trace numbers and the Accounts receivable closing voucher number sequence (USMF has them); for a check, when the cash and bank management parameters Use review process for payment reversals, a journal name of type Check reversal with a voucher number sequence (USMF CheckRev).
  3. Lattice setup. A valid license (an expired or invalid one refuses both operations with LicenseNotValid). To name the customer by your own key (AccountNumExternalKey), set up an external system as in Cross-reference keys.

Steps​

All three operations are on MGCLatticePaymentJournalService in MGCLatticeGeneralLedgerServiceGroup: POST https://<your-environment>/api/services/MGCLatticeGeneralLedgerServiceGroup/MGCLatticePaymentJournalService/<operation>, with the request in _request and an OAuth 2.0 client-credentials token.

  1. Post the payment. postCustomerPaymentJournal creates a customer payment journal and posts it:

    {
    "_request": {
    "RequestId": "b6a5c4d3-e2f1-4a0b-9c8d-7e6f5a4b3c2d",
    "LegalEntityId": "USMF",
    "UserContext": "Admin",
    "JournalNameId": "CustPay",
    "Description": "Lockbox 2026-10-07",
    "PostingDate": "2026-10-07",
    "Lines": [
    {
    "AccountNum": "US-001",
    "InvoiceId": "FTI-00000122",
    "AmountCurCredit": 500.00,
    "CurrencyCode": "USD",
    "BankAccountId": "USMF OPER",
    "MethodOfPayment": "CHECK",
    "Description": "Payment for FTI-00000122",
    "TransDate": "2026-10-07"
    }
    ]
    }
    }

    A customer receipt is a credit to the customer (AmountCurCredit); a debit line is a refund and cannot be settled against the invoice. InvoiceId stores the invoice the payment pays on the payment, so the settlement can find the payment by that invoice number. BankAccountId is the line's offset (empty uses the journal name's offset account); CurrencyCode defaults to the customer's currency. The journal carries your Description. The answer reports success only when the journal is posted: DocumentId and PostedDocumentId are the journal number, and the message reads Customer payment journal 25811 posted successfully.

  2. Settle the payment against the invoice. settleCustomerTransactions marks the open balances and settles them, as the Settle open transactions page does:

    {
    "_request": {
    "RequestId": "1d2c3b4a-5f6e-4d7c-8b9a-0f1e2d3c4b5a",
    "LegalEntityId": "USMF",
    "UserContext": "Admin",
    "AccountNum": "US-001",
    "PaymentInvoiceId": "FTI-00000122",
    "Lines": [ { "InvoiceId": "FTI-00000122", "SettleAmountCur": 500.00 } ]
    }
    }

    Name the payment one of three ways; the first one sent wins: PaymentTransRecId (the record id of the payment transaction), PaymentVoucher (its voucher), or PaymentInvoiceId (the invoice number the payment carries, from step 1). Name each invoice by InvoiceTransRecId or InvoiceId (the record id wins). SettleAmountCur 0 or omitted settles the whole open balance; a smaller amount settles part of it and leaves the rest open. Several lines settle one payment against several invoices. The payment's voucher and record id are on LatticeCustomerTransactions (Voucher, SysRecId) if you prefer them.

    DocumentId is the voucher of the settled payment (for example ARPM000974); a settlement produces no document of its own, so PostedDocumentId is empty. The message reads Customer transactions settled for account US-001. The settlement and its idempotency record commit in one transaction.

  3. Record a deposit for a sales order (prepayment). When the customer pays before the order is invoiced, post the payment as a prepayment marked against the order. Mark the line IsPrepayment and name the order in SalesOrderId (or SalesOrderIdExternalKey with ExternalSystem); the rest of the request is as in step 1, without InvoiceId:

    {
    "_request": {
    "RequestId": "2f4a6c8e-0b1d-4e3f-a5b7-c9d1e3f5a7b9",
    "LegalEntityId": "USMF",
    "UserContext": "Admin",
    "JournalNameId": "CustPay",
    "Description": "Deposit web order PO-88231",
    "PostingDate": "2026-10-07",
    "Lines": [
    {
    "AccountNum": "US-001",
    "IsPrepayment": true,
    "SalesOrderId": "001367",
    "AmountCurCredit": 200.00,
    "CurrencyCode": "USD",
    "BankAccountId": "USMF OPER",
    "MethodOfPayment": "CHECK",
    "Description": "Deposit for order 001367",
    "TransDate": "2026-10-07"
    }
    ]
    }
    }

    The prepayment posts with the prepayment posting profile of the Accounts receivable parameters (without one, D365 uses the default posting profile and Warnings says so). The order is checked before the journal is created: it must exist, be invoiced to the line's customer, and be neither invoiced nor canceled. After posting, the payment is marked against the order as the order's Settle transactions does, and InformationalMessages names the order (Payment ... is marked for settlement with the invoice of order 001367.). When the order is invoiced (Sales order to invoice), its invoice settles the prepayment; there is no separate settlement call. A payment that could not be marked is still posted, with a warning: mark it with Settle transactions on the order.

  4. Reverse a payment (when it was recorded in error). reverseCustomerPayment reverses a posted payment voucher of the customer with the standard reversal that covers it:

    The payment wasReversed byEffect
    Posted to a bank account (as in step 1)The bank's payment cancellationA canceling customer, bank and ledger transaction; the payment's settlements are reversed, so the invoices it paid are open again.
    Made by checkThe check reversal (Payment reversal on the Checks page)The payment is reversed and the check canceled. When the cash and bank management parameters require check reversals to be reviewed in a check reversal journal, a check reversal journal with the canceling line is created and the check becomes Pending cancellation: the journal is left for review (PostedDocumentId is the journal), or posted in the same request with "PostReversalJournal": true. ReversalJournalNameId names the journal name (type Check reversal; without it, the legal entity's only one).
    Not posted to a bank accountThe customer transaction reversalA settled payment is refused: unsettle it first.
    {
    "_request": {
    "RequestId": "6e8a0c2e-4f6b-4d8a-9c0e-2b4d6f8a0c3e",
    "LegalEntityId": "USMF",
    "UserContext": "Admin",
    "AccountNum": "US-001",
    "Voucher": "ARPM000974",
    "ReversalDate": "2026-10-08",
    "ReasonCode": "ERROR",
    "ReasonComment": "Payment applied to the wrong customer"
    }
    }

    Voucher is the payment's voucher (a Payment transaction of the account). ReversalDate defaults to today and cannot be before the payment date. ReasonCode is required when the parameters demand a reason (Accounts receivable Reason code required for a payment cancellation or transaction reversal; cash and bank management Require reasons for payment reversals for a check). DocumentId is the reversed payment voucher and PostedDocumentId the cancellation or reversal voucher: Customer payment ARPM000974 of account US-001 reversed with voucher ARPM000981.

Many payments at once. postCustomerPaymentJournalBatch, settleCustomerTransactionsBatch and reverseCustomerPaymentBatch take { "_requests": [ ... ] }, each item a full request with its own RequestId, and answer with one result per item (see API reference, Bulk Operations).

None of the operations has a preview: a request with "PreviewMode": true is refused with PreviewUnsupported before anything is created.

How to verify​

  • The responses: Success true and Status 3; the payment's DocumentId = PostedDocumentId = the journal number; the settlement's DocumentId = the payment voucher.

  • The transactions: both the invoice and the payment carry the invoice number, so one query shows both, and after a full settlement both have RemainingTransactionCurrencyAmount 0 and a ClosedDate:

    GET https://<your-environment>/data/LatticeCustomerTransactions?cross-company=true&$filter=dataAreaId eq 'USMF' and CustomerAccount eq 'US-001' and InvoiceId eq 'FTI-00000122'&$select=Voucher,TransactionType,TransactionCurrencyAmount,RemainingTransactionCurrencyAmount,ClosedDate,SysRecId
  • The settlement: LatticeCustomerSettlements has a row that pairs the two transactions (TransRecId and OffsetRecId are their SysRecId values, in either order) with SettledTransactionCurrencyAmount and SettlementVoucher:

    GET https://<your-environment>/data/LatticeCustomerSettlements?cross-company=true&$filter=dataAreaId eq 'USMF' and CustomerAccount eq 'US-001' and TransRecId eq 68719825540
  • In D365: Accounts receivable > Payments > Customer payment journal shows the journal as posted (set the Show filter to include posted journals); Accounts receivable > Customers > All customers, select US-001, then Transactions shows the invoice and the payment closed.

  • A prepayment: the standard CustTransactions entity shows the payment's voucher with Prepayment Yes and the prepayment PostingProfile (USMF PRE); on the order, Invoice > Settle transactions shows the payment marked. Once the order is invoiced, the prepayment's SettledTransactionCurrencyAmount in LatticeCustomerTransactions is no longer 0.

  • A reversal: CustTransactions shows the payment with CancelledPayment Yes (for a payment cancellation); LatticeCustomerTransactions has a transaction on the returned voucher with the opposite amount, and LatticeBankAccountTransactions (filter on Voucher) the canceling bank transaction; an invoice the payment had settled has its RemainingTransactionCurrencyAmount back. In D365 the customer's Transactions show the payment canceled, and the bank account's Transactions (Cash and bank management > Bank accounts) the canceling transaction.

When it goes wrong​

ErrorCodeTypical cause in this flowWhat to do
ValidationFailed (1)Payment: no JournalNameId; a journal name that does not exist (Journal name CP was not found.) or is not a customer payment journal (Journal name GenJrn is not a Customer payment journal.); no lines or an empty line; an unknown customer (Journal line 1: customer account 'US-999' was not found.) or bank account; GeneratePayments (GeneratePayments is available for vendor payment journals only.). Settlement: no AccountNum or an unknown customer; none of PaymentTransRecId, PaymentVoucher, PaymentInvoiceId (PaymentTransRecId, PaymentVoucher or PaymentInvoiceId is required.); no lines; a line with neither InvoiceTransRecId nor InvoiceIdCorrect the request and send it again. Nothing was created.
ConfigurationMissing (11)The journal name has no voucher number sequenceConfigure the voucher series on the journal name.
DocumentNotFound (2)The payment is not a payment transaction of the account (Customer payment transaction with voucher or invoice FTI-00000122 for account US-001 was not found.), or an invoice line names no transaction of the account, or names a paymentCheck that step 1 posted, with InvoiceId and AmountCurCredit, for the same customer.
BusinessRule (4)Payment: D365 refused the posting (Journal 25811 was not posted., reasons in Errors). Settlement: the payment or invoice has no open balance (Customer invoice FTI-00000122 has no open balance to settle.), or nothing was settledFor the payment, see below. For the settlement, check what is still open on the customer's transactions.
TransientRuntime (6)A runtime fault; IsRetryable is trueResend with the same RequestId.
PreviewUnsupported (10)PreviewMode trueSend the request without preview.
LicenseNotValid (12)The Lattice license is expired or invalidRenew the license, then resend.

Prepayment refusals. ValidationFailed: "Line 1: PurchaseOrderId applies to vendor payments only."; "Line 1: order ... was not found."; "Line 1: order ... is invoiced to account ..., not to the line's account." InvalidStatus: "Line 1: order ... is ..., so a payment cannot be marked against it." (the order is invoiced or canceled). Nothing was created.

Reversal refusals. A refused reversal reverses nothing; the reasons are in Errors.

ErrorCodeTypical causeWhat to do
ValidationFailed (1)No AccountNum or Voucher; an unknown customer or reason code; "The reversal date ... is before the date of voucher ..."; "A reason code is required to reverse voucher ..."; a ReversalJournalNameId that is unknown or not a check reversal journal name; "Several check reversal journal names exist (...). Send ReversalJournalNameId."Correct the request and resend.
DocumentNotFound (2)"Customer payment voucher ... was not found on account US-001."Send the voucher of a payment of that customer.
InvalidStatus (3)The payment is already canceled or reversed; a check already reversed or pending reversal (a check reversal journal exists for it), or not in status PaymentNothing to reverse; post or delete the pending check reversal journal in D365.
BusinessRule (4)A payment posted to a bank account that cannot be canceled (a post-dated check or a letter of credit); "Voucher ... is settled. Unsettle it before you reverse it." for a payment not posted to a bank account; a refusal of the standard engineUnsettle the payment first, or reverse it in D365.
ConfigurationMissing (11)"The number sequence for ... is not set up."; the parameters require a check reversal journal but no journal name of type Check reversal exists, or it has no voucher number sequenceSet up the reversal voucher, trace number or customer closing voucher number sequence, or the check reversal journal name.

A refused post leaves no journal behind. The service saves the journal before it posts it. When D365 refuses the posting, the service deletes the unposted journal again and the request is not remembered, so fix the cause and resend: the resend creates the journal anew. Only if that cleanup itself fails does the unposted journal (with your Description) stay on Customer payment journal; post or delete it before you resend.

Retries are safe otherwise. Resending a successful request with the same RequestId posts, settles or reverses nothing again: the answer repeats the original DocumentId with the message Duplicate request detected. Original request processed at .... A RequestId already used by another operation is refused with DuplicateRequest (7). If a call is cut off while it runs, a resend with the same RequestId is answered RequestInProgress (8) and, once the attempt is an hour old, RequestNeedsReview (9); nothing runs again. Once the journal was created, the claim names it, so the scheduled idempotency reconciliation sweep can resolve it; otherwise an operator resolves it on Service idempotency log, see Idempotency reconcile runbook.

Reference​