Install

Works with Drupal: ^11.3 || ^12

Using Composer to manage Drupal site dependencies

Alternative installation files

Download tar.gz 918.19 KB
MD5: c049c08a4ff2950d5c419079ee7046b3
SHA-1: 712d9db1b9d8bea67e80332ddcc0efc0f20c2ff4
SHA-256: 68da933d72435ea35d8a7dccb9fa714d03f11d1f6bdec61ce7c677d3d347fb02
Download zip 1.15 MB
MD5: 168ae9ca25df61edd04adcae9afc730c
SHA-1: d02c01839e478942b1d571c4075f22ac70edd242
SHA-256: 1f68a2dc41c59729c1edd4bd6f36dc9882c12c2d1d0f18cd88bb9febdad5021e

Release notes

No update hook: reinstall kessai_engine, as for every alpha, and upgrade both sides of a remote setup together. This release adds base fields to kessai_payment and its movements (among them store_payment_method, payment_method_valid_until, released, money_after_end, source_payment, last_reconciled and a request_key on claims and refunds), renames token to payment_method_id and token_operation to payment_method_operation, and replaces the reversal movement with a release movement (kessai_release). A site keeping its payments installs the new fields, copies the old columns across and uninstalls the old fields by hand. The wire names change, so an alpha10 host and an alpha11 consumer do not understand each other: upgrade the site running kessai_server and its consumer sites at the same time. Re-import the French catalogs with drush locale:import fr MODULE/translations/fr.po --type=not-customized --override=not-customized, one per catalog. orchestra and yoyaku need their releases that follow these names.

Breaking changes in the API

  • #3622020. A caller never names a stored payment method. authorizeToken() and chargeToken() are authorizeStoredPaymentMethod() and chargeStoredPaymentMethod(), with no $token argument: the engine uses the subject's latest stored payment method whose payer still agrees. Across the wire the door is /kessai-api/payments/with-stored-payment-method.
  • #3622020. The provider's id of a stored payment method is a payment method id: getPaymentMethodId(), PaymentQuery's payment_method_id and has_payment_method_id (on the wire too), payment_method_operation and PAYMENT_METHOD_OPERATION_*, isStoredMethodPayment(), recordStoredPaymentMethod(), and StoredPaymentMethodGatewayInterface with chargeStoredPaymentMethod() and deleteStoredPaymentMethod(). "Token" now means only the OAuth access token and the handoff and return links.
  • #3622015. A hold nobody collected is released: release(), the release movement, the released state and the RELEASED and PARTIALLY_RELEASED events. Canceled now means only a pending payment called off.
  • #3622015. capture(), refund() and release() take an optional request key, and recording what a provider already took is recordPaymentReceived(), split from capture().
  • #3622015. The consumer is out of the contract: an engine acts for this site, or for one consumer through forConsumer().
  • #3622015. The agreement to charge later is store_payment_method and payment_method_valid_until, withdrawn with forgetStoredPaymentMethods(). It is refused on a zero amount and on an account that cannot charge later. Kinds and metadata names starting with kessai_ are reserved.

Money moves once

  • #3622015. A claim, refund, release, hold or charge asked again after its answer was lost no longer moves the money a second time. Asked again under the same request key, the movement it made answers. A claim, refund or release unanswered for 23 hours or more is looked up among the transaction's operations instead of being sent again.
  • #3622015. A rejected first attempt no longer fails a checkout the payer can still pay, two late acceptances arriving together no longer overwrite each other, and a released hold reported again is no longer read as new money.
  • #3622015. A payment whose money is still moving cannot be deleted through any door, money a provider took after a payment ended is given back once, and a lapsed hold leaves "authorized".
  • #3622020. On SQLite, which reads a decimal back with 14 significant digits, the largest amount is 9999999999.9999, and a larger one is refused rather than stored rounded. Other databases keep 999999999999999.9999.
  • #3622020. The simulator declines a hold or a charge with no stored card, so a forgotten card is no longer charged again on it.

Input checked before storage

  • #3622015. A value kessai matches on (subject, metadata, consumer, payment method id, client id) may not end in whitespace, which MySQL ignores on exact-match columns. Text that is not UTF-8, or a value longer than its column, is refused with \InvalidArgumentException (400 over the wire) instead of failing in the database.

Each site's payments kept apart

  • #3622015. On a host, another site's payment no longer loads the host's own entity as its subject. A site running its own engine beside a client no longer sends its own payment to the host. The client refuses a host's answer about another payment, and only a payment settled by hand takes a consumer's word that it was paid.
  • #3622015. A site copied to another environment no longer trusts the host it was copied from: what a remote learns is kept with the address and client id it was learned from.
  • #3622015. A consumer site installed under a path is told when its payments move, and a site serving several domains gives each domain a remote of its own.

Payers sent back to the right page

  • #3622019. A return address keeps its language prefix, so a payer who left from /fr/node/1 comes back to it rather than to the English page, and a site installed under a path no longer gets that path twice. A return address is taken as Url::toString() writes it.

Code and documentation

#3622020: docs and docblocks that no longer matched the code, tests for three guards no test covered, and a refreshed metrics page.

Created by: mably
Created on: 12 Nov 2026 at 13:52 UTC
Last updated: 12 Nov 2026 at 13:53 UTC
Bug fixes
New features

Other releases