Skip to content

Payment (yoyaku_payment)

yoyaku_payment adds deposit configuration and payment-gated confirmation on top of the engine, with pluggable payment gateways. It is optional - the core engine never depends on it.

What it adds

  • Config fields (attached to the core entities, kept out of the engine): auto_confirm_on_payment on the Resource; deposit_required and deposit_amount on the Category.
  • BookingPayment entity - records a payment for a booking: gateway, amount, currency, state (pending|paid|failed|refunded), reference. The amount is snapshotted, so history stays correct when deposit config changes later.
  • PaymentManager (yoyaku_payment.payment_manager) - gateway-agnostic; owns the booking-engine contract.
  • PaymentGateway plugin type - one per payment solution.

The gateway-agnostic contract

PaymentManager never talks to a provider. Gateways report outcomes back to it, and it drives the booking lifecycle:

flowchart LR
    G[Gateway plugin] -- settle() --> PM[PaymentManager]
    G -- fail() --> PM
    PM -- paid + auto_confirm --> C[BookingManager::confirm]
    PM -- paid + manual --> H[extendHold·0 → operator queue]
    PM -- failed --> R[BookingManager::release]
Method Effect
createForBooking($booking, $gateway, $amount?) Pending BookingPayment (amount defaults to the category deposit).
initiate($payment) Delegates to the gateway (redirect URL / instructions / nothing).
settle($payment) paid; then confirm() if the resource auto-confirms, else extendHold(0) to await operator validation.
fail($payment) failed; release() the held booking.
refund($payment) Calls the gateway's refund; → refunded on success.

Writing a gateway

A gateway implements only the provider-specific bits; the engine contract is handled for you.

namespace Drupal\my_gateway\Plugin\PaymentGateway;

use Drupal\yoyaku_payment\Attribute\PaymentGateway;
use Drupal\yoyaku_payment\BookingPaymentInterface;
use Drupal\yoyaku_payment\PaymentGatewayBase;
use Drupal\Core\StringTranslation\TranslatableMarkup;

#[PaymentGateway(id: 'my_gateway', label: new TranslatableMarkup('My gateway'))]
final class MyGateway extends PaymentGatewayBase {

  public function initiate(BookingPaymentInterface $payment): array {
    // Start the payment; return a redirect URL / instructions.
    return ['redirect' => 'https://…'];
  }

  public function refund(BookingPaymentInterface $payment): bool {
    // Refund via the provider; return success.
    return TRUE;
  }

}

On the provider's return/webhook, call PaymentManager::settle($payment) (or fail()), and the booking advances automatically.

Built-in and companion gateways

The gateways themselves are provided by the standalone Kessai payment engine that yoyaku consumes, not by yoyaku:

  • manual (from Kessai) - an operator records an offline payment (cash / cheque / transfer); no external integration.
  • worldline (from the kessai_worldline submodule) - a Worldline (Direct) gateway using the Worldline PHP SDK. It takes payment on a hosted checkout, settles via the browser return and an HMAC-verified server-to-server webhook (so a payer who never returns is still settled), and charges deferred deposit holds and no-show fees as card-on-file subsequent merchant-initiated transactions against the booking's own authorized payment id. To make that abandoned-browser settlement reliable you must turn the webhook on: see the Kessai Worldline README for the endpoint and credentials. See also Deposits, no-show, and the real-gateway design.

See Deposits and payment for the end-to-end flow, including the pay-first / operator-validation pattern.