如何使用PHP SDK编辑已标记为已付款的Xero发票?
Hey there, I've tackled this exact scenario with the Xero PHP SDK before—Xero blocks direct edits to invoices marked as Paid by default, so you need to work around their state rules. Here's a step-by-step breakdown of how to make it happen:
Step 1: Fetch the Invoice's Associated Payment(s)
First, you need to find the payment record(s) linked to the paid invoice—removing these is what unlocks the invoice for editing.
use XeroAPI\XeroPHP\Api\AccountingApi; use XeroAPI\XeroPHP\Configuration; // Assume you've already set up OAuth2 auth and have your tokens ready $config = Configuration::getDefaultConfiguration()->setAccessToken('<YOUR_ACCESS_TOKEN>'); $api = new AccountingApi(null, $config); $tenantId = '<YOUR_TENANT_ID>'; $targetInvoiceId = '<THE_PAID_INVOICE_ID_YOU_WANT_TO_EDIT>'; // Grab the full invoice details, including linked payments $invoice = $api->getInvoice($tenantId, $targetInvoiceId); $linkedPayments = $invoice->getPayments(); // Pull the first payment ID (if multiple payments exist, loop through all) if (!empty($linkedPayments)) { $paymentId = $linkedPayments[0]->getPaymentId(); }
Step 2: Delete the Linked Payment to Revert Invoice State
Deleting the payment will flip the invoice's status from Paid back to Authorised—this is the editable state we need.
try { $api->deletePayment($tenantId, $paymentId); echo "Payment removed successfully; invoice is now editable (Authorised state)"; } catch (Exception $e) { echo "Error deleting payment: " . $e->getMessage() . PHP_EOL; }
Step 3: Make Your Edits to the Invoice
Now that the invoice is back in Authorised status, you can modify any fields you need—like line items, totals, contact details, etc.
// Example edits: update description and total amount $invoice->setDescription("Updated: Q3 Consulting Services"); $invoice->setTotal(175.00); try { $updatedInvoice = $api->updateInvoice($tenantId, $targetInvoiceId, $invoice); echo "Invoice edited successfully!"; } catch (Exception $e) { echo "Error updating invoice: " . $e->getMessage() . PHP_EOL; }
Step 4: Re-apply the Payment to Mark It as Paid Again
Once your edits are done, re-create the payment to set the invoice back to Paid status.
use XeroAPI\XeroPHP\Model\Accounting\Payment; // Create a new payment object linked to your updated invoice $payment = new Payment(); $payment->setInvoice($updatedInvoice); $payment->setAccountCode('090'); // Replace with your actual bank account code $payment->setAmount(175.00); // Match your updated invoice total $payment->setDate(new \DateTime()); try { $api->createPayment($tenantId, $payment); echo "Payment re-applied; invoice is now marked as Paid again"; } catch (Exception $e) { echo "Error re-creating payment: " . $e->getMessage() . PHP_EOL; }
Quick Alternative: Use a Credit Note (If You Don't Want to Delete Payments)
If you don't want to mess with removing existing payment records (e.g., for audit trails), you can create a credit note to offset the original paid invoice, then create a new corrected invoice and mark that as paid instead. This is great for small corrections or when you need to preserve the original transaction history.
Key Notes
- Make sure your OAuth2 scopes include
accounting.transactionsandaccounting.invoices(read/write permissions are required). - If the invoice has multiple linked payments, you'll need to delete all of them to get it back to
Authorisedstatus. - Always test this workflow in a Xero sandbox first before touching production data!
内容的提问来源于stack exchange,提问作者Niraj Savaliya

