@droplinked_inc/payment-hub
v0.2.0
Published
PSP-aggregator orchestration for droplinked: routes payment-intent creation and webhook verification across Stripe, PayPal, Bonum, PayMob, Telr, Coinbase Commerce, x402, and XION. Hardened recover+rewrite of [email protected].
Readme
@droplinked_inc/payment-hub
PSP-aggregator orchestration for droplinked. Routes payment-intent
creation to the droplinked backend and verifies inbound webhooks against
registered, dependency-injected adapters. Hardened recover+rewrite of
[email protected] (last hostile-published by
droplinked-component).
See THREAT_MODEL.md for the five hardening
commitments and the tests that prove each one.
Install
pnpm add @droplinked_inc/payment-hubzod is a runtime dependency. PSP SDKs (@stripe/stripe-js, etc.) are
NOT bundled — wire them as adapters at composition time.
Quick start
import {
PaymentHub,
createStripeAdapter,
createPaypalAdapter,
createBonumAdapter,
} from '@droplinked_inc/payment-hub';
import Stripe from 'stripe';
import { createHmac } from 'node:crypto';
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
const hub = new PaymentHub({
apiBaseUrl: 'https://apiv3.droplinked.com/',
apiTestnetBaseUrl: 'https://apiv3dev.droplinked.com/',
getAuthToken: () => session.accessToken,
maxRetries: 2,
requestTimeoutMs: 15_000,
adapters: {
stripe: createStripeAdapter({
verify: ({ rawBody, headers, secret }) => {
const sig = String(headers['stripe-signature'] ?? '');
stripe.webhooks.constructEvent(Buffer.from(rawBody), sig, secret);
return true;
},
}),
paypal: createPaypalAdapter({
verify: async ({ rawBody, headers, secret }) => {
// delegate to PayPal SDK webhook-id verification
return verifyPaypalWebhook(rawBody, headers, secret);
},
}),
bonum: createBonumAdapter({
computeHmac: (raw, secret) =>
createHmac('sha256', secret).update(raw).digest('hex'),
}),
},
});
// Create a payment intent (Stripe).
const intent = await hub.createPaymentIntent({
orderId: 'ord_123',
paymentMethod: 'stripe',
isTestnet: false,
});
console.log(intent.clientSecret);
// PayPal convenience.
const url = await hub.getPaypalCheckoutUrl({
orderId: 'ord_123',
returnUrl: 'https://shop.example.com/success',
cancelUrl: 'https://shop.example.com/cancel',
});
// Inbound webhook verification.
const ok = await hub.verifyWebhook({
provider: 'stripe',
rawBody: req.rawBody,
headers: req.headers,
secret: process.env.STRIPE_WEBHOOK_SECRET!,
});Public API
| Symbol | Kind | Notes |
|---|---|---|
| PaymentHub | class | Orchestrator — createPaymentIntent, getPaypalCheckoutUrl, verifyWebhook, registerAdapter, hasAdapter, baseUrl |
| PspAdapter | interface | { provider, verifyWebhook, getStatus? } |
| PaymentProvider | type | stripe \| paypal \| bonum \| paymob \| telr \| coinbase-commerce \| x402 \| xion \| web3 |
| PaymentProviderSchema | zod | Validates incoming provider strings |
| CreatePaymentIntentOptionsSchema | zod | Validates intent creation input |
| PaymentIntentResultSchema | zod | Validates backend responses |
| createStripeAdapter, createPaypalAdapter, createBonumAdapter, createPaymobAdapter, createTelrAdapter, createCoinbaseCommerceAdapter, createX402Adapter | factories | DI-style — never bundle the upstream SDK |
| createAdapter, createHmacAdapter | factories | Build a custom adapter |
| MemoryIdempotencyCache | class | Bounded FIFO + TTL, swap via idempotencyCache option |
| deriveIdempotencyKey, timingSafeEqual | helpers | Exposed for adapter authors |
| redactSecrets | helper | Pre-publish error scrubbing |
| PaymentError, PaymentConfigError, PaymentNetworkError, PaymentValidationError, PaymentProviderMismatchError, PaymentWebhookError, PaymentIdempotencyConflictError, PaymentTimeoutError, PaymentAbortError | errors | All extend Error, all scrub credentials |
Compatibility notes
- The legacy
createPaymentIntent(orderId, method, isTestnet, returnUrl, cancelUrl)positional signature is replaced by a single-object form onPaymentHub.createPaymentIntent({ ... }). Consumers should update call sites; a thin positional shim can be added later if the rollout pace demands it. DroplinkedPaymentIntent(React) anduseXionWallet(React hook) from the legacy package are intentionally NOT shipped here. The React UI surface is a separate concern and will live in a UI package (most likely@droplinked_inc/ui-kit) once the underlying transports (Stripe Elements, AbstraxionProvider) are wired through DI as well.
Development
pnpm --filter @droplinked_inc/payment-hub typecheck
pnpm --filter @droplinked_inc/payment-hub test:coverage
pnpm --filter @droplinked_inc/payment-hub build
pnpm --filter @droplinked_inc/payment-hub lint