One-time payments products

Checking for purchased Products

Owning products is trickier to manage as Stripe does not directly store these for us since we use payment intents.

The solution is to store owned products within the member account. Using the fulfilment hook after a successful purchase, the Klub plugin will automatically write the price_id into the field stripe['payments'] as an array to store that the user owns this product.

If you want a certain page only accessible to users who purchased a certain product, you first have to link the product (more specifically, the price ID of the product) to the page via the blueprint. Then, go into the panel, navigate to the page, select the product as a gate, and save the page. In your frontend code, you can check if the user has purchased the product using the Klub helpers.

site/blueprints/pages/course.yml
fields:
  gates: fields/klub/gates/products
site/templates/course.php
<?php

if ($page->isNotAllowed()) {
   // user has no access
   go($page->parent()->url();
}

if ($page->isAllowed()) {
   // user has purchased the product defined in panel
}

Listing Products

You can use the klub()->products('one_time') to retrieve all products in stripe and $user->owns($price_id) to check if a user owns a single product by price id.

<?php

$products = klub()->products('one_time');

foreach ($products as $product) {
    $owned = kirby()->user()?->owns($product['default_price']['id']); ?>
    <div>
        <p>
            <?= $product['name'] ?><br>
            <?= $product['default_price']['nickname'] ?><br>
            <?= $product['price'] ?>
        </p>
        <?php if ($owned) { ?>
            <button disabled>Owned</button>
        <?php } else { ?>
            <!-- form with POST to prevent browser URL preloading -->
            <form method="post" action="<?= site()->url() ?>/klub/payment">
                <input type="hidden" name="product" 
                       value="<?= $product['default_price']['id'] ?>">
                <input type="hidden" name="token" value="<?= csrf() ?>">
                <input type="hidden" name="redirect" value="<?= $page->url() ?>">
                <button type="submit">Buy</button>
            </form>
        <?php } ?>
    </div>
<?php } ?>

Endpoint: klub/payment

Submitting the form with the given Stripe price ID to the klub/payment-endpoint will initiate a new Stripe Checkout session.

After the purchase, it will return to the page defined by the redirect form data.

Fulfilment hook

If you want to perform additional steps after the completion of a one-time purchase you can add your logic to the klub.payment:after-hook provided by the Klub plugin.

The plugin will use the same hook when registering purchased products to the user data stripe['purchases'][].

You can find all hooks in the guide.

site/config/config.php
<?php

return [
  'hooks' => [
     'klub.payment:after' => function (User $user, array $session) {
        // your code here
     }
  ],
  // other options
];
Kirby Klub is not affiliated with the developers of Kirby CMS. We are merely standing on the shoulder of giants.
© 2025 Bruno Meilick All rights reserved.