---
name: evolution_xero_payment_externalref
description: payment.externalRef holds the raw Xero payment GUID; putPayment must stamp it on send or delete/reconcile breaks
metadata: 
  node_type: memory
  type: project
  originSessionId: df60a75f-9cd9-4ae0-a191-8931114d001e
---

`payment.externalRef` stores the **raw Xero payment GUID** (not serialized — unlike `invoices.externalRef`; same convention as bills). It is the key for deleting/reconciling the Xero-side payment.

**Send path** (`library/xero.php::putPayment($id)`, called from `invoiceaddsave.php` after `$inv->addPayment` stamps the keyID): after `createPayment` succeeds, it now writes the returned GUID back via `update payment set externalRef=? where id=?` (guarded by `!getHasValidationErrors()`). Before the 2026-06-16 fix this write-back was missing, so externalRef stayed empty and Xero-side delete silently no-op'd.

**Delete path** (`payment.php?delete`): when `xeroActive()` (functions.php — true when xero_auth_version=="2"), a pre-flight loop calls `evoXero::deletePayment($val->externalRef)` for each linked row BEFORE the GL uncommit. `deletePayment` returns bool (false when Xero refuses, e.g. reconciled). On any failure it redirects to `paymentDetail&error=...` (rendered as an alert-danger banner) and touches NOTHING in evo, keeping both sides in sync.

**deletePayment contract**: takes the GUID directly (Xero "delete" = POST /Payments/{PaymentID} body {"Status":"DELETED"}). Do NOT re-query payment row by id — the row is already gone by call time.

Open: payments pushed BEFORE the fix have empty externalRef (not deletable from evo) — would need a backfill matching by invoice+amount+date. See [[evolution_xero_ap_payment_sync]].
