Skip to content

Trials & discounts ​

Free trials and discounts work for subscriptions, app subscriptions and per subscription of a checkout session. Both are set when the subscription is created.

Free trials ​

Give a subscription a free trial with trial_days (1–365, counted from the user's confirmation or the checkout) or a fixed trial_ends_at (ISO 8601, in the future) – not both. The subscription starts right away with status trial and nothing billed; the first period is billed when the trial ends.

php
$subscription = $user->subscriptions()->create([
    'name' => 'Pixel Stream Pass',
    'unit' => 'month',
    'price' => 6.714286,
    'trial_days' => 14,
    'trial_once' => true,
]);
  • trial_once: true refuses the trial if the user already had one with your app in the same group (422 when creating, 409 trial_already_used when checking out). In a checkout session only one subscription per group may ask for it.
  • A few days before the trial ends, Anikeen ID sends subscription.trial_will_end, e.g. to remind the user; data.payment_method_missing tells you whether the user still has to add a payment method.
  • Cancelling during the trial bills nothing.
  • A trial_ends_at that passed before the checkout means no trial.

Trials without a payment method ​

By default the user needs a payment method to confirm a trial. With trial_payment_method: "optional" (only together with a trial; required is the default) the user can confirm it without one, as long as nothing else has to be paid: in a checkout session every subscription has to be such a trial.

The confirmation page tells the user to add a payment method before the trial ends. If there is still none then, the subscription is cancelled at the trial's end instead of billed:

  • status cancelled, cancellation_reason trial_ended_without_payment_method,
  • subscription.cancelled with data.reason set to the same value, and no subscription.renewed.

Adding a payment method later doesn't bring it back – create a new subscription. The trial still counts for trial_once. Anikeen ID also mails the user a few days before such a trial ends without a payment method.

php
use Anikeen\Id\Enums\CancellationReason;
use Anikeen\Id\Enums\TrialPaymentMethod;
use Anikeen\Id\Webhooks\EventType;

$subscription = $user->appSubscriptions()->create([
    'group' => $teamId,
    'name' => 'Example Pro',
    'unit' => 'month',
    'price' => 19.00,
    'trial_days' => 14,
    'trial_payment_method' => TrialPaymentMethod::OPTIONAL,
]);

// in your webhook handler
$data = $event['data'];

match ($event['type']) {
    EventType::SUBSCRIPTION_TRIAL_WILL_END => $data['payment_method_missing']
        ? Reminder::addPaymentMethod($data['object']['id'])
        : null,
    EventType::SUBSCRIPTION_CANCELLED => $data['reason'] === CancellationReason::TRIAL_ENDED_WITHOUT_PAYMENT_METHOD
        ? Plan::downgrade($data['object']['id'])
        : Plan::revoke($data['object']['id']),
    default => null,
};

Discounts ​

A discount (e.g. a coupon) lowers the price of billed periods: every discounted period gets a negative line with the label next to its usage on the same bill, until the discount ends.

php
$subscription = $user->subscriptions()->create([
    'name' => 'Pixel Stream Pass',
    'unit' => 'month',
    'price' => 6.714286,
    'discount' => [
        'label' => 'SUMMER20',         // shown to the user, e.g. the coupon code
        'percent_off' => 20,           // or 'amount_off' => 1.50 (net, per period)
        'duration' => 'repeating',     // once, repeating or forever
        'duration_in_periods' => 3,    // only for repeating
    ],
]);

$subscription->discount;               // {label, percent_off, amount_off, duration, duration_in_periods, periods_remaining}
Field
labelrequired, at most 100 characters, shown to the user
percent_off0.01–100, of the net price
amount_offnet, per period, never more than the price (instead of percent_off)
durationonce (the first billed period), repeating or forever
duration_in_periods1–1200, only for repeating
  • A period is one unit of the subscription (a month for monthly prices).
  • A free trial isn't a billed period: the discount starts with the first paid one.
  • periods_remaining counts down with every billed period (null for forever, 0 once it ended); then subscription.discount_ended is sent.
  • A discount can't be combined with a monthly_cap.
  • Trusted apps change it on a running subscription with change.
  • A resize (predecessor_id) doesn't carry the discount over: send the successor the rest ("duration": "repeating", "duration_in_periods" = the old periods_remaining).

In order.* webhooks and app orders every item has discount (null, or {label, percent_off, amount_off} on a discount line), so you can attribute the net amount actually paid per subscription.

Anikeen ID developer documentation