Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/own-pay/OwnPay-Documentation/llms.txt

Use this file to discover all available pages before exploring further.

OwnPay is an open-source project built in the open, for the community. Whether you’re fixing a typo in the docs, adding a payment gateway for a regional market, writing tests for an edge case, or translating the admin panel to a new language — every contribution moves the project forward. This guide explains how to get involved effectively.
This project is governed by a Code of Conduct. Report unacceptable behavior to ping@ownpay.org.

Ways to Contribute

Report Bugs

Open a clear, reproducible issue with the exact error, steps to reproduce, and your PHP version. Payment bugs should include relevant transaction IDs and log excerpts (sanitized).

Suggest Features

Start a GitHub Discussion or feature request issue. Describe the use case, not just the solution — this helps maintainers design the right API.

Build a Gateway or Plugin

Add a payment gateway adapter or addon for a provider or region that isn’t covered yet. Gateway plugins are the highest-impact contribution area.

Translate

Add or improve a language catalog. See TRANSLATIONS.md for the dot-notation JSON format.

Improve Documentation

Fix typos, expand examples, clarify guides, or add missing edge cases. Documentation PRs get reviewed quickly.

Write Code

Fix bugs, add tests, or refactor with care. Keep PRs focused on one logical change so reviews are fast.
Not sure where to start? Look for issues labeled good first issue or help wanted on GitHub.

Prerequisites

ToolVersionNotes
PHP8.3+With bcmath, json, mbstring, openssl, pdo_mysql, curl
Composer2.xPHP dependency manager
MySQL or MariaDBMySQL 8.0+ / MariaDB 10.4+Database
GitAny recent versionFor branching and commits
Node.js20+ (optional)Only needed for JS/CSS linting

Getting Set Up

1

Fork and Clone

Fork the repository on GitHub, then clone your fork:
git clone https://github.com/YOUR-USERNAME/OwnPay.git
cd OwnPay
2

Follow Local Setup

See the Local Setup guide to get a running instance in about 2 minutes. The quick path uses PHP’s built-in web server.
3

Create a Feature Branch

Branch from dev — all PRs target the integration branch, not main:
git checkout dev
git checkout -b feat/short-description

Development Workflow

1

Make Focused Changes

Keep each branch to one logical change. If you’re adding a gateway and fixing a bug, make two separate PRs.
2

Add or Update Tests

Add PHPUnit tests for any behavior you change or add. Untested code is harder to review and more likely to regress.
3

Run the Full Check Suite

This is what CI runs. All three must pass before requesting review:
composer test       # PHPUnit — all tests must pass
composer analyse    # PHPStan — must stay clean at level 9
composer lint       # Twig + JS + CSS linting
4

Commit with DCO Sign-Off

Use the -s flag to append a Developer Certificate of Origin trailer:
git commit -s -m "feat(gateway): add Acme Pay adapter"
This appends Signed-off-by: Your Name <your.email@example.com> to the commit.
5

Open a Pull Request

Push to your fork and open a PR against the dev branch. Fill out the description: what changed, why, and how to test it. Link the related issue (e.g., Closes #123).

Coding Standards

OwnPay maintains a high quality bar. Match the existing code style and these non-negotiable rules:
Every PHP file starts with declare(strict_types=1); as its first statement (no BOM). PHPStan level 9 must stay green. Do not suppress errors with @phpstan-ignore or baselines — fix the root cause.
All financial arithmetic uses PHP’s bcmath extension with string operands. Never use floats for money calculations — floating-point representation errors will corrupt ledger entries.
// ✅ Correct
$fee = bcmul($amount, '0.025', 2);

// ❌ Wrong — floating-point precision loss
$fee = $amount * 0.025;
Never run a scoped query without forTenant($merchantId) or a deliberate forAllTenants(). Missing the tenant scope causes data bleed between brands.
// ✅ Correct — scoped to the active brand
$txns = $this->transactionRepo->forTenant($brandId)->paginate();

// ✅ Correct — deliberate owner-level view
$all = $this->transactionRepo->forAllTenants()->paginate();

// ❌ Wrong — missing scope; returns data from all brands
$txns = $this->transactionRepo->paginate();
Always build checkout, callback, and redirect URLs through DomainUrlService::buildCheckoutUrl() and DomainUrlService::buildCallbackUrl(). Hardcoding a host breaks white-label custom domains.
No string-interpolated SQL under any circumstances. Use the Database wrapper with parameter binding for every query.
Never use |raw on untrusted data. Twig’s autoescaping must remain enabled. Write trusted static strings with |raw only when absolutely necessary and clearly documented.
No new heavyweight dependencies without discussion. Prefer the existing first-party utilities. Add a dependency only when there is a clear, well-scoped reason.

Contributing a Payment Gateway

Gateways are the most impactful contributions because each one expands OwnPay’s reach to new markets.
1

Create the Plugin Directory

modules/gateways/<slug>/
├── manifest.json           # plugin metadata and capabilities
├── src/
│   └── <Slug>Adapter.php   # implements GatewayAdapterInterface
└── icon.svg                # displayed in admin and checkout
2

Implement the Adapter

Implement OwnPay\Gateway\GatewayAdapterInterface. Use the GatewayDefaults trait to provide no-op defaults so you only implement what the gateway actually supports:
<?php
declare(strict_types=1);

namespace OwnPay\Modules\Gateways\AcmePay;

use OwnPay\Gateway\GatewayAdapterInterface;
use OwnPay\Gateway\GatewayDefaults;

class AcmePayAdapter implements GatewayAdapterInterface
{
    use GatewayDefaults;

    public function slug(): string { return 'acme-pay'; }

    public function supportedCurrencies(): array
    {
        return ['USD', 'EUR']; // return [] for any currency
    }

    public function initiate(array $params, array $credentials): array
    {
        // Call Acme Pay API, return ['redirect_url' => '...']
    }

    public function verify(array $callbackData, array $credentials): array
    {
        // Verify callback, return ['status' => 'completed', 'gateway_trx_id' => '...']
    }
}
3

Respect Sandbox Rules

Plugins are scanned before loading. Do not use exec, shell_exec, passthru, eval, raw PDO, or reflection inside plugin code.
4

Test the Full Flow

Test initiate → redirect → callback → verify → refund locally using the gateway’s sandbox credentials before opening a PR.

Contributing Translations

OwnPay supports full i18n for both the admin panel and customer checkout. To add or update a language:
  1. Create a dot-notation JSON catalog (e.g., config/languages/de.json).
  2. Keep :placeholder syntax intact (Thank you, :name not Thank you, {name}).
  3. Follow the full instructions in TRANSLATIONS.md.

Pull Request Guidelines

  • One logical change per PR. Split large work into reviewable pieces.
  • Fill out the description — what changed, why, and how to test it.
  • Link the issue — use Closes #123 to auto-close on merge.
  • Update docs and tests alongside code.
  • Be responsive — maintainers review with care because this is payment software.
  • All CI checks must be green before requesting review.

Licensing of Contributions

OwnPay is licensed under GNU AGPL-3.0. By submitting a contribution, you agree that your work is licensed under the same AGPL-3.0 license (inbound = outbound) and you certify its origin via your DCO sign-off. No separate CLA is required. Questions about contributing? Reach out at ping@ownpay.org or open a GitHub Discussion.

Build docs developers (and LLMs) love