Subscription ordering and payment webhook – behaviour

Checks and permission grants of the order endpoint, and signature verification, idempotency and state changes of the payment provider notification.

Order (api/subscription/order.order)

POST definition_id, plan_id, months. The code checks no function permission.

  1. API key → the key owner is the orderer (401 if missing).
  2. getDefinition(): only status=1 and enabled=1 (otherwise 404); definitionHasPlan (400); planOnStore (403).
  3. Validity: start = the user's registration (registeredAt), end = start + months (max 120; 0 = open-ended).
  4. Targets: with a home client (dlbtclient->homeClient) every member of the client (usersOf), otherwise only the orderer.
  5. Per member dlbtprovision->materialise(uid, definition_id, plan_id, from, to, user_id, 'order', false) — adds, does not replace.
  6. Response: company_client_id, users, result (created, updated, deleted), valid_from, valid_to, definition.

Prerequisite: dlbtprovision registered (otherwise 409). Tables read: subscription_definition, subscription_definition_plan, subscription_plan, subscription_plan_to_store; written: user_feature (source='order'). No payment check.

Webhook (api/subscription/webhook)

A public endpoint protected by signatures.

  • SimplePay (if dlbtpayment->provider() = simplepay): Signature header checked on the raw body (401); JSON (400); event_ref required (400); claimEvent() unique-key insert, a repeated event only gets a receipt; applySimplepay(): find the transaction, attach provider_ref, payment.succeeded → store token and settlePaid(), payment.failed → settleFailed(); finally markEventProcessed() and a signed receipt.
  • Generic path: HMAC in the X-Dlbt-Signature header with the webhook secret stored in the module settings (a missing secret is also 401); id, type, provider, data; idempotency (duplicate:true); payment.succeeded → settlePaid, payment.failed → settleFailed, subscription.canceled → state canceled. On a processing error 500 ("The event was stored but could not be processed.").
  • webhook.back: information only (pending:true), no state change — a browser return is no proof.

The own tables of the dlbtpayment library (event, transaction, subscription) cannot be named from the controller. Charge requests are created by the subscription lifecycle background job.