PHPackages                             goldnead/statamic-payments - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. goldnead/statamic-payments

Active

goldnead/statamic-payments
==========================

00PHPCI passing

Pushed todayCompare

[ Source](https://github.com/goldnead/statamic-payments)[ Packagist](https://packagist.org/packages/goldnead/statamic-payments)[ RSS](/packages/goldnead-statamic-payments/feed)WikiDiscussions main Synced today

READMEChangelogDependenciesVersionsUsed By (0)

Statamic Payments
=================

[](#statamic-payments)

> Take payments in Statamic with Mollie — and never believe the caller.

Requirements
------------

[](#requirements)

Statamic 6 · PHP 8.2+ · a database · a Mollie account.

Mollie rather than Stripe because this is built for a German and European audience: SEPA direct debit, Sofort, iDEAL and Bancontact are what people here actually reach for, and there is no monthly floor — which matters on a client site that takes four payments a month.

Installation
------------

[](#installation)

```
composer require goldnead/statamic-payments
php artisan migrate
php please vendor:publish --tag=statamic-payments-config
```

Set `MOLLIE_KEY` in your environment, then list what you sell.

Usage
-----

[](#usage)

### Products

[](#products)

```
// config/statamic-payments.php
'products' => [
    'noten-paket' => [
        'name' => 'Notenpaket „Frühling"',
        'amount_cent' => 1900,
    ],
],
```

`amount_cent` is an integer in minor units. Not a float: a float is how a cent goes missing every thousand orders.

### Starting a payment

[](#starting-a-payment)

```
use Goldnead\StatamicPayments\Support\Checkout;

$checkout = app(Checkout::class)->start('noten-paket', [
    'email' => $request->input('email'),
    'name' => $request->input('name'),
]);

abort_if($checkout === null, 404);          // no such product

$checkout->payment;                          // the row
return redirect()->away($checkout->checkoutUrl);
```

**The amount is looked up, never accepted.** Anything the buyer sends is a label on the order, not a term of it — a checkout that took a posted price would sell a €19 thing for a cent.

### Reacting

[](#reacting)

```
use Goldnead\StatamicPayments\Events\PaymentPaid;

Event::listen(PaymentPaid::class, function (PaymentPaid $event) {
    $event->payment->product;   // what was bought
    $event->payment->email;     // by whom
    $event->payment->amount_cent;
});
```

**Dispatched once per payment**, guaranteed by a conditional `UPDATE` rather than by a check, and the claim stands in the database *before* any listener runs. A listener may therefore grant access without carrying its own idempotency for the ordinary case — redelivery, a duplicated request, two deliveries landing together. `PaymentFailed` works the same way, on its own column.

**The one case a listener must still think about: its own exception.** If a listener throws, the claim is released, the exception reaches the caller, the webhook answers non-2xx and the provider delivers again — so that listener, and every other listener on the event, runs a second time. The alternative would be keeping the claim, and the failure mode of that is a customer who paid, got nothing, and no retry ever comes, silently, because the row says fulfilled. Given the choice, this package repeats rather than loses. Make irreversible work in a listener idempotent, or queue it.

What a payment *means* belongs to your site. There is one optional exception, below.

### Entitlements (optional)

[](#entitlements-optional)

With [`goldnead/statamic-entitlements`](https://github.com/goldnead/statamic-entitlements) installed:

```
// config/statamic-payments.php
'entitlements' => ['enabled' => true],

'products' => [
    'noten-paket' => ['name' => '…', 'amount_cent' => 1900, 'grants' => 'noten-fruehling'],
],
```

Off unless all three are true: the sibling installed, the flag on, and the product carrying `grants`. A failure in the sibling is logged and swallowed — the money was taken and the row says so; an entitlements outage must not send the whole webhook round again.

Configuration
-------------

[](#configuration)

KeyDefaultWhat happens when it is wrong`products`**none**Nothing can be bought. An addon that shipped prices would be wrong about every site.`currency``EUR`Must match what your Mollie account accepts.`return_url``/danke`Where the buyer lands after paying. **Not** where fulfilment happens.`rate_limit``60`Per minute, per IP, on the webhook.`entitlements.enabled``false`On, plus a `grants` key on a product, grants that entitlement to the buyer.Security
--------

[](#security)

**The webhook has no signature, and does not need one.** Mollie posts a payment id; this package reads nothing else from the request. The status is fetched from Mollie by that id, so the worst a forged call can do is make the server ask about a payment that is not paid.

That is a stronger position than a shared secret, because it does not depend on the secret staying secret. It is also the only design that survives someone replaying a genuine delivery.

Three consequences worth knowing:

- **Fulfilment runs once.** The claim is staked with a conditional `UPDATE`, before any listener runs, so two simultaneous deliveries cannot both win it. A read-then-write guard loses that race, and a redelivery arriving twice within milliseconds is exactly that race. The exception is a listener throwing, above.
- **An id this site never issued creates nothing** — even if it really is paid at Mollie. An id we did not issue is not evidence of an order here.
- **The endpoint answers identically for known and unknown ids**, and asks Mollie in both cases. Asking only about known ids would answer, in the response time, the question the flat `200`refuses: which payment ids this site has seen.
- **A payment nobody can match is logged, loudly.** The one way a real site loses money is a checkout dying between Mollie creating the payment and the id reaching the database. The buyer pays, the webhook matches nothing. This package sends its own row id along as metadata and recovers the payment from it; if even that fails, `Log::warning` says so instead of a silent `200`.

**The return URL proves nothing.** A buyer who closes the tab still paid; a buyer who reaches that page has not necessarily paid. Only the webhook decides.

What it stores
--------------

[](#what-it-stores)

Provider and its id, product handle, amount and currency, status, buyer name and address, and the timestamps for paid, fulfilled and failure-announced. A row is `initiated` until Mollie has acknowledged it — deliberately not `open`, so a checkout that died mid-flight is not counted as an order in flight. An address the buyer typed is never overwritten by the one on their Mollie account — those are often different people.

Multi-site
----------

[](#multi-site)

Payments are not site-scoped. A payment is a transaction, not content.

Support
-------

[](#support)

Only the latest version is supported.

Changelog · License
-------------------

[](#changelog--license)

[CHANGELOG.md](CHANGELOG.md) · [LICENSE.md](LICENSE.md)

###  Health Score

20

—

LowBetter than 12% of packages

Maintenance65

Regular maintenance activity

Popularity0

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity8

Early-stage or recently created project

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

### Community

Maintainers

![](https://www.gravatar.com/avatar/85572d690277234a86834808cab169c4900922b4855fe9028426f8350dd74e97?d=identicon)[goldnead](/maintainers/goldnead)

---

Top Contributors

[![goldnead](https://avatars.githubusercontent.com/u/1313348?v=4)](https://github.com/goldnead "goldnead (1 commits)")

### Embed Badge

![Health badge](/badges/goldnead-statamic-payments/health.svg)

```
[![Health](https://phpackages.com/badges/goldnead-statamic-payments/health.svg)](https://phpackages.com/packages/goldnead-statamic-payments)
```

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
