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
- Security.
MGCLatticeGeneralLedgerServiceMaintaingrantspostCustomerPaymentJournal,settleCustomerTransactionsand their batch variants (the payment journal service is in the General ledger service group).MGCLatticeIntegrationOperatorRoleandMGCLatticeIntegrationAdministratorRolehold it, and their Lattice entity duties grant read access toLatticeCustomerTransactionsandLatticeCustomerSettlements. Reversing a payment (reverseCustomerPayment) is high-risk: it needsMGCLatticePaymentReverseMaintain("Payments - reverse customer and vendor payments (high-risk)"), in dutyMGCLatticeIntegrationServiceHighRiskDutyof the integration administrator role only. The standardCustTransactionsentity used below to read prepayment and cancellation flags needs D365's own privileges. See Security model. - 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 (USMFCHECK); - 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 account212160) 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).
- a journal name of type Customer payment with a voucher number sequence (USMF
- 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.
-
Post the payment.
postCustomerPaymentJournalcreates 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.InvoiceIdstores the invoice the payment pays on the payment, so the settlement can find the payment by that invoice number.BankAccountIdis the line's offset (empty uses the journal name's offset account);CurrencyCodedefaults to the customer's currency. The journal carries yourDescription. The answer reports success only when the journal is posted:DocumentIdandPostedDocumentIdare the journal number, and the message readsCustomer payment journal 25811 posted successfully. -
Settle the payment against the invoice.
settleCustomerTransactionsmarks 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), orPaymentInvoiceId(the invoice number the payment carries, from step 1). Name each invoice byInvoiceTransRecIdorInvoiceId(the record id wins).SettleAmountCur0 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 onLatticeCustomerTransactions(Voucher,SysRecId) if you prefer them.DocumentIdis the voucher of the settled payment (for exampleARPM000974); a settlement produces no document of its own, soPostedDocumentIdis empty. The message readsCustomer transactions settled for account US-001.The settlement and its idempotency record commit in one transaction. -
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
IsPrepaymentand name the order inSalesOrderId(orSalesOrderIdExternalKeywithExternalSystem); the rest of the request is as in step 1, withoutInvoiceId:{"_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
Warningssays 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, andInformationalMessagesnames 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. -
Reverse a payment (when it was recorded in error).
reverseCustomerPaymentreverses a posted payment voucher of the customer with the standard reversal that covers it:The payment was Reversed by Effect Posted to a bank account (as in step 1) The bank's payment cancellation A canceling customer, bank and ledger transaction; the payment's settlements are reversed, so the invoices it paid are open again. Made by check The 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 ( PostedDocumentIdis the journal), or posted in the same request with"PostReversalJournal": true.ReversalJournalNameIdnames the journal name (type Check reversal; without it, the legal entity's only one).Not posted to a bank account The customer transaction reversal A 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"}}Voucheris the payment's voucher (a Payment transaction of the account).ReversalDatedefaults to today and cannot be before the payment date.ReasonCodeis 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).DocumentIdis the reversed payment voucher andPostedDocumentIdthe 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:
SuccesstrueandStatus3; the payment'sDocumentId=PostedDocumentId= the journal number; the settlement'sDocumentId= 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
RemainingTransactionCurrencyAmount0 and aClosedDate: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:
LatticeCustomerSettlementshas a row that pairs the two transactions (TransRecIdandOffsetRecIdare theirSysRecIdvalues, in either order) withSettledTransactionCurrencyAmountandSettlementVoucher: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
CustTransactionsentity shows the payment's voucher withPrepaymentYesand the prepaymentPostingProfile(USMFPRE); on the order, Invoice > Settle transactions shows the payment marked. Once the order is invoiced, the prepayment'sSettledTransactionCurrencyAmountinLatticeCustomerTransactionsis no longer 0. -
A reversal:
CustTransactionsshows the payment withCancelledPaymentYes(for a payment cancellation);LatticeCustomerTransactionshas a transaction on the returned voucher with the opposite amount, andLatticeBankAccountTransactions(filter onVoucher) the canceling bank transaction; an invoice the payment had settled has itsRemainingTransactionCurrencyAmountback. 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
ErrorCode | Typical cause in this flow | What 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 InvoiceId | Correct the request and send it again. Nothing was created. |
ConfigurationMissing (11) | The journal name has no voucher number sequence | Configure 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 payment | Check 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 settled | For the payment, see below. For the settlement, check what is still open on the customer's transactions. |
TransientRuntime (6) | A runtime fault; IsRetryable is true | Resend with the same RequestId. |
PreviewUnsupported (10) | PreviewMode true | Send the request without preview. |
LicenseNotValid (12) | The Lattice license is expired or invalid | Renew 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.
ErrorCode | Typical cause | What 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 Payment | Nothing 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 engine | Unsettle 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 sequence | Set 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
- API reference: section 9 (Payment Journal Service) and section 19 (Bulk Operations)
- Endpoint payload specification:
MGCLatticePaymentJournalService - Security model:
MGCLatticeIntegrationServiceHighRiskDuty - Cross-reference keys
- Idempotency reconcile runbook
- Free text invoice, Sales order to invoice
- Post a journal exactly-once
- General and intercompany journals: reversing a whole journal