UCS E2E: PayPal Apple Pay fails via HS Router due to PaymentService vs CompositePaymentService mismatch
bug
## Background
While testing PR #1047 (PayPal Apple Pay support in UCS), E2E payments via HS Router fail even though direct gRPC calls to UCS succeed. This issue documents the root cause and the open architectural question.
---
## Discovery: Two gRPC Services in UCS for Payment Authorization
UCS exposes two separate gRPC services for payment authorization:
### 1. `PaymentService/Authorize`
- Standard authorize endpoint
- For connectors where `should_do_access_token = true`, it **requires** the access token to be pre-fetched by the caller and passed in `state.access_token`
- If the token is missing, UCS returns: `unauthenticated: "Connector requires an access token; provide it via state.access_token"`
- See: `crates/grpc-server/grpc-server/src/server/payments.rs` lines 806–821
### 2. `CompositePaymentService/Authorize`
- Extended authorize endpoint
- Internally calls `MerchantAuthenticationService/CreateServerAuthenticationToken` to fetch OAuth token when `should_do_access_token = true`
- Falls through to `PaymentService/Authorize` internally after fetching the token
- Is a functional superset of `PaymentService/Authorize`
- See: `crates/internal/composite-service/src/payments.rs`
**PayPal specifically** uses `CompositePaymentService/Authorize` because it requires an OAuth token fetched via PayPal's `/v1/oauth2/token` endpoint before every payment request.
---
## Current HS Router Behaviour
HS Router always calls `PaymentService/Authorize` for all connectors on the UCS execution path. This works fine for connectors that do not require an access token.
For PayPal, HS Router also attempts to pre-fetch the access token before calling UCS. This goes through `add_access_token` in `crates/router/src/core/payments/access_token.rs`, which for the UCS execution path calls `refresh_connector_auth` → UCS `CreateAccessToken` RPC. However, PayPal's `CreateAccessToken` is **not implemented** in UCS (returns `not_implemented`), so the pre-fetch fails.
Even if we skip the pre-fetch (which is one fix we explored), HS Router would still call `PaymentService/Authorize`, which then fails at PayPal's `get_headers` because no token is present in `ConnectorState`.
---
## Root Cause
```
HS Router
└─ UCS execution path
└─ always calls PaymentService/Authorize
↓
PayPal requires access token in state
→ UCS returns: "Failed to obtain authentication type"
```
PayPal must be called via `CompositePaymentService/Authorize` so UCS can fetch the OAuth token internally.
---
## What We Explored (and Why We Paused)
We implemented a fix in HS Router that:
1. Skips access token pre-fetch for UCS execution path (correct — UCS handles it internally)
2. Routes to `CompositePaymentService/Authorize` when `connector.supports_access_token(payment_method)` returns `true`
**Why we paused:** `supports_access_token` returns `true` for many connectors beyond PayPal (Airwallex, Globalpay, Fiservcommercehub, Volt, Truelayer, Itaubank, Iatapay, Trustly, Getnet, JPMorgan, etc.). The concern is:
- Some of these connectors may have OAuth implemented in HS Router (not in UCS's `CompositePaymentService`)
- Routing them to `CompositePaymentService` could break their flows if UCS's composite service doesn't have a working OAuth implementation for them
- We do not yet have a comprehensive map of which connectors use `CompositePaymentService` vs `PaymentService` in UCS
Scoping the fix to `connector == "paypal"` would work but feels like a precedent for connector-specific routing logic in the gateway layer.
---
## Open Questions
1. **Which connectors in UCS actually use `CompositePaymentService`?** Is it only PayPal today, or are others expected to migrate to it?
2. **For connectors where `should_do_access_token = true` in UCS** (Airwallex, Globalpay, etc.) — is HS Router expected to pre-fetch and pass the token, or should they also use `CompositePaymentService`?
3. **What is the intended long-term routing signal?** Options:
- A per-connector flag/config (e.g. `uses_composite_service: bool` in UCS rollout config)
- Always use `CompositePaymentService` for all UCS authorize calls (it's a superset, so safe for connectors that don't need OAuth)
- Connector-specific routing in HS Router (simplest but doesn't scale)
4. **Should `CreateAccessToken` RPC in UCS ever be called from HS Router?** Or should all token management be internal to UCS?
---
## Affected Connectors (connectors where `supports_access_token` = true in HS Router)
Airwallex, Globalpay, Deutschebank, Nordea, Payu, Trustpay (BankRedirect/Transfer), Tesouro, Iatapay, Volt, Itaubank, Facilitapay, Dwolla, Santander, Truelayer, Fiservcommercehub, **PayPal**
---
## Stash Reference
The partial implementation is stashed in the HS Router repo as:
```
stash@{0}: On main: paypal-applepay-composite-service-e2e-fix
```
Files modified:
- `crates/router/src/core/payments/access_token.rs` — skip pre-fetch on UCS path
- `crates/external_services/src/grpc_client/unified_connector_service.rs` — add `CompositePaymentServiceClient` + `composite_payment_authorize` method
- `crates/router/src/core/payments/gateway/authorize_gateway.rs` — route `supports_access_token` connectors to `CompositePaymentService`
- `config/development.toml` — PayPal Apple Pay entries
关闭于 2026-04-17 1 条评论