npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@86d-app/payments

v0.0.42

Published

Payment processing abstraction for 86d commerce platform

Readme

[!WARNING] This project is under active development and is not ready for production use. Please proceed with caution. Use at your own risk.

📚 Documentation: 86d.app/docs/modules/payments

Payments Module

Provider-neutral payment ownership for the 86d commerce platform. The additive v2 boundary records named Store Payment Connections and durable provider operations; legacy intent, method, and refund APIs remain migration state.

version license

Installation

npm install @86d-app/payments

Usage

import payments from "@86d-app/payments";
import { createModuleClient } from "@86d-app/core";

// Without a provider (offline/test mode)
const client = createModuleClient([payments()]);

// With a Stripe provider
import { StripePaymentProvider } from "@86d-app/stripe";
const provider = new StripePaymentProvider("sk_live_...");
const client = createModuleClient([payments({ provider, currency: "USD" })]);

Configuration

| Option | Type | Default | Description | |---|---|---|---| | currency | string | "USD" | Default currency for payment intents | | provider | PaymentProvider | undefined | Payment processor implementation | | connectionProviders | PaymentConnectionProvider[] | undefined | Server-created v2 adapters, each bound to one immutable Connection |

PaymentProvider Interface

Implement this interface to connect any payment processor:

interface PaymentProvider {
  createIntent(params: {
    amount: number;      // in smallest currency unit (e.g. cents)
    currency: string;
    metadata?: Record<string, unknown>;
  }): Promise<ProviderIntentResult>;

  confirmIntent(providerIntentId: string): Promise<ProviderIntentResult>;

  cancelIntent(providerIntentId: string): Promise<ProviderIntentResult>;

  createRefund(params: {
    providerIntentId: string;
    amount?: number;    // partial refund; omit for full refund
    reason?: string;
  }): Promise<ProviderRefundResult>;
}

Missing provider: a positive Payment cannot be confirmed or refunded without a configured provider. Explicit offline behavior is restricted to non-production development callers.

Payment Connections v2

PaymentsOptions.connectionProviders accepts server-created adapters bound to one immutable connectionId and server-provisioned provider-owned providerAccountId. The host is responsible for verifying that the credential inside the adapter authorizes that account before binding it; the local identifier is not provider approval or live evidence. The owner-local paymentConnections controller manages named Connections and durable intent, authorization, capture, refund, and void operations. Each operation records its Connection, operation-specific idempotency key, request digest, provider reference, attempt history, and ambiguous or needs-attention state.

The v2 service is intentionally not exposed as a shopper endpoint or wired into the legacy Checkout capability. A host must supply an owner-local locking transaction runner, and every enabled Connection must have an exact provider/account/mode/capability adapter match. Credential rotation disables the Connection but cannot change its persisted provider account identity; an adapter authorized for another upstream account is rejected after restart. Missing, disabled, revoked, unhealthy, or mismatched Connections fail closed. Capture, refund, and void operations must continue a succeeded source operation and use its original Connection and provider reference.

The opaque secretReference is server-side configuration data. It must never be returned by Store or admin endpoints, browser output, logs, or agent output.

Payment aggregate and recovery

paymentAggregates owns the v2 shopper Payment record. Creation freezes the Checkout, optional Order, Payment option, Connection, expected integer amount, currency, and eligible-merchandise fee basis. Confirmed authorization, capture, void, and refund facts cite exact source operations. Owner-local locks reserve in-flight totals so distinct idempotency keys cannot bypass cumulative capture or refund ceilings. Disputes update a separate projection and never count as refunds or settlement.

The state projection is derived from confirmed totals: pending, authorized, partially captured, captured, partially refunded, refunded, or voided. Only a fully captured and fully refunded accepted amount is terminal refunded; a full authorization void with no capture is terminal voided. Confirmed transitions and their Payment snapshots enter the transactional outbox with the aggregate update.

Provider-known pending and requires_action outcomes retain the provider reference and normalized result without advancing the Payment aggregate. They use longer, state-specific bounded polling schedules and remain distinguishable from ambiguous, which means the provider outcome itself is unknown. Exhausted known-state polling preserves that provider truth for manual attention; it does not convert the operation to success or dead letter.

All nonfinal and stale operations retain their original Connection, immutable creation time, caller key, and payload. The controller exposes bounded scheduled backoff, stale-running recovery, dead-letter state for unresolved ambiguity, and audited manual reconciliation. Caller keys are capped at 108 characters so the same key can be forwarded to every supported provider. This is the durable worker seam; no scheduler or shopper route is activated by the module.

Durable webhook receipt foundation

paymentWebhookReceipts accepts only provider facts that a server-side Integration has already signature-verified against the exact Connection. It persists a unique Store/Connection/provider/event receipt, payload digest, normalized fact, processing lease, attempts, and disposition without storing the raw payload or a secret. Exact replays acknowledge the existing result; digest or normalized-fact conflicts fail closed. A crash after applying a fact is safe because Payment-owner operation/dispute identities replay exactly.

Provider network reconciliation occurs outside the receipt transaction. Out-of-order or ambiguous facts remain unacknowledged in needs_attention until canonical reconciliation completes. PayPal, Stripe, and Braintree registered webhooks do not use this controller yet and continue returning the explicit 503 durability containment response.

Store Endpoints

| Method | Path | Description | |---|---|---| | GET | /payments/methods | Contained: verified Customer + Payment Connection required | | DELETE | /payments/methods/:id | Contained: verified Customer + Payment Connection required |

Generic shopper intent create/get/confirm/cancel and saved-method source files remain as migration history but their unsafe paths are not registered by the Module. Live activation stays contained until the v2 Checkout finalizer, verified Store Customer binding, and provider paths satisfy the critical-path contract.

Admin Endpoints

| Method | Path | Description | |---|---|---| | GET | /admin/payments | List all intents (filter: customerId, status, orderId) | | GET | /admin/payments/:id | Get intent detail | | POST | /admin/payments/:id/refund | Contained: original-Connection-bound v2 operation required | | GET | /admin/payments/:id/refunds | List refunds for an intent |

Legacy v1 Controller API

// ── Payment intents ─────────────────────────────────────────────────────────

controller.createIntent(params: {
  amount: number;             // positive integer, smallest currency unit (e.g. cents)
  currency?: string;          // default: module currency option
  customerId?: string;
  email?: string;
  orderId?: string;
  checkoutSessionId?: string;
  metadata?: Record<string, unknown>;
}): Promise<PaymentIntent>
// Throws: "Amount must be a positive integer"

controller.getIntent(id: string): Promise<PaymentIntent | null>

controller.confirmIntent(id: string): Promise<PaymentIntent | null>
// Throws: "Cannot confirm intent in '{status}' state" for terminal states

controller.cancelIntent(id: string): Promise<PaymentIntent | null>
// Throws: "Cannot cancel intent in '{status}' state" for succeeded/failed/refunded

controller.listIntents(params?: {
  customerId?: string;
  status?: PaymentIntentStatus;
  orderId?: string;
  take?: number;
  skip?: number;
}): Promise<PaymentIntent[]>

// ── Payment methods ─────────────────────────────────────────────────────────

// Saves a payment method; if isDefault=true, clears all other defaults
controller.savePaymentMethod(params: {
  customerId: string;
  providerMethodId: string;  // e.g. Stripe's pm_xxx
  type?: string;             // "card" | "bank_transfer" | "wallet"
  last4?: string;
  brand?: string;            // "visa" | "mastercard" | etc.
  expiryMonth?: number;
  expiryYear?: number;
  isDefault?: boolean;
}): Promise<PaymentMethod>

controller.getPaymentMethod(id: string): Promise<PaymentMethod | null>

controller.listPaymentMethods(customerId: string): Promise<PaymentMethod[]>

controller.deletePaymentMethod(id: string): Promise<boolean>

// ── Refunds ─────────────────────────────────────────────────────────────────

// Only on succeeded/refunded intents. Cumulative refunds capped at intent amount.
controller.createRefund(params: {
  intentId: string;
  amount?: number;   // positive integer; omit for full refund
  reason?: string;
}): Promise<Refund>
// Throws: "Payment intent not found"
// Throws: "Cannot refund intent in '{status}' state"
// Throws: "Refund amount must be positive"
// Throws: "Refund amount {n} exceeds remaining refundable amount {m}"

controller.getRefund(id: string): Promise<Refund | null>

controller.listRefunds(intentId: string): Promise<Refund[]>

Legacy Controller Example

This example describes direct server-side controller use during migration. It is not the active Store Checkout path and does not provide Payment Connection durability.

// 1. Customer initiates checkout — create intent
const intent = await controller.createIntent({
  amount: 4999,   // $49.99
  currency: "USD",
  customerId: "cust_123",
  orderId: "ord_456",
});
// intent.status === "pending"
// With Stripe: intent.providerMetadata.clientSecret → send to frontend

// 2. Customer completes payment on frontend → call confirm
const confirmed = await controller.confirmIntent(intent.id);
// confirmed.status === "succeeded"

// 3. Customer requests refund
const refund = await controller.createRefund({
  intentId: intent.id,
  reason: "customer request",
});
// refund.status === "succeeded"
// intent.status is now "refunded"

// 4. Save a payment method for future use
const method = await controller.savePaymentMethod({
  customerId: "cust_123",
  providerMethodId: "pm_stripe_xxx",
  type: "card",
  last4: "4242",
  brand: "visa",
  isDefault: true,
});

Payment Intent Statuses

| Status | Description | |---|---| | pending | Intent created, payment not yet initiated | | processing | Payment is being processed | | succeeded | Payment completed successfully | | failed | Payment failed | | cancelled | Intent was cancelled | | refunded | Legacy projection after a refund; not authoritative for partial-refund accounting |

Financial Safety Guards

The payments controller enforces several financial safety rules at the controller level:

| Rule | Description | |---|---| | Amount validation | createIntent rejects zero, negative, and fractional amounts. Amount must be a positive integer (smallest currency unit). | | Confirm guards | confirmIntent only works on pending or processing intents. Throws on cancelled, failed, refunded. | | Cancel guards | cancelIntent only works on pending or processing intents. Throws on succeeded, failed, refunded. | | Refund guards | createRefund only works on succeeded or refunded intents. Throws on pending, processing, cancelled, failed. | | Refund cap | Cumulative non-failed refunds cannot exceed the original intent amount. Partial refunds are tracked and summed. | | Refund amount | Refund amount must be positive. Zero and negative amounts are rejected. | | Webhook dedup | handleWebhookRefund deduplicates by providerRefundId — webhook retries return the existing refund instead of creating duplicates. |

Types

type PaymentIntentStatus =
  | "pending" | "processing" | "succeeded"
  | "failed" | "cancelled" | "refunded";

type RefundStatus = "pending" | "succeeded" | "failed";

interface PaymentIntent {
  id: string;
  providerIntentId?: string;   // e.g. Stripe's pi_xxx
  customerId?: string;
  email?: string;
  amount: number;
  currency: string;
  status: PaymentIntentStatus;
  paymentMethodId?: string;
  orderId?: string;
  checkoutSessionId?: string;
  metadata: Record<string, unknown>;
  providerMetadata: Record<string, unknown>;
  createdAt: Date;
  updatedAt: Date;
}

interface PaymentMethod {
  id: string;
  customerId: string;
  providerMethodId: string;
  type: string;
  last4?: string;
  brand?: string;
  expiryMonth?: number;
  expiryYear?: number;
  isDefault: boolean;
  createdAt: Date;
  updatedAt: Date;
}

interface Refund {
  id: string;
  paymentIntentId: string;
  providerRefundId: string;
  amount: number;
  reason?: string;
  status: RefundStatus;
  createdAt: Date;
  updatedAt: Date;
}