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

payload-payment-shared

v0.2.0

Published

Shared utilities, order-data builder and errors for PayloadCMS payment adapters (CorvusPay, Comgate, COD, MiniMax, Money S5)

Readme

payload-payment-shared

Shared utilities, order-data builder and errors for PayloadCMS payment adapters. Consolidates code that otherwise drifts across adapter packages (CorvusPay, Comgate, COD, MiniMax, Money S5) into a single source of truth.

Host-agnostic by design: the package never imports an app's generated payload-types, has no runtime dependencies, and takes collection slugs as runtime strings. payload is the only peer dependency.

Install

pnpm add payload-payment-shared

Peer deps: payload@^3.81.0, @payloadcms/db-postgres@^3.81.0.

All exports are at the root:

import { PaymentError, buildOrderDataFromTransaction } from 'payload-payment-shared'

The package ships dual ESM/CJS via tsup plus types at dist/index.d.ts.

Exports

Errors

| Export | Kind | Summary | | --- | --- | --- | | PaymentError | class | The only payment error class. Stable code taxonomy, dynamic details, native cause, plain-string message. | | PaymentErrorOptions | type | Constructor options. |

code is machine-readable and stable (REFUND_FAILED, SIGNATURE_INVALID, CART_CURRENCY_MISMATCH, …) — group on it in Sentry, and never put dynamic values in it. Dynamic payload goes in details.

Order creation

| Export | Kind | Summary | | --- | --- | --- | | buildOrderDataFromTransaction | function | Builds the orders create payload from a paid transaction. Pure — no reads, no writes. | | DEFAULT_ORDER_STATUS | const | 'processing' — the status a confirmed order is created with by default. | | OrderItemPricingResolver | type | Injection point for host-specific line pricing. | | BuildOrderDataOptions, OrderDataFromTransaction, OrderSourceTransaction, OrderSourceCart, OrderSourceCartItem, OrderItemData, OrderAddressSnapshot, OrderDiscountSnapshot, OrderShippingMethodSnapshot, OrderOssFields, OrderInvoiceType, OrderCurrency | types | The order/transaction shapes the builder maps between. |

Order creation exists twice per gateway — the adapter's confirmOrder (browser return) and the gateway webhook (server-to-server) — and the webhook usually wins the race. Two hand-written copies drift, and what they drop is consents, pricingContext and cart-written line prices on live paid orders. This builder is the one mapping both call:

const data = buildOrderDataFromTransaction({
  transaction,                 // read at depth 2 so cart items carry their products
  fallbackCurrency: 'EUR',     // when the transaction carries none
  customerId,                  // resolved by the caller (lookups are async)
  transactionId,               // links the payment onto the order
})

await payload.create({ collection: ordersSlug, data, req })

It is generic over the currency and status unions, so a host whose orders collection declares narrower enums gets its own literal types back and payload.create typechecks with no cast:

buildOrderDataFromTransaction<'EUR' | 'CZK', 'processing'>({ ... })

Three seams keep host-specific behaviour out of the package:

  • resolveItemPricing — by default each line is re-derived from the catalog by resolveOrderItemPricing. A host whose cart carries server-written prices that must NOT be re-derived (bundle apportionment, B2B contract prices) passes its own resolver; it receives the raw cart line and can trust item.priceAtPurchase.
  • destination — cross-border net × (1 + VAT) pricing for b2c lines. Omit for home deliveries so list prices pass through untouched.
  • status — the post-payment state, when it isn't 'processing'.

Host-specific order groups are spread on by the caller — the result is a plain object.

Pricing

| Export | Kind | Summary | | --- | --- | --- | | resolveOrderItemPricing | function | Line unit price in minor units + originalPrice when a sale undercuts the regular price. Reads priceIn{CUR} / salePriceIn{CUR} (gated by saleEnabled) / b2bPriceIn{CUR} / netPriceIn{CUR}. | | resolveOrderItemPrice | function | .price of the above, for callers that only need the charged amount. | | calcSubscriptionDiscountCents | function | Subscription line discount, in cents. | | PricingContext | type | 'b2c' (retail, honours salePrice) | 'b2b' (wholesale tier, never a consumer discount). | | ItemDestination, ResolvedItemPricing | types | Cross-border destination descriptor and the resolver result. |

Checkout consents

| Export | Kind | Summary | | --- | --- | --- | | extractCheckoutConsents | function | Pulls note / newsletter opt-in / survey opt-OUT / terms stamp out of client-supplied additionalData, validated and length-capped. | | consentsFromTransaction | function | Re-reads the same consents off a persisted transaction, for the order create. | | CUSTOMER_NOTE_MAX_LENGTH | const | 500 — the server-side authority for the note length. | | CheckoutConsents | type | The consent subset. |

Nothing is trusted: only an explicit true counts as consent, and the terms stamp is taken only as a complete, parseable (timestamp, version) pair — half a consent record is logged and dropped rather than written as legal evidence.

Guards

| Export | Kind | Summary | | --- | --- | --- | | assertExpectedCurrency | function | Throws CART_CURRENCY_MISMATCH when the cart's currency differs from the currency the storefront displayed to the payer. | | withPaymentLock | function | Advisory lock around order creation, so a webhook and a browser return cannot both create one. | | resolveCustomerId | function | Secure-by-default customer lookup: authed user → id, guest → null unless allowEmailLookup is passed. | | resolvePaymentMode | function | Resolves 'mock' \| 'live' from config + env, refusing mock in production unless ALLOW_MOCK_PAYMENTS=1. | | isMockSignatureValue | function | The signature === 'mock' check at verification call sites. | | AssertExpectedCurrencyOptions, ResolveCustomerIdOptions, PaymentMode, MockModeInput | types | |

The ecommerce plugin resolves the charge currency from cart.currency, so a stale cart created under another storefront can silently charge in one currency while the payer saw prices in another. Fail loudly; the storefront reprices and the payer retries.

resolveCustomerId returns null for guests on purpose — without it, a guest checkout using someone else's email silently links the new order to that person's account.

Payload plumbing

| Export | Kind | Summary | | --- | --- | --- | | extractRelationId | function | A relationship's raw id, whether it came back populated or as a bare id. undefined — never null — when absent, so it drops out of an optional-property spread. | | extractRelation | function | The populated document, or null when the relation is a bare id. | | asTransactionDoc | function | Runtime-guarded narrowing of an opaque Payload doc (TRANSACTION_DOC_INVALID). | | asTransactionCollection | function | Narrows a runtime slug string to the literal CollectionSlug Payload's calls demand. Compile-time only, zero runtime effect. | | asOrderId | function | The order relation as a number, throwing ORDER_RELATION_INVALID rather than typing a string id as a number. |

Caching & logging

| Export | Kind | Summary | | --- | --- | --- | | createCache | function | In-memory TTL cache factory (get, set, delete, clear). | | DEFAULT_TTL_MS | const | 3_600_000 (1 hour). | | consoleLogger, noopLogger | const | PaymentLogger implementations for standalone clients, scripts and tests. | | TtlCache, PaymentLogger | types | |

Prefer req.payload.logger (pino) wherever req is in scope — it gives structured JSON, request correlation and secret redaction. Inject a PaymentLogger only where there is no req (stateless API clients, refund helpers).

Statuses & shared unions

| Export | Kind | Summary | | --- | --- | --- | | SETTLED_ORDER_STATUSES | const | Statuses that are settled or void — nothing in the set counts toward an outstanding balance or can be overdue. | | SettledOrderStatus, Currency, TransactionStatus | types | | | PaymentSuccessResult, PaymentInitResult | types | What confirmOrder / initiatePayment return to the client. |

Scripts

pnpm build       # tsup -> dist/ (esm + cjs + d.ts)
pnpm typecheck   # tsc --noEmit
pnpm test        # vitest run

Don'ts

  • Don't reintroduce a local PaymentError class inside an adapter. The instanceof PaymentError check MUST work across adapter boundaries.
  • Don't JSON-stringify the PaymentError message. Keep err.message a plain string; put dynamic values in details.
  • Don't store null in the TTL cache as "absent" — use delete(key) or set(key, undefined). A literal null reads as "cache hit, empty" for the whole TTL.
  • Don't read process.env.PAYMENT_MOCK_MODE directly — go through resolvePaymentMode() so the production guardrail applies.
  • Don't email-lookup customers by default (account takeover risk).
  • Don't re-derive a line price the cart wrote server-side — pass resolveItemPricing instead, or the discount is silently undone and Σ(items) ≠ grandTotal on the ERP push.

Releasing

Bump the version, update CHANGELOG.md, commit, then push a tag:

git tag v0.2.0 && git push origin v0.2.0

The release workflow builds, tests and runs npm publish --provenance --access public with the NPM_TOKEN secret.

License

MIT © blaze IT s.r.o.