Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,22 @@ All notable changes to `vatly-api-php` will be documented in this file.

## Unreleased

## v0.1.0-alpha.33

### Added

- **`relatedResources` on order lines**: `OrderLine->relatedResources` and `OrderLineData->relatedResources` (on the `OrderPaid` webhook DTO's `lines`) are a nullable `Vatly\API\Types\OrderLineRelatedResources` with a `?string $subscriptionId`: the subscription the line belongs to, on initial signup, renewal and immediate-update lines alike. To find the subscription a paid checkout created, follow `Checkout->orderId` and read its lines.
- `relatedResources` is `null` on historical lines, on lines unrelated to a subscription (such as one-off product lines), and when a payload leaves the key out.
- On a signup line, `subscriptionId` is `null` while the subscription is not available yet. Retrieve the order again later to read it.
- A subscription ID does not mean the subscription is active. Check its status before granting access.
- Additive: `OrderLineData` gains an optional last constructor argument that defaults to `null`.

### Changed

- **Synced `openapi.yaml`** with the published spec. It adds `lines[].relatedResources` to orders, and `links.customerInvoice` (the credit note, `Link` or `null`) to the Refund and Chargeback schemas, which the SDK already supported since v0.1.0-alpha.32.
- `productType` and `productId` keep their meaning. The docs now say it correctly: an initial signup line references the plan (`subscription_plan`, a `subscription_plan_…` ID); renewal and immediate-update lines reference the subscription (`subscription`). Earlier docblocks said a subscription line always carried the subscription ID.
- `docs/Orders.md` documents the order line properties.

## v0.1.0-alpha.32

### Added
Expand Down
28 changes: 28 additions & 0 deletions docs/Orders.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,34 @@ Below you'll find all properties for the Vatly Order resource.
| `createdAt` | `string` | Creation timestamp (ISO 8601). |
| `links` | `OrderLinks` | Links to this order, the customer (`null` for orders with no associated customer) and the customer invoice. The `customerInvoice` URL is signed and expires after 72 hours (the customer then sees a page to request a new link). |

### Order line properties

| Name | Type | Description |
| --- | --- | --- |
| `id` | `string` | Unique identifier for the order line. |
| `description` | `string` | Line description. |
| `quantity` | `int` | Quantity. On a subscription line this is the quantity of one subscription, not a count of subscriptions. |
| `basePrice` | `Money` | Price per unit before taxes. |
| `total` | `Money` | Line total including taxes. |
| `subtotal` | `Money` | Line subtotal before taxes. |
| `taxes` | `TaxSummaryCollection` | Tax breakdown for this line. |
| `productType` | `string \| null` | The linked resource type: `subscription_plan` on an initial subscription signup line, `subscription` on a renewal or immediate-update line, `one_off_product` for a one-off product. `null` when the line has no product link. |
| `productId` | `string \| null` | The linked resource's ID: a plan ID for `subscription_plan`, a subscription ID for `subscription`. |
| `relatedResources` | `OrderLineRelatedResources \| null` | Additional relationships for this line. `null` on historical lines and on lines unrelated to a subscription. |
| `relatedResources->subscriptionId` | `string \| null` | The subscription this line belongs to, on signup, renewal and immediate-update lines alike. `null` on a signup line while the subscription is not available yet; retrieve the order again later to read it. |

To find the subscription a paid checkout created, follow the checkout's `orderId` and read the lines' `relatedResources`:

```php
$order = $vatly->orders->get($checkout->orderId);

foreach ($order->lines() as $line) {
$subscriptionId = $line->relatedResources?->subscriptionId;
}
```

A subscription ID alone does not mean the subscription is active. Check the subscription's status before granting access.

---

## Retrieve an order
Expand Down
119 changes: 110 additions & 9 deletions openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1965,6 +1965,10 @@ paths:
description: |
Retrieves a specific order by its ID.
Includes full order details with line items, totals, and billing information.
For a paid checkout, follow `Checkout.orderId` and inspect `lines[].relatedResources`.
Signup, renewal and immediate-update lines expose the same subscriptionId.
Product references are unchanged. Neither
creation, a paid order nor an ID alone proves activation or entitlement.
tags:
- Orders
parameters:
Expand Down Expand Up @@ -2009,7 +2013,7 @@ paths:
lines:
- id: order_item_Jk4pQrSvWm8NjLhYbUcP
resource: orderline
description: Pro Monthly Subscription
description: Pro Monthly Subscription — initial checkout
quantity: 1
basePrice:
value: '29.00'
Expand All @@ -2028,8 +2032,10 @@ paths:
amount:
value: '6.09'
currency: EUR
productType: subscription
productId: subscription_Hn5xWqVfKm8RjTgYbUcP
productType: subscription_plan
productId: subscription_plan_Hn5xWqVfKm8RjTgYbUcP
relatedResources:
subscriptionId: subscription_Hn5xWqVfKm8RjTgYbUcP
customerDetails:
fullName: John Doe
companyName: Acme Corp
Expand Down Expand Up @@ -2191,6 +2197,7 @@ paths:
href: https://api.vatly.com/v1/orders/order_Fp2kQrSvWm8NjLhYbUcP
type: application/json
order: null
customerInvoice: null
count: 1
links:
self:
Expand Down Expand Up @@ -2322,6 +2329,7 @@ paths:
href: https://api.vatly.com/v1/orders/order_Fp2kQrSvWm8NjLhYbUcP
type: application/json
order: null
customerInvoice: null
'401':
$ref: '#/components/responses/Unauthorized'
'403':
Expand Down Expand Up @@ -2450,6 +2458,7 @@ paths:
href: https://api.vatly.com/v1/orders/order_Fp2kQrSvWm8NjLhYbUcP
type: application/json
order: null
customerInvoice: null
'401':
$ref: '#/components/responses/Unauthorized'
'403':
Expand Down Expand Up @@ -2537,6 +2546,9 @@ paths:
order:
href: https://api.vatly.com/v1/orders/order_Bm7xNvPwKr3YjTgHcZaE
type: application/json
customerInvoice:
href: https://vatly.com/invoices/order_Bm7xNvPwKr3YjTgHcZaE
type: text/html
'401':
$ref: '#/components/responses/Unauthorized'
'403':
Expand Down Expand Up @@ -2668,6 +2680,9 @@ paths:
order:
href: https://api.vatly.com/v1/orders/order_Rk5pQrSvWm8NjLhYbUcP
type: application/json
customerInvoice:
href: https://vatly.com/invoices/order_Rk5pQrSvWm8NjLhYbUcP
type: text/html
count: 1
links:
self:
Expand Down Expand Up @@ -2745,6 +2760,9 @@ paths:
order:
href: https://api.vatly.com/v1/orders/order_Rk5pQrSvWm8NjLhYbUcP
type: application/json
customerInvoice:
href: https://vatly.com/invoices/order_Rk5pQrSvWm8NjLhYbUcP
type: text/html
'401':
$ref: '#/components/responses/Unauthorized'
'403':
Expand Down Expand Up @@ -2845,6 +2863,9 @@ paths:
order:
href: https://api.vatly.com/v1/orders/order_Bm7xNvPwKr3YjTgHcZaE
type: application/json
customerInvoice:
href: https://vatly.com/invoices/order_Bm7xNvPwKr3YjTgHcZaE
type: text/html
count: 1
links:
self:
Expand Down Expand Up @@ -2931,6 +2952,9 @@ paths:
order:
href: https://api.vatly.com/v1/orders/order_Bm7xNvPwKr3YjTgHcZaE
type: application/json
customerInvoice:
href: https://vatly.com/invoices/order_Bm7xNvPwKr3YjTgHcZaE
type: text/html
'401':
$ref: '#/components/responses/Unauthorized'
'403':
Expand Down Expand Up @@ -3020,6 +3044,9 @@ paths:
order:
href: https://api.vatly.com/v1/orders/order_Rk5pQrSvWm8NjLhYbUcP
type: application/json
customerInvoice:
href: https://vatly.com/invoices/order_Rk5pQrSvWm8NjLhYbUcP
type: text/html
count: 1
links:
self:
Expand Down Expand Up @@ -3091,6 +3118,9 @@ paths:
order:
href: https://api.vatly.com/v1/orders/order_Rk5pQrSvWm8NjLhYbUcP
type: application/json
customerInvoice:
href: https://vatly.com/invoices/order_Rk5pQrSvWm8NjLhYbUcP
type: text/html
'401':
$ref: '#/components/responses/Unauthorized'
'403':
Expand Down Expand Up @@ -6737,10 +6767,20 @@ components:
customerInvoice:
allOf:
- $ref: '#/components/schemas/Link'
description: Link to the customer-facing invoice page in the customer portal (text/html)
description: |
Link to the customer-facing invoice page in the customer portal (text/html).
On a refund or chargeback order this page is the credit note. The link is signed
and expires after 72 hours; an expired link lets the customer request a fresh one by email.
OrderLine:
type: object
description: A line item in an order
description: |
A line item in an order. `productType`/`productId` retain their existing meaning:
initial subscription lines reference the plan; renewal and immediate-update
lines reference the existing subscription. `relatedResources` adds the exact
per-line subscription relationship without repeating the product reference.
Discounts remain components of their owning line.
Follow a paid Checkout's `orderId` to its order lines; Checkout is unchanged.
No historical backfill is performed.
required:
- id
- resource
Expand Down Expand Up @@ -6795,14 +6835,55 @@ components:
- billing_details_verification
- auth_request_product
- null
description: The product type this line links to; null when the line has no product link.
example: subscription
description: |
Linked resource type. Initial subscription orders use
`subscription_plan`; renewals and immediate updates use `subscription`.
Existing values and new-write behavior are unchanged.
example: subscription_plan
productId:
type:
- string
- 'null'
description: The linked product's id; for a `subscription` line this is the public `subscription_…` id.
example: subscription_Hn5xWqVfKm8RjTgYbUcP
description: |
Linked resource ID: a plan ID when productType is subscription_plan,
a subscription ID when productType is subscription. Existing values and
new-write behavior are unchanged.
example: subscription_plan_Hn5xWqVfKm8RjTgYbUcP
relatedResources:
description: |
Additional per-line relationships. Null on historical lines without recorded
correlation and on unrelated lines (including one-off, billing-verification
and authorization-minimum lines). Older payloads may omit this property.
Signup, renewal and immediate-update lines use the same subscriptionId:
the subscription associated with this line, not a creation or entitlement
signal. On ordinary sale lines productType distinguishes initial signup
(subscription_plan) from renewal/update (subscription). It does not
distinguish a renewal from an immediate update. Reversal lines copy the
original relationship and product reference, not a new creation claim.
oneOf:
- type: object
additionalProperties: false
required:
- subscriptionId
properties:
subscriptionId:
type:
- string
- 'null'
description: |
Subscription associated with this line. On initial signup lines,
null while the exact subscription projection is unavailable,
including pending or skipped creation; null does not distinguish
these cases and is not proof that creation failed or was skipped.
The deterministic per-line identity is retained durably, so GET
reveals the ID after projection recovery without recreating the
paid order. Frozen webhook deliveries retain their original snapshot;
GET and newly generated webhooks use the current availability.
Separate same-plan checkout lines have distinct subscription IDs;
quantity is the quantity of one subscription, not a subscription count.
Creation does not imply activation, entitlement or immediate readability.
Validate ownership and status before granting access.
- type: 'null'
InvoiceUpdateLink:
type: object
description: A signed URL for the customer to update invoice details (billing address, VAT number, company name)
Expand Down Expand Up @@ -6920,6 +7001,7 @@ components:
- self
- originalOrder
- order
- customerInvoice
additionalProperties: false
properties:
self:
Expand All @@ -6935,6 +7017,15 @@ components:
- $ref: '#/components/schemas/Link'
- type: 'null'
description: Link to the credit note order. Always present; `null` until the credit-note order is created.
customerInvoice:
oneOf:
- $ref: '#/components/schemas/Link'
- type: 'null'
description: |
Link to this refund's credit note in the customer portal (text/html). This is the
same page as the credit-note order's `customerInvoice` link, not the original order's invoice.
Always present; `null` until the refund is processed. The link is signed and
expires after 72 hours; an expired link lets the customer request a fresh one by email.
RefundLine:
type: object
description: A line item in a refund
Expand Down Expand Up @@ -7124,6 +7215,7 @@ components:
- self
- originalOrder
- order
- customerInvoice
additionalProperties: false
properties:
self:
Expand All @@ -7139,6 +7231,15 @@ components:
- $ref: '#/components/schemas/Link'
- type: 'null'
description: Link to the credit note order. Always present; `null` until the credit-note order is created.
customerInvoice:
oneOf:
- $ref: '#/components/schemas/Link'
- type: 'null'
description: |
Link to this chargeback's credit note in the customer portal (text/html). This is the
same page as the credit-note order's `customerInvoice` link, not the original order's invoice.
Always present; `null` until the lost chargeback is processed. The link is signed and
expires after 72 hours; an expired link lets the customer request a fresh one by email.
WebhookEvent:
type: object
description: |
Expand Down
20 changes: 15 additions & 5 deletions src/API/Resources/OrderLine.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace Vatly\API\Resources;

use Vatly\API\Types\Money;
use Vatly\API\Types\OrderLineRelatedResources;
use Vatly\API\Types\TaxSummaryCollection;

class OrderLine extends BaseResource
Expand All @@ -25,20 +26,29 @@ class OrderLine extends BaseResource
public int $quantity;

/**
* The product type this line links to. Null until the backend ships it.
* The resource type this line links to. An initial subscription signup
* line uses `subscription_plan`; renewal and immediate-update lines use
* `subscription`. Null when the line has no product link.
*
* @example subscription
* @example subscription_plan
*/
public ?string $productType = null;

/**
* The linked product's id. For a subscription line this is the public
* `subscription_…` id. Null until the backend ships it.
* The linked resource's id: a plan id when `productType` is
* `subscription_plan`, a subscription id when it is `subscription`.
*
* @example subscription_2a46f4c01d3b47979f4d7b3f58c98be7
* @example subscription_plan_2a46f4c01d3b47979f4d7b3f58c98be7
*/
public ?string $productId = null;

/**
* Additional per-line relationships, such as the subscription this line
* belongs to. Null on historical lines and on lines unrelated to a
* subscription, and when an older response leaves the key out.
*/
public ?OrderLineRelatedResources $relatedResources = null;

public Money $basePrice;

public Money $total;
Expand Down
8 changes: 8 additions & 0 deletions src/API/Resources/ResourceFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
use Vatly\API\Types\Address;
use Vatly\API\Types\Mandate;
use Vatly\API\Types\Money;
use Vatly\API\Types\OrderLineRelatedResources;
use Vatly\API\Types\ScheduledSubscriptionUpdate;
use Vatly\API\Types\TaxSummaryCollection;
use Vatly\API\VatlyApiClient;
Expand Down Expand Up @@ -87,6 +88,13 @@ public static function createResourceFromApiResult(object $apiResult, BaseResour

break;

case 'relatedResources':
$resource->{$property} = null === $value
? null
: OrderLineRelatedResources::createResourceFromApiResult($value);

break;

default:
$resource->{$property} = $value;

Expand Down
Loading
Loading