Skip to content

fix(connector): send paypal billing agreements as billing_agreement_id - #13767

Open
cryptiklemur wants to merge 2 commits into
juspay:mainfrom
cryptiklemur:paypal-billing-agreement-mit
Open

fix(connector): send paypal billing agreements as billing_agreement_id#13767
cryptiklemur wants to merge 2 commits into
juspay:mainfrom
cryptiklemur:paypal-billing-agreement-mit

Conversation

@cryptiklemur

@cryptiklemur cryptiklemur commented Aug 18, 2026

Copy link
Copy Markdown

Type of Change

  • Bugfix

Description

Orders v2 accepts a stored PayPal payment method under two different keys:

stored thing key id shape
Payment Method Tokens v3 vault entry vault_id no fixed prefix
Express Checkout billing agreement billing_agreement_id B-...

The connector sends both as vault_id. For a merchant migrating existing billing agreements from another provider, every MIT fails.

The two keys are not interchangeable. Probing Orders v2 with the same made-up B- id under each key gives different failures, so PayPal routes them separately:

payment_source.paypal.vault_id              -> NOT_AUTHORIZED / PERMISSION_DENIED
payment_source.paypal.billing_agreement_id  -> UNPROCESSABLE_ENTITY / BILLING_AGREEMENT_NOT_FOUND

BILLING_AGREEMENT_NOT_FOUND is the expected answer for an id that does not exist, so the format is recognised under that key and not under the other. A real billing agreement charged successfully through billing_agreement_id against live PayPal.

This picks the key by prefix, so v3 vault tokens keep their current behaviour.

Additional Changes

  • API modification
  • Config modification

Motivation and Context

Closes #13768

Merchants moving to Hyperswitch from a provider that stored Express Checkout billing agreements cannot charge those agreements today. Their subscribers would have to re-authorise, even though PayPal keeps the agreements valid until the buyer cancels them.

How did you test it?

Manually, against PayPal.

Sent the same B- id to Orders v2 under each key and compared the errors, as above. Charged a real Express Checkout billing agreement through billing_agreement_id and it completed, confirming the key works for reference transactions.

cargo check -p hyperswitch_connectors --features v1 reports no errors in paypal. The remaining errors in that run are in trustly and worldpayxml and need feature flags unrelated to this change.

Checklist

  • I formatted the code (cargo fmt --all on stable; nightly was not available, so the
    nightly-only import rules are unverified)
  • I addressed lints thrown by cargo clippy
  • I reviewed the submitted code

@cryptiklemur
cryptiklemur requested a review from a team as a code owner August 18, 2026 13:23
@semanticdiff-com

semanticdiff-com Bot commented Aug 18, 2026

Copy link
Copy Markdown

Review changes with  SemanticDiff

Changed Files
File Status
  crates/hyperswitch_connectors/src/connectors/paypal/transformers.rs  52% smaller

Orders v2 accepts a stored paypal payment method under two different keys.
Payment Method Tokens v3 entries go in vault_id, Express Checkout billing
agreements go in billing_agreement_id. The connector sent both as vault_id,
so a merchant migrating existing billing agreements gets PERMISSION_DENIED.

Billing agreement ids are prefixed B-, which is how the two are told apart.
@cryptiklemur
cryptiklemur force-pushed the paypal-billing-agreement-mit branch from 45d27ab to ee745db Compare August 18, 2026 13:28
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.

PayPal: Express Checkout billing agreements sent as vault_id, so stored agreements cannot be charged

1 participant