Commit Graph
38 Commits
Author SHA1 Message Date
Maurice Preuß a54b5fe598 Document trials without a payment method
trial_payment_method (TrialPaymentMethod::REQUIRED, the default, or
::OPTIONAL) on app subscriptions, subscriptions and checkout sessions:
an optional trial can be confirmed without a payment method and is
cancelled at its end if there is still none (cancellation_reason and
subscription.cancelled data.reason
CancellationReason::TRIAL_ENDED_WITHOUT_PAYMENT_METHOD).
subscription.trial_will_end carries data.payment_method_missing.
2026-11-04 22:44:03 +01:00
Maurice Preuß a68e9ed257 Merge main into feature/uptimr-billing-api 2026-11-04 22:11:16 +01:00
Maurice Preuß df7e506a5e Require patched Laravel and php-jwt versions
Raise the minimum versions so installs can no longer resolve releases with
known advisories (Dependabot alerts):

- laravel/framework / illuminate/*: ^12.69|^13.0 (TokenGuard auth bypass,
  CRLF injection in the email rule, signed URL path confusion, debug page XSS).
  Laravel 11 is end of life and gets no fixes, so support for it is dropped.
- firebase/php-jwt: ^7.0 (weak encryption in 6.x).
- orchestra/testbench: ^10.0|^11.0 to match.

Tests pass with both the lowest and the highest allowed versions.
2026-11-04 22:05:30 +01:00
Maurice Preuß 7f16155701 Document first_period on subscription.renewed and login-free billing portal links
- subscription.renewed is now sent for the first paid period too;
  data.period.first_period tells it apart (README, EventType, test payload).
- Billing portal links need no login, are single use and valid for about
  15 minutes: create them on click and redirect right away (README,
  createBillingPortalLinkForUser(), appBillingPortalUrl()).
2026-11-04 21:57:49 +01:00
Maurice Preuß 35ca9ddd2b Create billing portal links for users as a trusted app
AnikeenId::createBillingPortalLinkForUser() and $user->appBillingPortalUrl()
create a short-lived link to a user's billing pages with the app's token
(return_url, group, flow), e.g. for a Manage billing button of an app
that bills a team owner's account.
2026-11-04 21:27:26 +01:00
Maurice Preuß e45a871ca7 Let users confirm orders of trusted apps
confirm, return_url and metadata on createOrderForUser() and orders
created with the user's token: the order stays a draft with a
confirmation_url and expires after 24 hours (409 confirmation_expired on
checkout). metadata can be changed on a draft with Order::update().
2026-11-04 21:26:47 +01:00
Maurice Preuß bd56f114da Read order lines and invoices of app orders
Document the items (subscription, period, net amount, discount), invoice
and metadata of orders read with the app's token, add
AppOrders::invoicePdf() for the invoice PDF and the opt-in
subscription.renewed event type with its period data.
2026-11-04 21:25:56 +01:00
Maurice Preuß db3c50af72 Change running subscriptions
AppSubscription::change() changes price, unit, name, description or
discount of a running subscription, prorated right away (APPLY_NOW) or
from the next period on (APPLY_PERIOD_END, shown as scheduled_change);
cancelScheduledChange() drops a scheduled one. Adds the
subscription.updated event type and documents proration_not_possible
and credit_exceeds_bill.
2026-11-04 21:25:00 +01:00
Maurice Preuß bf21142f72 Discount subscriptions per billed period
discount (label, percent_off or amount_off, duration once, repeating or
forever) when creating subscriptions and per subscription of checkout
sessions, the discount field on subscriptions and the
subscription.discount_ended event type.
2026-11-04 21:24:22 +01:00
Maurice Preuß 5cc1a519c3 Start subscriptions with a free trial
trial_days, trial_ends_at and trial_once when creating subscriptions (app and
user token) and per subscription of checkout sessions. isTrial() on
subscriptions, the subscription.trial_will_end event type, and the
trial_already_used error code in the docs.
2026-11-04 21:23:52 +01:00
Maurice PreußandClaude Opus 5.5 57ce0b4a79 Throw ApiException for requests with the user's token too
Requests with the user's token threw a bare ResourceException with a
message like "… for resource [Anikeen\Id\Resources\Order]" and no
error code, while app requests threw ApiException. Both throw
ApiException now: status (also getCode()), Anikeen ID's error code and
message, isNotFound(), isConflict(), isTemporary(). Failed lists throw
CollectionException, now a subclass of ApiException. A request without
a response (connection refused, timeout) throws ApiException with
status 0 instead of a Guzzle exception.

ApiException extends ResourceException, so existing catch blocks keep
working.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 18:28:50 +01:00
Maurice PreußandClaude Opus 5.5 ce820cd198 Add subscription status helpers
status() returns draft, trial, active, paused, cancelling, expired or
cancelled (SubscriptionStatus constants), derived from the fields the
same way Anikeen ID derives its status, so it also works with older
responses. isActive() is true while the subscription runs and is paid.
Both Subscription and AppSubscription have them. Document the states.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 16:15:48 +01:00
Maurice PreußandClaude Opus 5.5 2faccac0f4 Document Stripe test cards on staging
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 16:09:47 +01:00
Maurice PreußandClaude Opus 5.5 e3e59fb8a4 Document payments end to end
Add a Payments guide that follows a typical third-party shop: sign-in
scopes, user table and token storage, creating orders and subscriptions,
the confirmation and return flow, statuses, prices and VAT, shipments,
webhooks with Laravel 11 CSRF exclusion, error handling and testing.

Move the trusted-app sections and API authentication after it, add the
webhook secret to the config, fix the broken General/Examples snippets
and the Scope enum link.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 16:06:23 +01:00
Maurice PreußandClaude Opus 5.5 2acbef72c8 Drop the live API test hint from the README
The tests use a mocked HTTP client and ignore these variables. Also move
the shipping address and shipment docs into README.stub, so
composer docs no longer drops them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 16:00:24 +01:00
Maurice PreußandClaude Opus 5.5 dc29eb2844 Fix the anikeen API guard and register its user provider
- Register the guard driver and the anikeen user provider with static closures:
  Laravel binds extend() closures to the AuthManager, so $this->makeGuard() failed.
- Look the token's sub up in the user's Anikeen ID column (anikeen_id by default;
  provider 'column', services.anikeen.user_id_column or useUserIdField()),
  not in the primary key; works with the anikeen, eloquent and database providers.
- JwtParser: key from services.anikeen.public_key(_path) or the bundled key, read
  once per process; misconfiguration is an error, not a 401. Passport tokens have
  no client claim, so it is optional now.
- Optional revocation check against Anikeen ID (verify_revocation), cached per token.
- anikeenTokenCan() without a token, anikeenTokenCanAny(), scope middleware accept
  JSON responses.
- Update the bundled staging public key to the current staging key.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VbpgPaLqhBzo1AXHNs9Cx5
2026-11-03 23:07:58 +01:00
Maurice PreußandClaude Opus 5.5 3acd36ca29 Add checkout sessions and webhook event type constants
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 17:26:24 +01:00
Maurice PreußandClaude Opus 5.5 934b1c5c3f Document the shipment mail and notify_customer
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 14:57:46 +01:00
Maurice PreußandClaude Opus 5.5 5349ee81f6 Ship orders: shipments with delivery notes
$order->shipments()->create([...]) records a delivery of a paid order's goods on Anikeen ID,
which adds its delivery note to the user's order documents and sends order.shipped.
$shipment->deliveryNote() downloads the PDF.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 14:01:45 +01:00
Maurice PreußandClaude Opus 5.5 fd96e559af Document shipping address fields and item images on orders
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 12:36:26 +01:00
Maurice PreußandClaude Opus 5.5 526b65cb5c Verify Anikeen ID webhooks
Webhook::constructEvent() checks the Anikeen-Signature header (HMAC-SHA256
of "t.body" with the endpoint's secret, 5-minute tolerance) and returns
the event, like Stripe's helper.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 11:14:42 +01:00
Maurice PreußandClaude Opus 5.5 f20a12e6da Support third-party checkout confirmation
Orders and subscriptions created by untrusted apps are confirmed by the
user on Anikeen ID. confirmationUrl($returnUrl) on Order and
Subscription builds the link with the return URL; checking out such a
draft throws ConfirmationRequiredException carrying the same URL.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 11:01:48 +01:00
Maurice PreußandClaude Opus 5.5 c4a42b4ddc Check out a running service from its start
AppSubscription::checkout() takes an optional $startsAt (needs id-core
with starts_at support on PUT v1/client/subscriptions/{id}/checkout).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 19:03:47 +01:00
Maurice PreußandClaude Opus 5.5 820f1b37cc Read the orders that bill the app's subscriptions
$user->appOrders() and app(AnikeenId::class)->appOrders() read
v1/client/orders with the app's own token (scope orders:client):
all(), pending() and find(), paginated transparently, user scoped
when called on a billable.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 18:14:40 +01:00
Maurice PreußandClaude Opus 5.5 9613d31a8d Bring the package up to date with the Anikeen ID API
Fixed (called endpoints that don't exist):
- charge(): POST v1/billing/charge (Stripe options aren't passed through anymore)
- Invoice::getInvoiceTemporaryUrl(): GET instead of a non-existent PUT
- OrderItems::create(): POST v1/orders/{order}/items
- createUser() / isEmailExisting(): admin endpoints with the app token
- sshKeysByUserId(): app endpoint; no longer returns the first user's keys for every user
- createOrderPreview(): country_iso goes into billing_address as the API expects
- public endpoints (products, order preview) work without a token
- SshKeys::find() results can be deleted

Added:
- Billable: sshKeys() (create, update, delete), billingOverview(), PaymentMethod::delete()
- AnikeenId: products(), boxes(), shipping labels (list, show, pdf, issue, ship, price list),
  createOrderForUser(), createTransactionForUser(), findOrCreateSocialiteUser()

Requirements: PHP 8.2+, Laravel 11-13 (Laravel 10 and PHP 8.1 are end of life).
Docs generator handles union types; README regenerated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 17:18:05 +01:00
Maurice PreußandClaude Opus 5.5 8de737641e App subscriptions: manage a user's subscriptions with the app's own token
- $user->appSubscriptions(): create (idempotency_key), find, all, checkout, revoke, pause,
  resume, update against /v1/client/subscriptions with a client credentials token, so apps don't
  depend on the user's login
- user id from the billable's anikeen_id (AnikeenId::useUserIdField())
- ApiException with status, stable error code, isNotFound/isConflict/isTemporary; network
  errors no longer escape as raw Guzzle exceptions
- rejected app tokens are renewed once; asApp() never changes the shared client's token
- tests (testbench + Guzzle mock) and CI

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 17:02:16 +01:00
envoyr dcda4b990e update user provider
Signed-off-by: Maurice Preuß (envoyr) <hello@envoyr.com>
2025-04-30 12:00:34 +02:00
envoyr 937fde603b update docs
Signed-off-by: Maurice Preuß (envoyr) <hello@envoyr.com>
2025-04-30 10:03:27 +02:00
envoyr 80b1f003b2 update docs
Signed-off-by: Maurice Preuß (envoyr) <hello@envoyr.com>
2025-04-30 10:02:53 +02:00
envoyr 92eadcca08 update docs
Signed-off-by: Maurice Preuß (envoyr) <hello@envoyr.com>
2025-04-30 10:00:27 +02:00
envoyr 8b874f540c update docs
Signed-off-by: Maurice Preuß (envoyr) <hello@envoyr.com>
2025-04-30 09:08:29 +02:00
envoyr 85702fcb2c update docs
Signed-off-by: Maurice Preuß (envoyr) <hello@envoyr.com>
2025-04-29 21:50:12 +02:00
envoyr d447a88430 add mode
Signed-off-by: Maurice Preuß (envoyr) <hello@envoyr.com>
2025-04-29 21:33:02 +02:00
envoyr d9a330222b update billable
Signed-off-by: Maurice Preuß (envoyr) <hello@envoyr.com>
2025-04-28 17:26:02 +02:00
envoyr 21946e3a22 update docs
Signed-off-by: Maurice Preuß (envoyr) <hello@envoyr.com>
2025-04-28 05:32:44 +02:00
envoyr 1b96b87e1d refactored code
Signed-off-by: Maurice Preuß (envoyr) <hello@envoyr.com>
2025-04-28 05:27:06 +02:00
envoyr 7f908f4e6a refactored code
Signed-off-by: Maurice Preuß (envoyr) <hello@envoyr.com>
2025-04-28 04:47:50 +02:00
envoyr 05e8cca347 first commit 2025-04-27 04:02:46 +02:00