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_paymenton the Resource;deposit_requiredanddeposit_amounton the Category. BookingPaymententity - 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.PaymentGatewayplugin 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 thekessai_worldlinesubmodule) - 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.