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

@codefusion-cc/commerce-payments

v0.2.5

Published

Payment gateways for a CodeFusion shop: the gateway contract and the verdicts signed notifications become at the root, and one entry per gateway (./payu, ./przelewy24, ./tpay, each with ./<gateway>/browser for its panel definition), so a Worker bundles on

Readme

@codefusion-cc/commerce-payments

Payment gateways for a CodeFusion shop: Przelewy24, PayU and Tpay. Each gateway does three things:

  • starts a payment and returns where to send the customer,
  • refunds through its own API,
  • turns its signed notifications into a verdict.

The shop acts on the verdict. It keeps its payments table, checks the amount against the order, marks it paid, and sends mails and alerts. So the gateways never touch a database and are the same for every shop.

npm install @codefusion-cc/commerce-payments

Entry points

A shop imports only the gateways it uses, so its Worker bundles no other gateway's code (the rule for every commerce kind: ROADMAP.md, Commerce).

| Entry | Holds | | --- | --- | | @codefusion-cc/commerce-payments | The gateway contract: PaymentGateway, PaymentStart, RefundRequest, the verdicts (Notification), NOTIFICATION_MAX_BYTES, and the OAuth, refund (refundOrder, refundAnswerError, RefundRefused) and notification helpers a gateway builds on. No gateway. | | …/browser | What every gateway's definition shares, for the panel's page and the storefront: PAYMENT_GROUP, gatewayDef, isSandbox, modeLabel. | | …/payu, …/przelewy24, …/tpay | The gateway (payu, przelewy24, tpay), its signature helpers, and its definition. | | …/payu/browser, …/przelewy24/browser, …/tpay/browser | The gateway's panel definition alone (payuDef, przelewy24Def, tpayDef), without its code. | | …/testing | A fake of all three gateways' APIs and their signatures. |

// The Worker: the gateways the shop offers, each from its own entry.
import type { PaymentGateway } from '@codefusion-cc/commerce-payments'
import { payu } from '@codefusion-cc/commerce-payments/payu'
import { przelewy24 } from '@codefusion-cc/commerce-payments/przelewy24'

export const GATEWAYS: PaymentGateway[] = [przelewy24, payu]

// The panel's page and the storefront: the definitions, without the gateways' code.
import { isSandbox, PAYMENT_GROUP } from '@codefusion-cc/commerce-payments/browser'
import { payuDef } from '@codefusion-cc/commerce-payments/payu/browser'
import { przelewy24Def } from '@codefusion-cc/commerce-payments/przelewy24/browser'

Using a gateway

// The gateway plugin's context (@codefusion-cc/commerce) as it is: its config, secrets and fetch.
const { redirectUrl, providerId } = await gateway.start(ctx, { sessionId, amount, description, email, name, phone, returnUrl, notifyUrl, customerIp })

// /api/hooks/<id>/notify
const verdict = await gateway.notifications.notify(ctx, body, request.headers)
switch (verdict.kind) {
  case 'paid': // check sessionId and amount against your payment; if not paid yet: await verdict.confirm?.(), then mark it paid
  case 'failed': // the customer may pay again
  case 'refund': // a refund's outcome (Przelewy24, PayU): verdict.refundId is the ref refund() answered with; ok false: not made
  case 'warning': // tell the owner (PayU waiting for capture)
  case 'ignored':
    return gateway.ack() // what the gateway expects back
  case 'refused': // bad signature or data: answer 400 and tell the owner (verdict.field: the setting to check)
}

Refunds

try {
  const { ref } = await gateway.refund!(ctx, { payment: { sessionId, providerId, gatewayRef, amount }, amount, refundId, description, notifyUrl })
} catch (error) {
  // A ProviderError. uncertain false: the gateway surely made no refund (it refused, or the order never left), so the
  // amount may go back to what can be refunded. uncertain true (no answer, a 5xx, an answer that confirms nothing):
  // the refund may have been made. Keep the amount reserved and settle it from the gateway's panel or its refund
  // notification before ordering it again.
}

Keep ref with the refund: the refund verdict of the gateway's notification carries it as refundId. It is the refundId the shop sent, for every gateway here. Przelewy24 and PayU refuse a second order with that id; Tpay's API takes none, so a Tpay refund that came out uncertain is checked in Tpay's panel before it is ordered again.

A gateway built on this package sends its refund order through refundOrder, which tells the two apart, and words an answer that is not a success with refundAnswerError, or a refusal inside a successful answer with RefundRefused. Once the order may have been taken, any other error comes out uncertain.

Every request to a gateway gives up after GATEWAY_TIMEOUT_MS (10 s) without an answer, as a dropped connection does: starting a payment fails with an error, a refund order comes out uncertain, and a notification that needed a request (Przelewy24's verify, Tpay's certificate) fails, so the gateway sends it again.

PayU's and Tpay's OAuth tokens are kept in the Worker's memory until shortly before they run out (cachedToken in @codefusion-cc/commerce), so a checkout does not wait for a new one. The panel's check (test) always asks for a new token, and a token the gateway refuses is replaced once.

The notification body is at most NOTIFICATION_MAX_BYTES, so read it with a limit (readBody in @codefusion-cc/workers-http). Signatures use MD5 (PayU, Tpay), which only Workers' WebCrypto has, so the gateways run in Workers.

Each definition (field labels, hints, steps) is in Polish, and so are the errors the owner sees. These gateways serve Polish shops, and the text is the same for every shop.

Paying an order

The gateway talks to the provider and checks what it signed; the shop's side of a payment is in the root entry and works on the shop's D1 database (the orders of @codefusion-cc/commerce). Its tables, order_payments and order_payment_refunds, are this package's migrations/, which codefusion-commerce-migrations copies into the shop next to the commerce ones.

  • startOrderPayment starts an attempt for the order with an access token. The amount is the order's total, copied by the statement that writes the attempt, never taken from the customer. One start per order at a time (busy for a second), a pending attempt under two minutes old, of the same gateway and for the order's total as it is now, is handed out again (a double click makes one transaction), and the gateway's failure (an error, a timeout, a body that is not JSON, an address that is not https) is a gateway_failed result with the attempt marked failed. Once the gateway has the transaction, a database that cannot record it throws, and the attempt blocks a second transaction for a minute.
  • handleOrderNotification gives the request to the gateway first (signature, merchant, currency); only a verdict reaches the database. A payment is believed once: the attempt becomes paid and the order moves to paid through orderStatusStatements in one batch, only while it is awaiting_payment and its total is still the attempt's amount. A replay, a notification out of order (pending or cancelled after paid), another amount or transaction, an unknown attempt, a cancelled order: none moves an order backwards or pays it twice. The result is the response for the gateway and a PaymentEvent for the shop's mail, notes and logs (paid is the one that sends "payment received", with what came, so more than the amount is an overpayment to give back; paid_elsewhere is money for an order that cannot take it, to refund, which elsewhereNote writes on the order in the same transaction; underpaid and test_payment say with first whether this notification is the one to write a note on; refund is a refund this notification just settled, with what the shop held it for). A failing database throws, so the route answers 500 and the gateway sends the notification again. testPaysOrders false (a production shop) keeps the gateway's sandbox from paying orders; a notification the gateway marks as a test on a production configuration pays nowhere, and an attempt once held as a test stays one.
  • refundOrderPayment writes the refund before asking the gateway, in a statement that refuses more than is left of the payment. Only an answer that proves no refund was made frees the amount; any other failure leaves it pending and reserved, and the gateway's refund notification (the same webhook) settles it: made, or cancelled (also after the gateway first took it) and free again.
  • orderPaymentState is what the order page asks: the order's status, none, pending, paid, failed or test, and whether the customer may pay now (method: the order's way of paying that means this gateway).

The tables are the shop's record of money: an order that has a payment cannot be deleted, nor a payment that has a refund (ON DELETE RESTRICT).

The shop supplies its routes (the notification route must not require the page's origin), the gateway's keys, the return page, and what to say or send on each event.

A new gateway

A gateway is an entry of this package: src/<gateway>/index.ts (the PaymentGateway, built on the root's contract) and src/<gateway>/browser.ts (its definition, made with gatewayDef), both listed in exports. It becomes a package of its own (@codefusion-cc/commerce-payments-<gateway>, depending on this one for the contract) only when it needs a heavy dependency, is maintained by someone else, or needs its own release pace.

Testing

@codefusion-cc/commerce-payments/testing fakes the gateways' APIs, answering as their documentation does. It stays one entry for every gateway: tests never reach a Worker's bundle, and one fake lets a shop's tests send every gateway's requests to one place.

  • fakeGatewayApi() gives a fetch and the requests it received.
  • startFakeGatewayServer() runs an HTTP server, for a Worker under test that sends its calls to <url>/<host><path>.

It also signs notifications the way each gateway does: p24Sign, payuSignature, tpayMd5 and tpayJws. Tpay's notification certificate is a test certificate made once for this purpose, and only the fake serves it.

Tokens are kept between test cases too: call forgetToken() (@codefusion-cc/commerce) in afterEach when a case counts the gateway's requests.