Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
name: CI

on:
pull_request:
push:
branches:
- main

permissions:
contents: read

jobs:
quality:
name: Node ${{ matrix.node }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
node:
- 18
- 22
- 24

steps:
- name: Checkout
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3

- name: Setup Node.js
uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6.5.0
with:
node-version: ${{ matrix.node }}
cache: npm

- name: Install dependencies
run: npm ci --ignore-scripts --no-audit --no-fund

- name: Build
run: npm run build

- name: Test
run: npm test

- name: Inspect exact package allowlist and secret exclusions
run: node scripts/verify-package.mjs

- name: Audit production dependencies
if: matrix.node == 24
run: npm audit --omit=dev --audit-level=high
208 changes: 208 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
name: Publish npm candidate

on:
push:
tags:
- "v*"
workflow_dispatch:

concurrency:
group: npm-publish-${{ github.ref }}
cancel-in-progress: false

permissions:
contents: read

jobs:
prepare-candidate:
name: Validate and pack without OIDC
runs-on: ubuntu-latest

steps:
- name: Checkout the release commit
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
fetch-depth: 0

- name: Setup Node.js
uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6.5.0
with:
node-version: 24
package-manager-cache: false

- name: Verify tag, version, and main ancestry
shell: bash
run: |
set -euo pipefail
version="$(node -p "require('./package.json').version")"
if [[ "${GITHUB_REF_TYPE}" != "tag" || "${GITHUB_REF_NAME}" != "v${version}" ]]; then
echo "Release must run from tag v${version}." >&2
exit 1
fi
git fetch --no-tags origin main
git merge-base --is-ancestor "${GITHUB_SHA}" origin/main

- name: Install dependencies
run: npm ci --ignore-scripts --no-audit --no-fund

- name: Build
run: npm run build

- name: Test
run: npm test

- name: Inspect exact package allowlist and secret exclusions
run: node scripts/verify-package.mjs

- name: Audit production dependencies
run: npm audit --omit=dev --audit-level=high

- name: Pack the exact reviewed build
shell: bash
run: |
set -euo pipefail
rm -rf release-candidate
mkdir release-candidate
npm pack --ignore-scripts --json --pack-destination release-candidate > "${RUNNER_TEMP}/makepay-pack.json"
PACK_JSON="${RUNNER_TEMP}/makepay-pack.json" node --input-type=module <<'NODE'
import { createHash } from "node:crypto"
import { readFileSync, writeFileSync } from "node:fs"
import { join } from "node:path"

const [packed] = JSON.parse(readFileSync(process.env.PACK_JSON, "utf8"))
const pkg = JSON.parse(readFileSync("package.json", "utf8"))
if (!packed?.filename || packed.name !== pkg.name || packed.version !== pkg.version) {
throw new Error("npm pack identity does not match package.json")
}
const tarball = join("release-candidate", packed.filename)
const bytes = readFileSync(tarball)
writeFileSync(
join("release-candidate", "candidate.json"),
`${JSON.stringify(
{
commit: process.env.GITHUB_SHA,
filename: packed.filename,
integrity: packed.integrity,
name: packed.name,
sha1: packed.shasum,
sha256: createHash("sha256").update(bytes).digest("hex"),
size: bytes.length,
version: packed.version,
},
null,
2,
)}\n`,
)
NODE

- name: Upload immutable npm candidate
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: npm-release-candidate
if-no-files-found: error
path: release-candidate
retention-days: 1

publish-next:
name: Publish immutable candidate to next
runs-on: ubuntu-latest
needs: prepare-candidate
environment: npm-release
permissions:
actions: read
contents: read
id-token: write

steps:
- name: Setup Node.js for trusted publishing
uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6.5.0
with:
node-version: 24
package-manager-cache: false
registry-url: https://registry.npmjs.org

- name: Install trusted-publishing capable npm without lifecycle scripts
run: npm install --global npm@11.6.2 --ignore-scripts --no-audit --no-fund

- name: Download the validated candidate
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
mkdir release-candidate
gh run download "${GITHUB_RUN_ID}" \
--repo "${GITHUB_REPOSITORY}" \
--name npm-release-candidate \
--dir release-candidate

- name: Verify downloaded tarball identity and digest
shell: bash
run: |
set -euo pipefail
candidate="release-candidate/candidate.json"
test -f "${candidate}"
filename="$(CANDIDATE="${candidate}" node -p "require('./' + process.env.CANDIDATE).filename")"
tarball="release-candidate/${filename}"
test -f "${tarball}"
CANDIDATE="${candidate}" \
TARBALL="${tarball}" \
node --input-type=module <<'NODE'
import assert from "node:assert/strict"
import { createHash } from "node:crypto"
import { readFileSync } from "node:fs"
import { basename } from "node:path"

const candidate = JSON.parse(readFileSync(process.env.CANDIDATE, "utf8"))
assert.equal(candidate.filename, basename(candidate.filename))
assert.match(candidate.filename, /^[a-z0-9][a-z0-9._-]*\.tgz$/)
const tarball = readFileSync(process.env.TARBALL)
assert.equal(candidate.commit, process.env.GITHUB_SHA)
assert.equal(process.env.GITHUB_REF_TYPE, "tag")
assert.equal(process.env.GITHUB_REF_NAME, `v${candidate.version}`)
assert.equal(tarball.length, candidate.size)
assert.equal(
createHash("sha256").update(tarball).digest("hex"),
candidate.sha256,
)
assert.equal(
createHash("sha1").update(tarball).digest("hex"),
candidate.sha1,
)
assert.equal(
`sha512-${createHash("sha512").update(tarball).digest("base64")}`,
candidate.integrity,
)
NODE
tar -xOf "${tarball}" package/package.json > "${RUNNER_TEMP}/packed-package.json"
CANDIDATE="${candidate}" \
PACKED_PACKAGE="${RUNNER_TEMP}/packed-package.json" \
node --input-type=module <<'NODE'
import assert from "node:assert/strict"
import { readFileSync } from "node:fs"

const candidate = JSON.parse(readFileSync(process.env.CANDIDATE, "utf8"))
const packed = JSON.parse(readFileSync(process.env.PACKED_PACKAGE, "utf8"))
assert.equal(packed.name, candidate.name)
assert.equal(packed.version, candidate.version)
NODE

- name: Refuse to overwrite an existing version
shell: bash
run: |
set -euo pipefail
package="$(node -p "require('./release-candidate/candidate.json').name")"
version="$(node -p "require('./release-candidate/candidate.json').version")"
if npm view "${package}@${version}" version >/dev/null 2>&1; then
echo "${package}@${version} is already published and is immutable." >&2
exit 1
fi

- name: Publish candidate with npm trusted publishing
run: |
set -euo pipefail
filename="$(node -p "require('./release-candidate/candidate.json').filename")"
npm publish "release-candidate/${filename}" \
--ignore-scripts \
--provenance \
--access public \
--tag next
45 changes: 45 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Changelog

All notable changes to `@makecrypto/makepay` are documented here.

## 0.4.0 - 2026-07-23

### Added

- Asynchronous OAuth authorization providers while preserving the existing
`keyId` and `keySecret` client configuration.
- P-256 DPoP key generation, JWK thumbprints, and ES256 proof helpers for native
OAuth integrations.
- One controlled authorization refresh and retry after a `401`; token storage,
refresh locking, and atomic persistence remain the host application's
responsibility.
- Idempotency keys for payment-link mutations and grant-scoped MakePay webhook
subscription methods.
- Allowlisted Medusa payment-link correlation metadata for reliable order and
payment reconciliation.

### Changed

- Requests refuse cross-origin path escapes and use manual redirect handling so
credentials and DPoP proofs are never automatically forwarded to a redirect
target.
- API, anonymous, checkout, script, iframe, and DPoP transport URLs require
HTTPS; HTTP is limited to exact loopback hosts for local testing, and base
URLs remain origin-only.
- Embedded-checkout parent origins are normalized and validated before they are
serialized or used for browser message targeting.
- Mounted checkout events must come from both the configured MakePay origin and
that mount's iframe window, isolating sibling checkout frames.
- Webhook verification rejects non-finite or non-positive timestamp tolerances
instead of allowing them to disable freshness checks.
- The default hosted checkout and embedded checkout URLs use the canonical
`www.makepay.io` origin; the production modal loader uses the MakePay CDN.
- Published JavaScript and declarations no longer include source maps.
- Authenticated payment-link detail, update, and list response types now match
the partner-v1 envelope and retain the typed nested payment-link payload.
- Packaging always rebuilds through `prepack` before npm creates an artifact.

### Compatibility

- API-key construction and the public package root export remain compatible.
- Node.js 18 or newer is required.
Loading