Skip to content

[18.0][ADD] account_statement_import_online_ramp - #950

Open
dnplkndll wants to merge 1 commit into
OCA:18.0from
ledoent:18.0-add-account-statement-import-online-ramp
Open

[18.0][ADD] account_statement_import_online_ramp#950
dnplkndll wants to merge 1 commit into
OCA:18.0from
ledoent:18.0-add-account-statement-import-online-ramp

Conversation

@dnplkndll

@dnplkndll dnplkndll commented May 12, 2026

Copy link
Copy Markdown

Description

Adds a new online-statement provider that imports
Ramp corporate-card transactions via the
Ramp Developer API, mirroring
the existing Plaid and Mercury providers.

Ramp is a US corporate-card and spend-management platform. A Ramp
organization has one credit line shared across all cards, billed as a
single monthly statement and paid via one ACH transfer. The module
follows that accounting reality: one Ramp credit line maps to one
Odoo bank-type journal, regardless of how many cards are issued under
it.

Endpoints used

  • POST /developer/v1/token — OAuth2 client_credentials. Mints a
    short-lived bearer cached on the provider record; refreshed on
    expiry or HTTP 401.
  • GET /developer/v1/transactions — paginated card-transaction feed
    filtered by date range; cursor pagination via page.next.

Key features

  • OAuth2 client_credentials auth — pure backend; no JS UI (unlike
    Plaid Link). Closer in shape to the Mercury provider.
  • Concurrent-safe token refreshSELECT ... FOR UPDATE on the
    provider row + post-lock cache re-check, so two parallel cron runs
    on the same provider don't mint two separate bearers.
  • Transient-failure retryurllib3.Retry adapter mounted on
    https:// with 3 tries, exponential backoff, retrying on 502/503/504.
    Applied to both token mint and data fetch.
  • SSRF / bearer-leak guard on pagination cursor — Ramp's
    page.next URL is validated to be HTTPS on the configured Ramp
    host (.hostname comparison, port- and case-tolerant, user-authority
    bypass rejected) before being followed.
  • Bounded pagination — fetch loop caps at 1000 pages with an
    explicit UserError if Ramp's cursor never terminates.
  • State filtering — transactions in DECLINED,
    PENDING_INITIATION, or ERROR are skipped at fetch time;
    CLEARED and PENDING are kept.
  • Sign convention — Ramp returns positive amounts on spend; the
    module flips the sign so spend lands as a negative line on the
    credit-line liability journal (standard Odoo bank-statement
    convention).
  • raw_data preservation — the full Ramp transaction payload
    (including card_id, user_id, sk_category_name,
    accounting_categories) is stored on each line so downstream
    automation can use it without a second API call.

Tests

35 mocked-HTTP tests, all green locally:

account_statement_import_online_ramp: 37 tests 0.70s 1930 queries
0 failed, 0 error(s) of 35 tests

Coverage:

  • service registration
  • single-page + multi-page cursor pagination + max-pages safeguard
  • mapping (date/amount/sign/merchant) + partner_name fallback
  • state filtering (DECLINED/PENDING_INITIATION/ERROR dropped)
  • OAuth2 token cache: hit, miss-when-blank, miss-when-expired,
    persist-to-record, default-expires-in, null-expires-in,
    missing-access-token, missing-credentials, rejected-credentials
  • 401 → token refresh → retry once
  • network errors → UserError; non-2xx → UserError
  • SSRF cursor guard: matching host (with port, mixed case), foreign
    host, plain HTTP, cross-env (sandbox↔prod), user@ authority bypass
  • Retry adapter actually mounted on the session

End-to-end smoke test

Module installed cleanly into a fresh Odoo 18 DB, provider configured
against a real Ramp production account, OAuth2 token minted
successfully (after fixing initial client_id/secret swap), and the
/transactions request returned a well-formed (empty) result set for
an org with no card spend yet. Re-pull once real transactions accrue.

License

AGPL-3, matching the sibling Plaid and Mercury providers.

Checklist

  • License compatible with dependencies (AGPL-3).
  • pre-commit run --all-files clean (ruff, ruff-format,
    prettier, pylint_odoo, whool-init, oca-gen-addon-readme).
  • readme/ fragments provided; README.rst auto-generated.
  • No mention of "Odoo Enterprise" in any module file.
  • Tests run on a fresh DB and pass.
  • Audited against the
    OCA 18.0 → 19.0 migration wiki:
    no self._cr/_uid/_context, no pytz, no urljoin,
    no deprecated SUPERUSER_ID import, no groups_id XML,
    no demo-data dependency in tests, no odoo-test-helper,
    no migrations/ directory. Already on the 19.0 trajectory.
  • Single [18.0][ADD] commit (squashed from review iteration).

Configuration screenshot

Ramp provider configuration form

The "Ramp Configuration" group only appears when Service = Ramp. Client Secret is rendered as a password field (masked). Demo values shown above; no real credentials.

@OCA-git-bot OCA-git-bot added series:18.0 mod:account_statement_import_online_ramp Module account_statement_import_online_ramp labels May 12, 2026
New OCA online-statement provider that imports Ramp corporate-card
transactions via the Ramp Developer API.

A Ramp organization has one credit line shared across all cards,
billed as a single monthly statement and paid via one ACH transfer.
The module follows that accounting reality: one Ramp credit line maps
to one Odoo bank-type journal, regardless of how many cards are issued
under it.

Key features
============

* OAuth2 client_credentials auth — the module mints and caches a
  short-lived bearer keyed on the provider record. Refreshes
  transparently on expiry or HTTP 401.
* Concurrent-safe token refresh — SELECT ... FOR UPDATE on the
  provider row plus a post-lock cache re-check ensures two parallel
  cron runs on the same provider can't mint two separate bearers.
* Transient-failure retry — urllib3 Retry adapter mounted on
  https://: 3 tries with exponential backoff on 502/503/504. Applied
  to both the token mint and the data-fetch session.
* SSRF / bearer-leak guard on pagination — Ramp's `page.next` URL is
  validated to be HTTPS on the configured Ramp host (hostname
  comparison, port- and case-tolerant) before being followed, so a
  tampered response cannot redirect the bearer.
* Bounded pagination — fetch loop caps at 1000 pages with an explicit
  UserError if Ramp's cursor never terminates.
* State filter — transactions in DECLINED, PENDING_INITIATION, or
  ERROR are skipped before producing statement lines.
* Sign convention — Ramp returns positive amounts on spend; the module
  flips the sign so spend lands as a negative line on the credit-line
  liability journal (standard Odoo bank-statement convention).
* raw_data preservation — the full Ramp transaction payload including
  card_id, user_id, sk_category_name, accounting_categories is stored
  on each statement line for downstream automation.

35 mocked-HTTP tests covering: service registration, single/multi-
page cursor pagination (and max-pages safeguard), mapping, sign flip,
state filtering, OAuth2 token cache hit/miss/refresh, missing-token
and missing-expires_in edge cases, 401 → refresh → retry, network
errors, missing-credentials guard, SSRF cursor guard (cross-env,
port-tolerant, case-insensitive, user-authority bypass).
@dnplkndll
dnplkndll force-pushed the 18.0-add-account-statement-import-online-ramp branch from 92e7415 to 3aa1064 Compare May 12, 2026 13:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

mod:account_statement_import_online_ramp Module account_statement_import_online_ramp series:18.0

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants