Skip to content
Open
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
34 changes: 34 additions & 0 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
## Description
<!-- Provide a clear summary of the changes and the problem it solves. -->
<!-- Include any relevant context or background information. -->

**Motivation:**

**Related issue(s):** Closes #

---

## Type of Change
<!-- check the corresponding checkbox(es) with an "x" -->
- 🐛 Bug fix (non-breaking change that fixes an issue) [ ]
- ✨ New feature (non-breaking change that adds functionality) [ ]
- 💥 Breaking change (fix or feature that causes existing functionality to change and that could impact other libs) [ ]
- 🔧 Refactor (no functional changes, code improvement only) [ ]
- 📦 Dependency update [ ]
- 🔒 Security fix [ ]
- 📝 Documentation update [ ]

---

## Checklist
### Code Quality
- [ ] Code is linted and formatted
- [ ] No unnecessary commented-out code or debug logs
- [ ] No hardcoded values (use env variables or config)

### Testing
- [ ] Unit tests added / updated

### Security & Ops
- [ ] No sensitive data or secrets introduced
- [ ] Logging and error handling are appropriate
82 changes: 82 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
name: CI

on:
push:
branches-ignore:
- develop
- main
tags:
- '*'
pull_request:
branches:
- develop
types: [opened, synchronize, reopened, closed]

jobs:
# -----------------------------------------------------------------------
# 1. QUALITY — static analysis & code style on PHP 8.4 source
# Runs on: push to feature branch | PR opened/updated → develop
# -----------------------------------------------------------------------
quality:
if: github.ref_type != 'tag' && github.event.action != 'closed'
uses: payplug/template-ci/.github/workflows/php-quality.yml@main
with:
php-version: '8.4'

# -----------------------------------------------------------------------
# 2. DOWNGRADE — transpile src/ + tests/ to PHP 7.2
# Runs on: push to feature branch | PR opened/updated → develop
# -----------------------------------------------------------------------
downgrade:
if: github.ref_type != 'tag' && github.event.action != 'closed'
needs: quality
uses: payplug/template-ci/.github/workflows/php-downgrade.yml@main
with:
php-version: '8.4'
artifact-name: 'payplug-core-php72'
artifact-retention-days: 7

# -----------------------------------------------------------------------
# 3. VALIDATE — install the downgraded package under PHP 7.2
# Runs on: push to feature branch | PR opened/updated → develop
# -----------------------------------------------------------------------
validate:
if: github.ref_type != 'tag' && github.event.action != 'closed'
needs: downgrade
uses: payplug/template-ci/.github/workflows/php-validate.yml@main
with:
php-version: '7.2'
artifact-name: 'payplug-core-php72'

# -----------------------------------------------------------------------
# 4. TEST — run unit tests against the downgraded PHP 7.2 code
# Runs on: push to feature branch | PR opened/updated → develop
# -----------------------------------------------------------------------
test-php72:
if: github.ref_type != 'tag' && github.event.action != 'closed'
needs: validate
uses: payplug/template-ci/.github/workflows/php-test-php72.yml@main
with:
php-version: '7.2'
artifact-name: 'payplug-core-php72'
test-namespace: 'PayplugPluginCore\tests\'

# -----------------------------------------------------------------------
# 5. PACKAGE — zip and store the artifact on PR merged → develop
# -----------------------------------------------------------------------
package:
if: github.event_name == 'pull_request' && github.event.pull_request.merged == true
uses: payplug/template-ci/.github/workflows/php-package.yml@main
with:
php-version: '8.4'
zip-prefix: 'payplug-plugin-core-php72'

# -----------------------------------------------------------------------
# 6. RELEASE — create a GitHub Release from a tag on main
# -----------------------------------------------------------------------
release:
if: github.event_name == 'push' && github.ref_type == 'tag'
uses: payplug/template-ci/.github/workflows/php-release.yml@main
with:
php-version: '8.4'
zip-prefix: 'payplug-plugin-core-php72'
9 changes: 9 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,12 @@ node_modules

# Vendor
vendor

# PHP CS Fixer
.php-cs-fixer.php
.php-cs-fixer.cache

# PHPStan
var/
/
.claude
24 changes: 24 additions & 0 deletions .php-cs-fixer.dist.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

$finder = PhpCsFixer\Finder::create()
->in([
__DIR__ . '/src',
__DIR__ . '/tests',
])
->name('*.php');

return (new PhpCsFixer\Config())
->setRiskyAllowed(true)
->setRules([
'@PSR12' => true,
'@PHP84Migration' => true,
'array_syntax' => ['syntax' => 'short'],
'no_unused_imports' => true,
'ordered_imports' => ['sort_algorithm' => 'alpha'],
'single_quote' => true,
'trailing_comma_in_multiline' => true,
'declare_strict_types' => true,
'void_return' => true,
'native_function_invocation' => ['include' => ['@compiler_optimized'], 'scope' => 'namespaced'],
])
->setFinder($finder);
24 changes: 24 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
FROM php:8.4-cli

# Install system dependencies
RUN apt-get update && apt-get install -y \
git \
unzip \
curl \
&& rm -rf /var/lib/apt/lists/*

# Install additional extensions via pecl
RUN pecl install xdebug \
&& docker-php-ext-enable xdebug

# Configure Xdebug — mode is controlled at runtime via XDEBUG_MODE env var
RUN echo "xdebug.client_host=host.docker.internal" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini \
&& echo "xdebug.client_port=9003" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini \
&& echo "xdebug.start_with_request=trigger" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini

# Install Composer
COPY --from=composer:2 /usr/bin/composer /usr/bin/composer

WORKDIR /app

CMD ["php", "-a"]
60 changes: 60 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
.PHONY: build up down shell install update test stan lint fix debug release

DC = docker compose
PHP = $(DC) run --rm php
PHP_DEBUG = XDEBUG_MODE=debug $(DC) run --rm php

## Docker
build:
$(DC) build

up:
$(DC) up -d

down:
$(DC) down

shell:
$(PHP) bash

## Composer
comp-install:
$(PHP) composer install

update:
$(PHP) composer update

## Quality
test:
$(PHP) vendor/bin/phpunit

stan:
$(PHP) vendor/bin/phpstan analyse

lint:
$(PHP) vendor/bin/php-cs-fixer fix --diff --dry-run

fix:
$(PHP) vendor/bin/php-cs-fixer fix

rector-dry:
$(PHP) vendor/bin/rector process --dry-run

## Release (downgrade src/ to PHP 7.2 into release/)
release:
rm -rf payplug-core && cp -r src payplug-core && cp -r tests payplug-core
$(PHP) vendor/bin/rector process

## Debug (Xdebug step-debug enabled)
debug:
$(PHP_DEBUG) bash

## CI (runs all checks)
ci: stan lint test

install: build update comp-install

audit:
$(PHP) composer audit

security: audit
73 changes: 73 additions & 0 deletions Security.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# Security Report

## ⚠️ <ins>_This report is internal only_</ins> ⚠️

### Critical
1. API Bearer Token Handling — `src/Utilities/Services/Api.php`
The secret API key is stored as a plain string property, passed through getter methods, and could leak in stack traces if exceptions occur.

Recommendation: Never store tokens as plain strings. Obfuscate in any debug output (show last 4 chars only). Clear from memory when no longer needed.

### High
2. Unvalidated URLs Passed to Payment API — src/Gateways/AbstractPaymentGateway.php:28-39
return_url, cancel_url, and notification_url from the DTO are forwarded to the Payplug API without any validation. This opens the door to open redirect and phishing attacks.
``` PHP
'return_url' => $urls['return'], // no validation
'cancel_url' => $urls['cancel'],
```
Recommendation: Validate with ``filter_var($url, FILTER_VALIDATE_URL)``, enforce HTTPS, and optionally restrict to your domain.

3. Empty Whitelist Arrays — `src/Utilities/Traits/DependenciesLoader.php:11-15`
`$allowed_services` and `$allowed_gateways` are initialized empty and never populated, so in_array() checks always evaluate against an empty array — the whitelist mechanism is effectively disabled.

Recommendation: Populate these arrays with the actual allowed values at initialization.

4. Dynamic Class Instantiation — `src/Gateways/PaymentGateway.php:17-20`
The payment method name (user input) is used directly to construct a class name, with no whitelist enforced.
``` PHP
$class = '\PayplugPluginCore\Gateways\Payment\\'
. str_replace('_', '', ucwords($payment_method_name, '_'))
. 'PaymentGateway';
```
Recommendation: Validate against an explicit whitelist (e.g., ['standard', 'apple_pay', 'google_pay']) before building the class name.

5. Exception Message Information Disclosure — src/Utilities/Services/Api.php:82
Raw exception messages are re-thrown to callers, potentially leaking internal configuration details or API information.

Recommendation: Log full errors server-side, expose only generic messages to callers.

### Medium
6. Unvalidated Array Key Access — `src/Gateways/AbstractPaymentGateway.php:33-39`
Direct array access (`$customer['billing']`, `$urls['return']`, etc.) without checking key existence. Will cause PHP warnings/errors on incomplete data.

Recommendation: Use `$array['key'] ?? null` or `array_key_exists()`.

7. Missing Input Validation on Payment Method — `src/Actions/PaymentAction.php:25-27`
Only a null check is performed. No format, length, or character validation.

`// todo: add a validator to check if the given paymentDTO is usable ← still a TODO`

Recommendation: Implement the noted validator, enforce an allowed-values whitelist.

8. Silent Type Coercion in DTO Hydration — `src/Models/Entities/PaymentInputDTO.php:64-71`
Invalid values are silently cast (e.g., `(int) "abc" → 0`) instead of being rejected.

Recommendation: Validate before casting; return meaningful errors for type mismatches.

Low / Informational

| Issue | File |
|-------|---------------------------------------------------------------------------------------------------------------|
| 9 | Internal parameter names exposed in exception messages src/Gateways/Payment/StandardPaymentGateway.php:40 |
| 10 | Generic \Exception used everywhere (hard to distinguish errors) src/Utilities/Exceptions/PayplugException.php |
| 11 | Xdebug included in Docker build (should be dev-only) Dockerfile |


### Priority Action Plan

- Immediate: Add URL validation for `return_url / cancel_url / notification_url`
- Immediate: Populate and enforce service/gateway whitelists
- Short term: Add payment method whitelist validation
- Short term: Implement the `TODO` validator in `PaymentAction`
- Short term: Replace raw exception propagation with a safe error translation layer
- Medium term: Custom exception hierarchy + secure token handling
33 changes: 33 additions & 0 deletions captainhook.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"commit-msg": {
"enabled": true,
"actions": [
{
"action": "\\CaptainHook\\App\\Hook\\Message\\Action\\Regex",
"options": {
"regex": "/^(PRE|MAG|SYL|SMP)-\\d+: .+/"
}
}
]
},
"pre-commit": {
"actions": [
{
"action": "\\CaptainHook\\App\\Hook\\Branch\\Action\\EnsureNaming",
"options": {
"regex": "/^(feature|fix|hotfix|refactor|release)\\/(PRE|MAG|SYL|SMP)-\\d+/"
}
},
{
"action": "vendor/bin/phpstan analyse --configuration=phpstan.neon",
"options": {}
},
{
"action": "vendor/bin/php-cs-fixer fix --config=.php-cs-fixer.php --diff",
"options": {}
}
],
"enabled": true
}
}

34 changes: 32 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,42 @@
}
],
"require": {
"php": ">=7.2",
"php": ">=8.4",
"captainhook/captainhook": "^5.28",
"payplug/payplug-php": "^4.0"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^3.0",
"phpstan/phpstan": "^2.0",
"phpunit/phpunit": "^11.0",
"rector/rector": "^2.0"
},
"autoload": {
"psr-4": {
"Payplug\\PluginCore\\": "src/"
"PayplugPluginCore\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"PayplugPluginCore\\tests\\": "tests/"
}
},
"scripts": {
"post-install-cmd": "vendor/bin/captainhook install --force --skip-existing",
"post-update-cmd": "vendor/bin/captainhook install --force --skip-existing",
"test": "vendor/bin/phpunit",
"stan": "vendor/bin/phpstan analyse --configuration=phpstan.neon",
"lint": "vendor/bin/php-cs-fixer fix --config=.php-cs-fixer.dist.php --dry-run --diff",
"fix": "vendor/bin/php-cs-fixer fix --config=.php-cs-fixer.dist.php",
"rector": "vendor/bin/rector process",
"rector-dry": "vendor/bin/rector process --dry-run",
"release": [
"rm -rf payplug-core && cp -r src payplug-core && cp -r tests payplug-core",
"vendor/bin/rector process --config rector.php",
"php scripts/generate-release-composer.php"
]
},
"config": {
"sort-packages": true
}
}
Loading
Loading