# @suaveplan/payment-provider-paypal - PayPal payment provider for @suaveplan/payment-providers. Implements the PaymentProvider interface against the PayPal Orders v2 + Payments Captures Refund APIs, with an OAuth2 client-credentials token cache, injectable now/base64Encode/FetchLike seams, sandbox-endpoint shortcut, MoneyLike bigint marshalling, and structured PaymentProviderError surfaces. Universal — runs unchanged on Node, Bun, browsers, and Web Workers. PayPal sibling adapter for the slim [`@suaveplan/payment-providers`](../payment-providers/README.md) foundation. Ships `PayPalProvider` — a `PaymentProvider` implementation against the PayPal Orders v2 + Payments Captures Refund APIs, with an OAuth2 client-credentials token cache, injectable `now` / `base64Encode` / `FetchLike` seams, a `sandbox` shortcut, `MoneyLike` bigint marshalling, and structured `PaymentProviderError` surfaces. Universal — runs unchanged on Node, Bun, browsers, and Web Workers. ## Documentation tree - [paypal](./src/paypal/paypal.md) — `PayPalProvider` is the PayPal-flavoured implementation of the `PaymentProvider` contract exported by `@suaveplan/payme… - [mapping](./src/paypal/mapping.md) — Pure mapping utilities used by `PayPalProvider` to bridge the PayPal Orders v2 + Payments Captures Refund wire shapes i… - [paypal](./src/paypal/paypal.md) — `PayPalProvider` is the PayPal-flavoured implementation of the `PaymentProvider` contract exported by `@suaveplan/payme… ## Install ```bash bun add @suaveplan/payment-provider-paypal @suaveplan/payment-providers @suaveplan/payment ``` ## Quick start ```ts import { ProviderRegistry } from "@suaveplan/payment-providers"; import { PayPalProvider } from "@suaveplan/payment-provider-paypal"; const registry = new ProviderRegistry(); registry.register( new PayPalProvider({ clientId: process.env.PAYPAL_CLIENT_ID ?? "", clientSecret: process.env.PAYPAL_CLIENT_SECRET ?? "", sandbox: true, }) ); const paypal = registry.get("paypal"); const intent = await paypal.createIntent({ amount: { amount: 5000n, currency: "USD" }, description: "Order #1234", captureMethod: "automatic", idempotencyKey: "order-1234", }); console.log(intent.id, intent.status); ``` ## Features - `class PayPalProvider implements PaymentProvider` with literal-typed `name: "paypal"` - OAuth2 client-credentials token cache with 60 s clock-skew buffer, refreshed transparently on expiry - Injectable `now: NowFn`, `base64Encode: Base64EncodeFn`, and `fetch: FetchLike` for universal determinism and testability - `sandbox` flag → `https://api-m.sandbox.paypal.com`; explicit `baseUrl` always wins - `MoneyLike` bigint minor-unit amounts marshalled to PayPal's major-unit decimal-string format - `PayPal-Request-Id` idempotency-header support on `createIntent` and `refund` - Exhaustive PayPal-status → `PaymentStatus` / `RefundStatus` mapping; unknown statuses map to `failed` - Structured `PaymentProviderError` (from `@suaveplan/payment-providers`) on every failure path — `OAUTH_TOKEN_FAILURE`, `HTTP_ERROR`, `TIMEOUT`, `PARSE_ERROR` — always with `provider: "paypal"` - Universal: zero Node / browser / React imports; `sideEffects: false` - 100% Istanbul coverage; sibling `.md` per implementation file ## Modules - [`src/paypal/paypal.md`](./src/paypal/paypal.md) — `PayPalProvider` class, `PayPalConfig` interface, lifecycle methods, OAuth token cache - [`src/paypal/mapping.md`](./src/paypal/mapping.md) — pure helpers: `mapStatus`, `mapRefundStatus`, `parseAmount`, `formatAmount`, `mapOrder` ## Foundation This package depends on [`@suaveplan/payment-providers`](../payment-providers/README.md) for: - `httpRequest`, `composeAbortSignal`, `FetchLike` - `OAuthConfig`, `NowFn`, `Base64EncodeFn` - `PaymentProviderError`, `PAYMENT_PROVIDER_CODES`, `oauthTokenFailure` and on [`@suaveplan/payment`](../payment/README.md) for the structural `PaymentProvider`, `PaymentIntent`, `Refund`, `PaymentStatus`, `RefundStatus`, `CreatePaymentIntentInput`, and `RefundInput` types. ## Related sibling packages - `@suaveplan/payment-provider-stripe` - `@suaveplan/payment-provider-square` - `@suaveplan/payment-provider-mollie` - `@suaveplan/payment-provider-adyen` All four follow the same pattern: depend on the slim core, expose a single `*Provider` class, register cleanly into `ProviderRegistry`. ## License MIT