Skip to content

feat(xero): add Xero integration - #249

Open
RishadAlam wants to merge 6 commits into
mainfrom
feat/xero-integration
Open

RishadAlam wants to merge 6 commits into
mainfrom
feat/xero-integration

Conversation

@RishadAlam

@RishadAlam RishadAlam commented Nov 1, 2026 •

Copy link
Copy Markdown
Member

Pull Request

Description

Adds Xero as a Bit Integrations action with 71 write actions. The free plugin owns the connection, the dropdown lists and the screens. The actions run in Pro: https://github.com/bit-apps-pro/bit-integrations-pro/pull/169

Motivation & Context

Xero is a widely used accounting app. This ports the bit-pi Xero integration (Bit-Apps-Pro/bit-pi#891). Only write actions are ported (create, update, approve, void, delete, allocate, email, attach, download PDF). The bit-pi webhook triggers are left out.

Type of Change

  • 🐛 Bug fix
  • ✨ New feature
  • 💥 Breaking change
  • 📚 Documentation update
  • ⚡ Improvement
  • 🔄 Code refactor

Key Changes

Backend (backend/Actions/Xero)

  • Added XeroController.
    • $authConfig OAuth2. The core OAuth2 handler already stores the rotating Xero refresh token.
    • Every call needs the xero-tenant-id header. The controller reads the authentication_event_id from the access token, asks GET /connections?authEventId= for the organisation picked on the consent screen, and caches tenant_id in the connection.
    • Seven dropdown routes: contacts, account codes, bank accounts, items, tax rates, currencies and branding themes. Only active records are listed.
  • Added RecordApiHelper: one switch case per action fires its own xero_* hook with the field map, the API client and the dropdown settings, then calls LogHandler::save.

Frontend (AllIntegrations/Xero)

  • Added the connection form. It uses the shared OAuth2 Authorization with Xero's granular scopes (accounting.contacts, .settings, .attachments, .invoices, .payments, .banktransactions, .manualjournals). Apps created after 2 March 2026 must use these scopes.
  • Added staticData.js.
    • Field maps hold the record IDs that an action targets, plus free text, amounts and dates (labelled YYYY-MM-DD).
    • Contacts, items, accounts, bank accounts, tax rates, currencies and branding themes are fetched dropdowns. Enums are fixed selects.
    • Each document takes one line (item or description, plus quantity, unit price, account, tax rate, discount and tracking).
    • Notes explain the Xero rules: approved documents need a line description, an update replaces the line, an overpayment needs the receivable or payable account, a batch payment needs a reference, and similar.
  • Added the action layout, the field map (locked required rows show *), the wizard and the edit screens. Changing the action clears every dropdown value, so a value from another action is never sent.
  • Added registration in NewInteg, EditInteg, IntegInfo, SelectAction and tutorialLinks, plus a 128px logo.

Testing

  • Connected through the real Xero OAuth popup in the UI and picked the organisation. All seven dropdown routes returned live data.
  • Tested all 71 actions as a user would: built one flow per action in the UI (dropdowns, field map, Finish & Save) on a dedicated Contact Form 7 form, submitted the real form in dependency stages (create, update, approve, allocate, void, delete) and checked every flow's log. 77 flows, all succeeded against a live Xero organisation.
  • Checked the error paths: a missing required field names the field as the UI shows it, and Xero validation errors reach the log with Xero's own message.
  • Found and fixed a bug during testing: Xero rejects the "Days After the Invoice Date" (DAYSAFTERINVOICEDATE) and DAYSAFTERBILLMONTH due date types for repeating invoices. The list now offers only the four types Xero accepts.
  • eslint (--max-warnings=0) and Prettier are clean. PHP passes php -l, PHPCompatibilityWP 7.4+ and php-cs-fixer.

Checklist

  • Code follows project style guidelines
  • Self-review completed
  • Tests added/updated
  • Documentation updated if needed
  • README updated if needed

Changelog

  • New Actions: Xero - Create, update, approve, void, delete and email invoices and bills, plus credit notes, quotes, purchase orders and repeating invoices (71 actions in total).
  • New Actions: Xero - Manage contacts, contact groups, items, payments, batch payments, prepayments, overpayments, bank transactions, bank transfers, manual journals, accounts, tracking categories, tax rates and currencies.
  • New Actions: Xero - Add history notes, upload attachments and save document PDFs to the media library.

🤖 Generated with Claude Code

XeroController uses the OAuth2 connection, finds the organisation from the token's auth event
and caches its tenant ID on the connection, and serves the contact, account, item, tax rate,
currency and branding theme lists. RecordApiHelper sends each of the 71 actions to its Pro hook.
OAuth connection, 71 Pro actions with one line per document, a contact dropdown, and selects for
fixed options and fetched lists.
Remove the unused utilities config, mark locked required rows, give the
list routes a fallback error, and add notes for contact, account, bank
transfer, batch payment and ID-only actions.
Xero rejects DAYSAFTERINVOICEDATE and DAYSAFTERBILLMONTH for repeating
invoices. Also tell users to pick the receivable or payable account for
an overpayment.
Copilot AI balanced review requested due to automatic review settings November 1, 2026 05:38

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota. Try requesting a review at a lower effort level.

@github-actions

github-actions Bot commented Nov 1, 2026

Copy link
Copy Markdown

✅ WordPress Plugin Check Report

✅ Status: Passed

📊 Report

All checks passed! No errors or warnings found.


🤖 Generated by WordPress Plugin Check Action • Learn more about Plugin Check

Reject connections that belong to another app, report Xero's error when
the organisation lookup fails, use the full organisation list only when
it holds one organisation, build dropdown labels once per list, and keep
the developer portal sentence in one translatable string.
Copilot AI balanced review requested due to automatic review settings November 1, 2026 05:54

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota. Try requesting a review at a lower effort level.

@RishadAlam

Copy link
Copy Markdown
Member Author

Self-review: findings and fixes (f5cac84)

Fixed

  • Connection check: XeroController::client() now rejects a connection that belongs to another app. getConnectionHandler() no longer checks the app slug, so before this fix a connection_id from another OAuth2 app could reach the Xero API with that app's token.
  • Auth errors: when /connections fails (expired or revoked token, network error), the error now says what Xero returned, for example "Xero did not return the organisations (AuthenticationUnsuccessful). Connect again and try again.". Before, every failure showed as "Xero has no organisation".
  • Organisation fallback: the unfiltered organisation list is now used only when it holds exactly one organisation, so a connection can never be bound to the wrong organisation.
  • Dropdown speed: labels are built once per list with useMemo and a name-count Map. This replaces an O(n²) filter on every render, which was slow for large contact lists.
  • i18n: the "Open the Xero Developer portal" sentence is now one translatable string.

Kept as is

  • Document contact: stays a dropdown only. This was a product decision for this port.
  • Connection switch: switching the connection on the info page keeps the old organisation's dropdown values. This is framework behaviour shared by all integrations.

Verified live: organisation rediscovery after tenant_id is cleared, rejection of another app's connection, the 401 path, all dropdowns on the edit screen, and a regression run of the line-item and PDF flows through real Contact Form 7 submits. eslint and Prettier are clean, and PHPCompatibilityWP 7.4+ and php-cs-fixer pass.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants