Skip to content

feat(webhooks): expose cancellationReason on the remaining cancel event DTOs - #60

Merged
sandervanhooft merged 1 commit into
mainfrom
fix/cancel-events-cancellation-reason
Sep 2, 2026
Merged

feat(webhooks): expose cancellationReason on the remaining cancel event DTOs#60
sandervanhooft merged 1 commit into
mainfrom
fix/cancel-events-cancellation-reason

Conversation

@sandervanhooft

Copy link
Copy Markdown
Member

Summary

Consistency fix so every subscription cancellation reason flows through to
downstream consumers (fluent persists cancellationReason when a cancellation
webhook ends a subscription).

Today only SubscriptionCanceledForNonpayment exposes a top-level
cancellationReason. The other two cancel event DTOs don't, so
merchant_request / customer_request were dropped. This adds the property to
both, matching the way the nonpayment DTO surfaces it.

Changes

  • SubscriptionCanceledImmediately — new nullable ?string $cancellationReason
    (its object carries cancellationReason: merchant_request).
  • SubscriptionCanceledWithGracePeriod — new nullable ?string $cancellationReason
    (customer_request from the portal, or merchant_request).

Both read the value straight from the delivery's object['cancellationReason']
in fromWebhook(), null if absent. Unlike the nonpayment DTO (which defaults
to payment_failure), these reflect exactly what's in the payload with no
hardcoded default
. Valid values are the existing Vatly\API\Types\CancellationReason
constants (payment_failure / merchant_request / customer_request).

Tests

  • SubscriptionEventsTest: each DTO hydrates the reason from object and is
    null when absent.
  • WebhookEventFactoryTest: the factory-built events carry the reason too.

Docs (Webhooks.md) and CHANGELOG.md updated.

Verification

  • composer test275 tests, 1370 assertions, OK.
  • php -d memory_limit=1G vendor/bin/phpstan analyse src tests → No errors (level 5).
  • vendor/bin/php-cs-fixer fix --allow-risky=yes → 0 files to fix.

Over to you to merge once CI is green, then cut alpha.28.

…nt DTOs

fluent persists a subscription's cancellationReason downstream when a
cancellation webhook ends it, but only SubscriptionCanceledForNonpayment
exposed a top-level cancellationReason — so merchant_request / customer_request
never flowed through.

- SubscriptionCanceledImmediately and SubscriptionCanceledWithGracePeriod now
  carry a nullable ?string $cancellationReason, read straight from
  object['cancellationReason'] in fromWebhook() (null if absent). No hardcoded
  default (unlike the nonpayment DTO, which defaults to payment_failure) —
  reflects exactly what's in the payload.
- Values are the existing Vatly\API\Types\CancellationReason constants.

Tests assert the reason hydrates from the object (and is null when absent) at
both the DTO and factory level. Docs (Webhooks.md) + CHANGELOG updated.

275 tests green; phpstan (level 5) and php-cs-fixer clean.
@sandervanhooft
sandervanhooft merged commit 83b3c8e into main Sep 2, 2026
12 checks passed
@sandervanhooft
sandervanhooft deleted the fix/cancel-events-cancellation-reason branch September 2, 2026 08:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant