Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
e1c27eb
ci: publish tagged releases to GitHub and Packagist
listopad Jul 25, 2026
fb84f9e
fix(api): bind fluent setter params to the calls that accept them
listopad Jul 25, 2026
982bbee
fix: validate IP allowlist entries and the facade domain
listopad Jul 25, 2026
b43e594
chore(qa): restore PHPMD thresholds and raise PHPStan to level 8
listopad Jul 25, 2026
c5b9c68
ci: add PHP 8.5 to the matrix and verify the release tag matches VERSION
listopad Jul 25, 2026
be07b8b
docs: document the 3.1.0 behavior changes and bump VERSION
listopad Jul 25, 2026
e31b92d
chore(release): consolidate the unreleased 3.1.0 batch into 4.0.0
listopad Jul 25, 2026
3b20aea
feat(http)!: replace the string|false transport contract with a Respo…
listopad Jul 25, 2026
28a4ea7
feat(http): configurable timeouts and connect-phase retries
listopad Jul 25, 2026
415f930
feat(webhook)!: reject replayed webhooks outside the tolerance window
listopad Jul 25, 2026
936a40f
feat(telemetry): CMS/framework/module slots and an opt-out
listopad Jul 25, 2026
7df1439
docs: 4.0.0 changelog and migration guide
listopad Jul 25, 2026
8ddf8b7
test(http): cover the default transport stack
listopad Jul 25, 2026
50cd5de
fix(http)!: never retry a failure the transport could not classify
listopad Jul 25, 2026
f27326e
docs: warn against logging exception traces that carry the secret key
listopad Jul 25, 2026
a0bbdcd
chore(qa): declare ext-ctype and put examples/ under static analysis
listopad Jul 28, 2026
a7ad03d
feat(telemetry)!: rename the client header and send slots as objects
listopad Jul 28, 2026
3bf6607
fix(telemetry): never throw, never emit a malformed client header
listopad Jul 28, 2026
b9a920c
docs: 4.0.0 telemetry wire format
listopad Jul 28, 2026
5ee6073
docs: say what ignoring a blank telemetry slot actually does
listopad Jul 28, 2026
2648433
feat(model)!: remove the deprecated PaymentObject values
listopad Jul 29, 2026
5729b8a
docs: record the PaymentObject removal in 4.0.0
listopad Jul 29, 2026
b37c090
feat(telemetry)!: replace the CMS/framework slots with a stack
listopad Jul 29, 2026
2e2f0f4
docs: module and stack telemetry
listopad Jul 29, 2026
c7ba847
docs: correct the documented Unitpay-Client payload for 4.0.0
listopad Jul 29, 2026
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
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
/.php-cs-fixer.dist.php export-ignore
/examples export-ignore
/phpmd.xml export-ignore
/phpstan-examples.neon export-ignore
/phpstan.neon export-ignore
/phpunit.xml export-ignore
/tests export-ignore
14 changes: 9 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ name: CI

on:
push:
branches: [ master ]
# Maintenance branches follow the 2.x naming Composer understands as 2.x-dev.
branches: [ master, '[0-9]+.x' ]
pull_request:
workflow_dispatch:

Expand All @@ -20,15 +21,15 @@ jobs:
strategy:
fail-fast: false
matrix:
php: ['7.4', '8.0', '8.1', '8.2', '8.3', '8.4']
php: ['7.4', '8.0', '8.1', '8.2', '8.3', '8.4', '8.5']
steps:
- uses: actions/checkout@v4

- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
extensions: json, curl
extensions: json, ctype, curl
coverage: none

- name: Install dependencies
Expand All @@ -53,7 +54,7 @@ jobs:
uses: shivammathur/setup-php@v2
with:
php-version: '8.3'
extensions: json, curl
extensions: json, ctype, curl
coverage: none

- name: Install dependencies
Expand All @@ -68,9 +69,12 @@ jobs:
- name: Static analysis (PHPStan)
run: composer stan

# examples/ sits outside phpstan.neon, and composer lint only syntax-checks it.
- name: Static analysis (examples)
run: composer stan-examples

- name: Mess detection (PHPMD)
run: composer md

- name: Security audit (composer)
run: composer audit
continue-on-error: true
127 changes: 127 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
name: Release

# Tag push -> GitHub release with the CHANGELOG section + a Packagist re-crawl.
# The Packagist GitHub hook already publishes new tags on its own; this ping is
# the fallback for when it lags or breaks (e.g. after a repository rename).
# Run via workflow_dispatch to ping Packagist without cutting a release.

on:
push:
tags:
# Historic tags use both spellings: v1.1.2 and 3.0.0.
- 'v[0-9]+.[0-9]+.[0-9]+*'
- '[0-9]+.[0-9]+.[0-9]+*'
workflow_dispatch:

permissions:
contents: write

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

jobs:
release:
name: GitHub release & Packagist
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
# Full history: the release step compares this tag against every other
# one to decide whether it is really the newest version.
fetch-depth: 0

- name: Set up PHP
if: startsWith(github.ref, 'refs/tags/')
uses: shivammathur/setup-php@v2
with:
php-version: '8.3'
coverage: none

- name: Verify Unitpay::VERSION matches the tag
if: startsWith(github.ref, 'refs/tags/')
run: |
set -euo pipefail
declared=$(php -r 'require "src/Unitpay.php"; echo Unitpay\Unitpay::VERSION;')
expected="${GITHUB_REF_NAME#v}"
if [ "$declared" != "$expected" ]; then
echo "::error::Tag ${GITHUB_REF_NAME} declares version '${expected}' but Unitpay::VERSION is '${declared}'. Bump the constant and retag."
exit 1
fi
echo "Unitpay::VERSION=${declared} matches tag ${GITHUB_REF_NAME}"

- name: Extract the CHANGELOG section for this tag
if: startsWith(github.ref, 'refs/tags/')
run: |
set -euo pipefail
# CHANGELOG headings are always "### v3.0.0 — 2026-07-25"; tags are not
# always prefixed, so normalise 3.0.0 and v3.0.0 to the same lookup.
heading="### v${GITHUB_REF_NAME#v} "
awk -v h="$heading" 'index($0, h) == 1 { found = 1; next } found && /^### / { exit } found' \
CHANGELOG.md > release-notes.md
if [ ! -s release-notes.md ]; then
echo "::warning::No CHANGELOG section matching '${heading}' — falling back to generated notes"
fi

- name: Create the GitHub release
if: startsWith(github.ref, 'refs/tags/')
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
if gh release view "$GITHUB_REF_NAME" >/dev/null 2>&1; then
echo "Release $GITHUB_REF_NAME already exists, leaving it untouched"
exit 0
fi
# GitHub picks "Latest" by tag date, so a 2.x backport cut after 3.0.0
# would steal the badge. Strip the optional v prefix before comparing,
# otherwise git sorts v1.1.2 above 3.0.0.
highest=$(git tag | sed 's/^v//' | sort -V | tail -1)
if [ "${GITHUB_REF_NAME#v}" = "$highest" ]; then latest=true; else latest=false; fi
echo "Highest tag in the repository: $highest (this release --latest=$latest)"
if [ -s release-notes.md ]; then
gh release create "$GITHUB_REF_NAME" --title "$GITHUB_REF_NAME" --latest="$latest" --notes-file release-notes.md
else
gh release create "$GITHUB_REF_NAME" --title "$GITHUB_REF_NAME" --latest="$latest" --generate-notes
fi

- name: Ask Packagist to re-crawl the repository
env:
PACKAGIST_USERNAME: ${{ vars.PACKAGIST_USERNAME }}
PACKAGIST_TOKEN: ${{ secrets.PACKAGIST_TOKEN }}
run: |
set -euo pipefail
if [ -z "${PACKAGIST_USERNAME:-}" ] || [ -z "${PACKAGIST_TOKEN:-}" ]; then
echo "PACKAGIST_USERNAME (variable) or PACKAGIST_TOKEN (secret) is missing" >&2
exit 1
fi
# The safe token is enough here: update-package is the one write endpoint
# it may call. Bearer auth keeps the token out of the request URL.
response=$(curl -sS --fail-with-body \
--retry 3 --retry-delay 5 --retry-all-errors \
-X POST \
-H 'Content-Type: application/json' \
-H "Authorization: Bearer ${PACKAGIST_USERNAME}:${PACKAGIST_TOKEN}" \
-A "unitpay-php-release (+https://github.com/${GITHUB_REPOSITORY})" \
-d "{\"repository\":\"https://github.com/${GITHUB_REPOSITORY}\"}" \
https://packagist.org/api/update-package)
echo "$response"
echo "$response" | jq -e '.status == "success"' >/dev/null

- name: Wait for the version to appear on Packagist
if: startsWith(github.ref, 'refs/tags/')
run: |
set -euo pipefail
package=$(jq -r .name composer.json)
version="${GITHUB_REF_NAME#v}"
# Packagist queues the crawl, so this is a poll, not an immediate check.
for _ in $(seq 1 12); do
if curl -sS "https://repo.packagist.org/p2/${package}.json" \
| jq -e --arg p "$package" --arg v "$version" \
'[.packages[$p][].version] | any(. == $v or . == "v" + $v)' >/dev/null; then
echo "$package $version is live on Packagist"
exit 0
fi
sleep 10
done
echo "::warning::$package $version is not on Packagist yet — check https://packagist.org/packages/$package"
Loading
Loading