{"_id":"@droplinked_inc/payment-intent","_rev":"2-0945308a6b09425820a58398d4f032df","name":"@droplinked_inc/payment-intent","dist-tags":{"latest":"0.1.1"},"versions":{"0.1.0":{"name":"@droplinked_inc/payment-intent","version":"0.1.0","license":"MIT","_id":"@droplinked_inc/payment-intent@0.1.0","maintainers":[{"name":"droplinked_inc","email":"ali@droplinked.com"}],"homepage":"https://github.com/droplinked/droplink-packages#readme","bugs":{"url":"https://github.com/droplinked/droplink-packages/issues"},"dist":{"shasum":"bba1e6573cf44ad311047670c331c967ca04f332","tarball":"https://registry.npmjs.org/@droplinked_inc/payment-intent/-/payment-intent-0.1.0.tgz","fileCount":48,"integrity":"sha512-T+MLeeIybNWNjfgNyK5JTJB+R1iYI/D61IvA9QxO+N+rUhqvyP0KVDwBDdj3hKZQN1s8nxmCCNqv7wsORmRi7w==","signatures":[{"sig":"MEUCIGw7Fqta9ggP2FIKUFqEF/0hTmDqtnMd1BzqX0H73NwZAiEAzDAK+awJCF7hq969lFXI+Y6XWTNIJBxANrOFjoFylj8=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":172299},"main":"./dist/index.js","type":"module","_from":"file:droplinked_inc-payment-intent-0.1.0.tgz","types":"./dist/index.d.ts","engines":{"node":">=22.0.0"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"scripts":{"lint":"eslint src --max-warnings=0","test":"jest","build":"tsc -p tsconfig.json","typecheck":"tsc --noEmit -p tsconfig.json","test:coverage":"jest --coverage"},"_npmUser":{"name":"droplinked_inc","email":"ali@droplinked.com"},"_resolved":"/tmp/f28dbbea6a65eac5ae585e3f1937eef9/droplinked_inc-payment-intent-0.1.0.tgz","_integrity":"sha512-T+MLeeIybNWNjfgNyK5JTJB+R1iYI/D61IvA9QxO+N+rUhqvyP0KVDwBDdj3hKZQN1s8nxmCCNqv7wsORmRi7w==","repository":{"url":"git+https://github.com/droplinked/droplink-packages.git","type":"git","directory":"packages/payment-intent"},"_npmVersion":"10.9.7","description":"Hardened rebuild of droplinked-payment-intent@1.5.0. Server-side PaymentIntent state machine + idempotency + cross-PSP defence. Pairs with @droplinked_inc/payment-hub.","directories":{},"sideEffects":false,"_nodeVersion":"22.22.2","dependencies":{"zod":"^3.23.8"},"publishConfig":{"access":"public","provenance":false},"_hasShrinkwrap":false,"devDependencies":{"fast-check":"^3.22.0"},"_npmOperationalInternal":{"tmp":"tmp/payment-intent_0.1.0_1779168343386_0.49262151334718807","host":"s3://npm-registry-packages-npm-production"}},"0.1.1":{"name":"@droplinked_inc/payment-intent","version":"0.1.1","description":"Hardened rebuild of droplinked-payment-intent@1.5.0. Server-side PaymentIntent state machine + idempotency + cross-PSP defence. Pairs with @droplinked_inc/payment-hub.","license":"MIT","type":"module","main":"./dist/index.js","types":"./dist/index.d.ts","sideEffects":false,"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"dependencies":{"zod":"^3.23.8"},"devDependencies":{"fast-check":"^3.22.0"},"publishConfig":{"provenance":false,"access":"public"},"repository":{"type":"git","url":"git+https://github.com/droplinked/droplink-packages.git","directory":"packages/payment-intent"},"engines":{"node":">=22.0.0"},"scripts":{"build":"tsc -p tsconfig.json","test":"jest","test:coverage":"jest --coverage","lint":"eslint src --max-warnings=0","typecheck":"tsc --noEmit -p tsconfig.json"},"_id":"@droplinked_inc/payment-intent@0.1.1","bugs":{"url":"https://github.com/droplinked/droplink-packages/issues"},"homepage":"https://github.com/droplinked/droplink-packages#readme","_integrity":"sha512-TyH1B4Ro83FIoGX5fkDEB1pKO2r428k1TvQUvIMSXgq5ZCWXYrvtqhR7I8Yvfk20S7yE+ye22uDWs5WQLzBW6Q==","_resolved":"/tmp/6021b51e0221ecaee989574614cfef48/droplinked_inc-payment-intent-0.1.1.tgz","_from":"file:droplinked_inc-payment-intent-0.1.1.tgz","_nodeVersion":"22.23.1","_npmVersion":"11.19.0","dist":{"integrity":"sha512-TyH1B4Ro83FIoGX5fkDEB1pKO2r428k1TvQUvIMSXgq5ZCWXYrvtqhR7I8Yvfk20S7yE+ye22uDWs5WQLzBW6Q==","shasum":"3b31cd6adce593ac0fe00bae8b0f26bb62711412","tarball":"https://registry.npmjs.org/@droplinked_inc/payment-intent/-/payment-intent-0.1.1.tgz","fileCount":52,"unpackedSize":175923,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQDu6uEsU26fFGP2LifxV3PwoniOuKHLufES4IqXyZvYTAIhAKNdKIeBah1h0LOvPx3ScjmWKpHz/eOR8Q1JrLBfiiHl"}]},"_npmUser":{"name":"droplinked_inc","email":"ali@droplinked.com"},"directories":{},"maintainers":[{"name":"droplinked_inc","email":"ali@droplinked.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/payment-intent_0.1.1_1786016229975_0.31153940601865027"},"_hasShrinkwrap":false}},"time":{"created":"2026-05-19T05:25:43.274Z","modified":"2026-08-06T11:37:10.264Z","0.1.0":"2026-05-19T05:25:43.558Z","0.1.1":"2026-08-06T11:37:10.115Z"},"bugs":{"url":"https://github.com/droplinked/droplink-packages/issues"},"license":"MIT","homepage":"https://github.com/droplinked/droplink-packages#readme","repository":{"type":"git","url":"git+https://github.com/droplinked/droplink-packages.git","directory":"packages/payment-intent"},"description":"Hardened rebuild of droplinked-payment-intent@1.5.0. Server-side PaymentIntent state machine + idempotency + cross-PSP defence. Pairs with @droplinked_inc/payment-hub.","maintainers":[{"name":"droplinked_inc","email":"ali@droplinked.com"}],"readme":"# `@droplinked_inc/payment-intent`\n\n> Replaces `droplinked-payment-intent`. See [MIGRATION.md](../../docs/MIGRATION.md) for the swap path.\n\n> Server-side **PaymentIntent state machine, idempotency, and refund accounting** for the droplinked platform. Pairs with [`@droplinked_inc/payment-hub`](../payment-hub) (PSP orchestration).\n\nThis package is the durable record of a payment's lifecycle. It does\n**not** call out to PSPs and it does **not** verify webhook signatures\n— `payment-hub` and its adapters own that. What it owns is the legal\nstate graph, idempotent creation, refund accounting under bigint\narithmetic, optimistic-concurrency-protected updates, and a tamper-\nevident audit history.\n\nSee [`THREAT_MODEL.md`](./THREAT_MODEL.md) for the eight P0 threats\nthis package mitigates and how.\n\n## State machine\n\n```\n   requires_payment_method ─► requires_confirmation ─► processing\n            │                          │                     │\n            │                          │                     ├─► succeeded\n            │                          │                     │       │\n            │                          │                     │       ├─► partially_refunded ──► refunded\n            │                          │                     │       └─► refunded\n            │                          │                     │\n            │                          │                     └─► failed\n            │                          │\n            └────────── canceled ◄─────┘\n```\n\nTerminal states (`refunded`, `failed`, `canceled`) have **no outgoing\ntransitions**. Every transition is gated by the allowlist in\n`state-machine.ts` and goes through one private codepath\n(`commitTransition`).\n\n## Install\n\n```sh\npnpm add @droplinked_inc/payment-intent\n```\n\n## Quick start\n\n```ts\nimport {\n  PaymentIntentService,\n  MemoryIdempotencyStore,\n  MemoryPaymentIntentRepository,\n} from '@droplinked_inc/payment-intent';\n\nconst svc = new PaymentIntentService({\n  // production: inject Mongo/Redis-backed implementations\n  repository: new MemoryPaymentIntentRepository(),\n  idempotencyStore: new MemoryIdempotencyStore(),\n});\n\n// 1. Create\nconst intent = await svc.create({\n  orderId: 'ord_abc',\n  provider: 'stripe',\n  intentType: 'payment',\n  amountMinorUnits: 1999n,  // $19.99 in cents\n  currency: 'USD',\n  idempotencyNonce: 'checkout-button-click-uuid',\n});\n\n// 2. Walk it through the machine\nawait svc.confirm(intent.id);\nawait svc.markProcessing(intent.id);\nawait svc.markSucceeded(intent.id);\n\n// 3. Refund (partial-first, then top-up)\nawait svc.refund({\n  intentId: intent.id,\n  provider: 'stripe',\n  amountMinorUnits: 999n,\n  currency: 'USD',\n  refundEventId: 're_xyz',\n  reason: 'partial-refund',\n});\n// state is now `partially_refunded`\n\nawait svc.refund({\n  intentId: intent.id,\n  provider: 'stripe',\n  amountMinorUnits: 1000n,\n  currency: 'USD',\n  refundEventId: 're_xyz_2',\n  reason: 'complete-refund',\n});\n// state is now `refunded` — terminal\n```\n\n## Webhook integration\n\nThe caller — usually a route handler in the droplinked backend — is\nresponsible for cryptographic signature verification (use\n`@droplinked_inc/payment-hub` adapters). Once verified, hand the\nstructured event to `applyWebhookEvent`:\n\n```ts\nconst updated = await svc.applyWebhookEvent({\n  intentId: 'pi_…',\n  provider: 'stripe',\n  eventId: 'evt_…',       // provider-side id (used for replay defence)\n  targetState: 'succeeded',\n  reason: 'payment_intent.succeeded',\n});\n```\n\nThe package will:\n\n1. Reject if the event's `provider` does not match the intent's\n   (cross-PSP defence — `ProviderMismatchError`).\n2. Reject if `eventId` was already applied (`WebhookEventReplayError`).\n3. Reject if the implied state is unreachable from the current state\n   (`InvalidStateTransitionError`).\n4. Append the event to the immutable `history` array, bump `version`,\n   and OCC-update the record.\n\n## Idempotency\n\nTwo derivation strategies:\n\n- **Deterministic** (`idempotencyNonce` or full input set):\n  `SHA-256(length-prefix(orderId, provider, amount, currency, nonce))`.\n  Re-submitting the same logical operation returns the same intent.\n- **Random** (no nonce supplied): 256 bits of CSPRNG entropy, hex.\n\nImplementations of `IdempotencyStore` for Redis/Mongo must enforce\natomic put-if-absent semantics. See `src/idempotency.ts`.\n\n## Errors\n\nEvery typed error extends `PaymentIntentError`, which extends `Error`.\nError messages are passed through `redactSecrets()` so credential-\nshaped tokens never leak through the surface.\n\n| Class                          | Code                       |\n|--------------------------------|----------------------------|\n| `InvalidStateTransitionError`  | `INVALID_STATE_TRANSITION` |\n| `ProviderMismatchError`        | `PROVIDER_MISMATCH`        |\n| `CurrencyMismatchError`        | `CURRENCY_MISMATCH`        |\n| `IdempotencyConflictError`     | `IDEMPOTENCY_CONFLICT`     |\n| `RefundExceedsChargeError`     | `REFUND_EXCEEDS_CHARGE`    |\n| `ConcurrentUpdateError`        | `CONCURRENT_UPDATE`        |\n| `WebhookEventReplayError`      | `WEBHOOK_REPLAY`           |\n| `WebhookSignatureError`        | `WEBHOOK_SIGNATURE`        |\n| `PaymentIntentNotFoundError`   | `NOT_FOUND`                |\n| `PaymentIntentValidationError` | `VALIDATION`               |\n\n## Test coverage\n\n> 90% lines / 95% branches / 100% funcs across the package.\nProperty-based tests (via `fast-check`) cover state-machine\ninvariants and refund-sum accumulation.\n\n## Development\n\n```sh\npnpm typecheck\npnpm lint\npnpm test\npnpm test:coverage\npnpm build\n```\n\n## License\n\nMIT — see monorepo root.\n","readmeFilename":"README.md"}