### Install Dependencies and Run Tests Source: https://github.com/nordkit/svea/blob/main/CONTRIBUTING.md Install project dependencies using Composer and run the test suite with Pest. Code style can be fixed using Pint. ```bash composer install vendor/bin/pest vendor/bin/pint ``` -------------------------------- ### Install Svea PHP SDK Source: https://context7.com/nordkit/svea/llms.txt Install the Svea PHP SDK using Composer. For Laravel applications, publish the configuration file after installation. ```bash composer require nordkit/svea ``` ```bash php artisan vendor:publish --tag=svea-config ``` -------------------------------- ### Namespace Usage Examples Source: https://github.com/nordkit/svea/blob/main/CONTRIBUTING.md Demonstrates the standard namespace conventions used throughout the Svea SDK, including core classes, services, contracts, and exceptions. ```php Svea\SveaClient Svea\Checkout\CheckoutService Svea\Admin\AdminService Svea\Contracts\CheckoutServiceInterface Svea\Contracts\AdminServiceInterface Svea\Contracts\SubscriptionServiceInterface Svea\Webhooks\Webhook Svea\Exceptions\SveaApiException Svea\Laravel\SveaServiceProvider Svea\Laravel\Svea ``` -------------------------------- ### Using Svea Facades for API Calls Source: https://github.com/nordkit/svea/blob/main/README.md Interact with Svea's Checkout, Admin, and Subscriptions APIs using the provided facades. Example calls demonstrate creating a checkout, managing an order, and listing subscriptions. ```php use Svea\Laravel\Svea; Svea::checkout()->create(...); Svea::admin()->order('12345678')->deliver(); Svea::subscriptions()->list(); ``` -------------------------------- ### Register and Verify Subscription with Fluent Builder Source: https://github.com/nordkit/svea/blob/main/README.md Use the fluent builder to configure and register a subscription, which automatically calls verify() after registration. This method simplifies the subscription setup process. ```php $subscription = Svea::subscriptions() ->on(EventType::CheckoutOrderCreated, EventType::CheckoutOrderDelivered) ->notifyAt('https://myapp.com/webhooks/svea') ->register(); // registers and verifies ``` -------------------------------- ### Instantiate Svea Client Source: https://github.com/nordkit/svea/blob/main/CONTRIBUTING.md Two entry points for using the Svea client: a static facade for Laravel with zero configuration, or an explicit instance for framework-agnostic use. The explicit instance allows for multi-merchant setups and testability. ```php // Laravel — static facade (zero config, reads config/svea.php) Svea::checkout()->create(...); ``` ```php // Framework-agnostic — explicit instance (portable, testable, multi-merchant) $svea = new SveaClient([ 'merchant_id' => 'abc', 'shared_secret' => 'xyz', 'environment' => 'test', ]); $svea->checkout->create(...); $svea->admin->order('12345678')->deliver(); ``` -------------------------------- ### Subscription Management Commands Source: https://github.com/nordkit/svea/blob/main/README.md Artisan commands for managing Svea subscriptions, including adding, listing, getting, verifying, updating, and removing subscriptions. ```APIDOC ## Subscription Management Commands ### Add Subscription ```bash php artisan svea:subscription:add [--no-verify] ``` Adds a new subscription. Use `--no-verify` to skip automatic verification. ### List Subscriptions ```bash php artisan svea:subscription:list ``` Outputs a table of subscription details including ID, Callback URL, Verified status, and subscribed event types. ### Get Subscription ```bash php artisan svea:subscription:get {id} ``` Retrieves details for a specific subscription by its ID. ### Verify Subscription ```bash php artisan svea:subscription:verify {id} ``` Verifies a subscription, required after using `--no-verify` or updating the URL. ### Update Subscription ```bash php artisan svea:subscription:update {id} [--url=] [--events=] [--verify] ``` Updates an existing subscription. Can change the URL, events, or both. Use `--verify` to re-verify after URL change. ### Remove Subscription ```bash php artisan svea:subscription:remove {id} [--force] ``` Removes a subscription. Use `--force` to skip the confirmation prompt. ``` -------------------------------- ### Get Subscription Details Source: https://github.com/nordkit/svea/blob/main/README.md Retrieves the details for a specific subscription using its ID. ```bash php artisan svea:subscription:get {id} ``` -------------------------------- ### Svea SDK Entry Points Source: https://github.com/nordkit/svea/blob/main/CONTRIBUTING.md Demonstrates the two primary ways to initialize and use the Svea SDK: via a static facade in Laravel or by creating an explicit SveaClient instance for framework-agnostic use. ```APIDOC ## Svea SDK Entry Points ### Laravel Facade ```php // Laravel — static facade (zero config, reads config/svea.php) Svea::checkout()->create(...); ``` ### Framework-Agnostic Instance ```php // Framework-agnostic — explicit instance (portable, testable, multi-merchant) $svea = new SveaClient([ 'merchant_id' => 'abc', 'shared_secret' => 'xyz', 'environment' => 'test', ]); $svea->checkout->create(...); $svea->admin->order('12345678')->deliver(); ``` **Note:** The `SveaClient` provides typed service properties like `->checkout`, `->admin`, `->subscriptions`, and `->webhook`, which are lazily resolved on first access. ``` -------------------------------- ### Get Order Details Source: https://github.com/nordkit/svea/blob/main/README.md Retrieves detailed information about a specific order. ```APIDOC ## Get Order Details ### Description Retrieves detailed information about a specific order. ### Method GET ### Endpoint `/admin/order/{orderId}` ### Parameters #### Path Parameters - **orderId** (string) - Required - The ID of the order to retrieve. ### Response #### Success Response (200 OK) - **status** (string) - The current status of the order (e.g., 'Open', 'Delivered', 'Cancelled'). - **actions** (array) - A list of available actions for the order (e.g., ['CanDeliverOrder', 'CanCancelOrder']). - **canDeliver** (bool) - Indicates if the order can be delivered. - **canCredit** (bool) - Indicates if the order can be credited. - **canCancel** (bool) - Indicates if the order can be cancelled. - **deliveries** (object) - An object containing all deliveries associated with the order, keyed by delivery ID. - **delivery(deliveryId)** (object|null) - Specific delivery details for a given delivery ID. - **deliveryRowIds(deliveryId)** (array) - Row IDs belonging to a specific delivery. - **hasAction(actionName)** (bool) - Checks if a specific action is available for the order. - **hasStatus(statusName)** (bool) - Checks if the order has a specific status. ### Response Example ```json { "status": "Open", "actions": ["CanDeliverOrder", "CanCancelOrder"], "canDeliver": true, "canCredit": false, "canCancel": true, "deliveries": { "456": { "deliveryId": 456, "rows": [...] } } } ``` ``` -------------------------------- ### SveaConnector::get Source: https://github.com/nordkit/svea/blob/main/CONTRIBUTING.md Sends a GET request to the specified path. The response is wrapped and processed for potential exceptions. ```APIDOC ## GET /path ### Description Retrieves data from a specific API endpoint using the GET method. This method is used for fetching resources. ### Method GET ### Endpoint /{path} ### Parameters #### Path Parameters - **path** (string) - Required - The API endpoint path. ### Response #### Success Response (200) - Returns the raw JSON array from the API response. #### Response Example ```json { "example": "response body" } ``` ``` -------------------------------- ### Register All Subscription Events Source: https://github.com/nordkit/svea/blob/main/README.md Use the `svea:subscription:add` Artisan command to register for all event types using the default callback URL configured in your `.env` file. ```bash php artisan svea:subscription:add ``` -------------------------------- ### Manage Svea Subscriptions Source: https://github.com/nordkit/svea/blob/main/README.md Perform get, list, update, and remove operations on subscriptions. Note that URL changes for subscriptions require re-verification. ```php $subscription = Svea::subscriptions()->get('sub-id'); $subscriptions = Svea::subscriptions()->list(); // array // Update URL or events — URL change requires re-verification $updated = Svea::subscriptions()->update( 'sub-id', 'https://myapp.com/webhooks/svea-new', [EventType::CheckoutOrderCreated] ); Svea::subscriptions()->verify('sub-id'); // required after URL change Svea::subscriptions()->remove('sub-id'); ``` -------------------------------- ### Run Pre-release Checks Locally Source: https://github.com/nordkit/svea/blob/main/RELEASING.md Execute these commands from the packages/svea/ directory to ensure all tests and static analysis pass before proceeding with the release. All checks must return zero failures or errors. ```bash # From packages/svea/ vendor/bin/pest --compact # 0 failures, 0 errors vendor/bin/pint --test # 0 violations vendor/bin/phpstan analyse src --memory-limit=512M # No errors ``` -------------------------------- ### Get Svea Checkout Order Source: https://context7.com/nordkit/svea/llms.txt Retrieves an existing checkout order by its ID. Handles potential 'Not Found' exceptions if the order does not exist. ```php use Svea\Checkout\CheckoutOrder; use Svea\Checkout\OrderRow; use Svea\Exceptions\SveaNotFoundException; // Get try { $order = Svea::checkout()->get('12345678'); $order->id(); // '12345678' $order->status(); // 'Created' | 'Cancelled' | 'Final' $order->snippet(); // '
...
' } catch (SveaNotFoundException $e) { // 404 — order not found } ``` -------------------------------- ### Get Checkout Order Source: https://github.com/nordkit/svea/blob/main/README.md Retrieves an existing checkout order by its ID. The returned order object provides methods to access its ID, status, and snippet. ```php $order = Svea::checkout()->get('12345678'); $order->id(); // '12345678' $order->status(); // 'Created' | 'Cancelled' | 'Final' $order->snippet(); // '
...
' ``` -------------------------------- ### Initialize SveaConnector Source: https://github.com/nordkit/svea/blob/main/CONTRIBUTING.md Instantiates the SveaConnector with configuration, base URL, and an optional Guzzle HandlerStack. Includes a retry middleware based on max_retries configuration. ```php $config */ public function __construct(private readonly array $config, string $baseUrl, ?HandlerStack $handlerStack = null) { $stack = $handlerStack ?? HandlerStack::create(); $stack->push(RetryMiddleware::make($config['max_retries'] ?? 0)); $this->client = new Client([ 'base_uri' => rtrim($baseUrl, '/') . '/', 'timeout' => $config['timeout'] ?? 10, 'handler' => $stack, ]); } /** * @param array $data * @return array */ public function post(string $path, array $data = [], ?string $idempotencyKey = null): array { return $this->send('POST', $path, $data, $idempotencyKey); } /** @return array */ public function get(string $path): array { return $this->send('GET', $path); } /** * @param array $data * @return array */ private function send(string $method, string $path, array $data = [], ?string $idempotencyKey = null): array { $body = $data ? json_encode($data) : ''; $headers = array_filter([ 'Authorization' => $this->buildAuthHeader((string) $body), 'Content-Type' => 'application/json', 'Idempotency-Key' => $idempotencyKey, ]); try { $response = $this->client->request($method, $path, [ 'headers' => $headers, 'body' => $body, ]); } catch (ConnectException $e) { throw new SveaConnectionException($e->getMessage(), previous: $e); } $sveaResponse = new SveaResponse($response); $this->throwForStatus($sveaResponse); return $sveaResponse->json; } /** * Build the Authorization header. * Format: SveaCheckoutGateway {merchantId} {base64(sha512(body + sharedSecret))} */ private function buildAuthHeader(string $body): string { $digest = base64_encode(hash('sha512', $body . $this->config['shared_secret'], binary: true)); return "SveaCheckoutGateway {$this->config['merchant_id']} {$digest}"; } private function throwForStatus(SveaResponse $response): void { if ($response->successful()) { return; } throw match (true) { $response->statusCode === 401 => new SveaAuthenticationException($response), $response->statusCode === 404 => new SveaNotFoundException($response), $response->statusCode === 429 => new SveaRateLimitException($response), default => new SveaApiException($response), }; } } ``` -------------------------------- ### Configure Svea SDK Source: https://context7.com/nordkit/svea/llms.txt Configure the Svea SDK by setting merchant credentials, environment, webhook secret, and API base URLs in the configuration file or .env. ```php return [ 'merchant_id' => env('SVEA_MERCHANT_ID'), 'shared_secret' => env('SVEA_SHARED_SECRET'), 'environment' => env('SVEA_ENVIRONMENT', 'test'), // 'test' | 'production' 'webhook_secret' => env('SVEA_WEBHOOK_SECRET'), 'subscription_callback_url' => env('SVEA_SUBSCRIPTION_CALLBACK_URL'), 'max_retries' => env('SVEA_MAX_RETRIES', 0), 'timeout' => env('SVEA_TIMEOUT', 10), 'base_urls' => [ 'checkout' => env('SVEA_CHECKOUT_URL'), 'admin' => env('SVEA_ADMIN_URL'), 'subscriptions' => env('SVEA_SUBSCRIPTIONS_URL'), ], ]; ``` -------------------------------- ### Checkout Order Get Source: https://github.com/nordkit/svea/blob/main/README.md Retrieves an existing Svea Checkout order by its ID. Returns an order object with methods to access its ID, status, and checkout snippet. ```APIDOC ## Get Checkout Order ### Description Retrieves an existing Svea Checkout order using its unique ID. The response is an order object that provides access to the order's ID, current status (e.g., 'Created', 'Cancelled', 'Final'), and the checkout snippet if available. ### Method ```php Svea::checkout()->get(string $id) ``` ### Parameters #### Path Parameters - **id** (string) - Required - The unique identifier of the order to retrieve. ### Response Example ```php $order = Svea::checkout()->get('12345678'); $order->id(); // '12345678' $order->status(); // 'Created' | 'Cancelled' | 'Final' $order->snippet(); // '
...
' ``` ``` -------------------------------- ### Helper for SveaClient in Tests Source: https://github.com/nordkit/svea/blob/main/CONTRIBUTING.md Creates a pre-configured SveaClient instance for unit tests, allowing for configuration overrides. ```php 'test-merchant', 'shared_secret' => 'test-secret', 'environment' => 'test', 'webhook_secret' => 'webhook-secret', 'max_retries' => 0, 'timeout' => 5, ], $overrides)); } ``` -------------------------------- ### Get, Update, Cancel Order Source: https://context7.com/nordkit/svea/llms.txt Provides functionality to retrieve an existing checkout order by its ID, update its contents by adding new order rows, or cancel the order entirely. ```APIDOC ## CheckoutService — Get, Update, Cancel Retrieves an existing checkout order, updates its contents, or cancels it entirely. ### Get Order ```php use Svea\Checkout\CheckoutOrder; use Svea\Exceptions\SveaNotFoundException; try { $order = Svea::checkout()->get('12345678'); $order->id(); // '12345678' $order->status(); // 'Created' | 'Cancelled' | 'Final' $order->snippet(); // '
...
' } catch (SveaNotFoundException $e) { // 404 — order not found } ``` ### Update Order ```php use Svea\Checkout\CheckoutOrder; use Svea\Checkout\OrderRow; // Update — same named/fluent API as create(), set only changed fields $order = Svea::checkout()->update('12345678', function (CheckoutOrder $order) use ($extraItem) { $order->addRow(fn (OrderRow $row) => $row ->sku($extraItem->sku) ->name($extraItem->name) ->quantity(100) ->unitPrice(5000) ->vatPercent(2500)); }); $order->id(); // '12345678' $order->status(); // 'Created' | 'Cancelled' | 'Final' ``` ### Cancel Order ```php // Cancel — returns void Svea::checkout()->cancel('12345678'); ``` ``` -------------------------------- ### Standalone SveaClient Initialization Source: https://github.com/nordkit/svea/blob/main/README.md Instantiate `SveaClient` directly with an array of configuration options for standalone usage outside of a Laravel framework. This requires providing merchant ID, shared secret, environment, and webhook secret. ```php use Svea\SveaClient; $svea = new SveaClient([ 'merchant_id' => 'abc', 'shared_secret' => 'xyz', 'environment' => 'test', 'webhook_secret' => 'whsec_...', ]); $svea->checkout->create(...); $svea->admin->order('12345678')->deliver(); ``` -------------------------------- ### AdminService: Get Order Details Source: https://context7.com/nordkit/svea/llms.txt Fetches full order details from the Payment Admin API. Handles potential API exceptions and provides access to order status, actions, and deliveries. ```php use Svea\Exceptions\SveaNotFoundException; use Svea\Exceptions\SveaApiException; try { $adminOrder = Svea::admin()->order('12345678')->get(); } catch (SveaNotFoundException $e) { // order not found } catch (SveaApiException $e) { echo $e->statusCode; // e.g. 403 echo $e->sveaMessage; // Svea error description } $adminOrder->status(); // SveaOrderStatus enum: Open|Delivered|Cancelled|Final $adminOrder->actions(); // ['CanDeliverOrder', 'CanCancelOrder', ...] $adminOrder->canDeliver(); // bool $adminOrder->canCredit(); // bool $adminOrder->canCancel(); // bool $adminOrder->deliveries(); // array> $adminOrder->delivery(456); // array|null — specific delivery by ID $adminOrder->deliveryRowIds(456); // int[] — row IDs on delivery 456 $adminOrder->hasAction('CanDeliverOrder'); // bool $adminOrder->hasStatus('Open'); // bool // Gate an operation before attempting it if ($adminOrder->canDeliver()) { Svea::admin()->order('12345678')->deliver(); } ``` -------------------------------- ### Instantiate SveaClient Standalone Source: https://context7.com/nordkit/svea/llms.txt Instantiate SveaClient directly for framework-agnostic usage. Provide configuration options such as merchant ID, shared secret, environment, and webhook secret. The client supports retries on specific HTTP errors and has a configurable timeout. ```php use Svea\SveaClient; $svea = new SveaClient([ 'merchant_id' => 'abc123', 'shared_secret' => 'my-shared-secret', 'environment' => 'test', // 'test' | 'production' 'webhook_secret' => 'whsec_...', 'max_retries' => 2, // retry on 429/500/503 with exponential backoff 'timeout' => 15, ]); // All four surfaces are available: $svea->checkout()->create(...); $svea->admin()->order('12345678')->deliver(); $svea->subscriptions()->list(); $svea->webhook()->fromRequest($request); // Property-style (convenience alias): $svea->checkout->create(...); ``` -------------------------------- ### Get Order Details Source: https://github.com/nordkit/svea/blob/main/README.md Retrieve comprehensive details about an order. The response object provides access to the order's status, available actions, delivery information, and allows checking for specific actions or statuses. ```php $adminOrder = Svea::admin()->order('12345678')->get(); ``` ```php $adminOrder->status(); // SveaOrderStatus enum $adminOrder->actions(); // string[] — e.g. ['CanDeliverOrder', 'CanCancelOrder'] $adminOrder->canDeliver(); // bool $adminOrder->canCredit(); // bool $adminOrder->canCancel(); // bool $adminOrder->deliveries(); // array> — all deliveries on the order $adminOrder->delivery(456); // array|null — specific delivery by ID $adminOrder->deliveryRowIds(456); // int[] — row IDs belonging to delivery 456 (useful before crediting) $adminOrder->hasAction('CanDeliverOrder'); // bool — check any action string $adminOrder->hasStatus('Open'); // bool — check status string directly ``` -------------------------------- ### Svea Configuration File (`config/svea.php`) Source: https://github.com/nordkit/svea/blob/main/README.md This PHP array defines the Svea configuration, reading values from environment variables. Defaults are provided for environment and retry/timeout settings. ```php return [ 'merchant_id' => env('SVEA_MERCHANT_ID'), 'shared_secret' => env('SVEA_SHARED_SECRET'), 'environment' => env('SVEA_ENVIRONMENT', 'test'), // 'test' | 'production' 'webhook_secret' => env('SVEA_WEBHOOK_SECRET'), 'subscription_callback_url' => env('SVEA_SUBSCRIPTION_CALLBACK_URL'), 'max_retries' => env('SVEA_MAX_RETRIES', 0), 'timeout' => env('SVEA_TIMEOUT', 10), // Override base URLs per API surface — useful for pointing at a local mock server. // When null the built-in environment defaults are used. 'base_urls' => [ 'checkout' => env('SVEA_CHECKOUT_URL'), // default: https://checkoutapistage.svea.com (test) 'admin' => env('SVEA_ADMIN_URL'), // default: https://paymentadminapistage.svea.com (test) 'subscriptions' => env('SVEA_SUBSCRIPTIONS_URL'), // default: https://paymentadminapistage.svea.com (test) ], ]; ``` -------------------------------- ### Get Order Details Source: https://context7.com/nordkit/svea/llms.txt Fetches full order details from the Payment Admin API, including status, available actions, and all deliveries. This method allows retrieval of comprehensive order information for further processing or display. ```APIDOC ## AdminService — Get Order Details ### Description Fetches full order details from the Payment Admin API, including status, available actions, and all deliveries. ### Method GET (implied by SDK usage) ### Endpoint /api/v1/orders/{orderId} ### Parameters #### Path Parameters - **orderId** (string) - Required - The unique identifier of the order. ### Request Example ```php use Svea\Exceptions\SveaNotFoundException; use Svea\Exceptions\SveaApiException; try { $adminOrder = Svea::admin()->order('12345678')->get(); } catch (SveaNotFoundException $e) { // order not found } catch (SveaApiException $e) { echo $e->statusCode; // e.g. 403 echo $e->sveaMessage; // Svea error description } ``` ### Response #### Success Response (200) - **status** (SveaOrderStatus enum) - The current status of the order. - **actions** (array) - A list of available actions for the order. - **deliveries** (array>) - Details of all deliveries associated with the order. ### Response Example ```php $adminOrder->status(); // SveaOrderStatus enum: Open|Delivered|Cancelled|Final $adminOrder->actions(); // ['CanDeliverOrder', 'CanCancelOrder', ...] $adminOrder->canDeliver(); // bool $adminOrder->canCredit(); // bool $adminOrder->canCancel(); // bool $adminOrder->deliveries(); // array> $adminOrder->delivery(456); // array|null — specific delivery by ID $adminOrder->deliveryRowIds(456); // int[] — row IDs on delivery 456 $adminOrder->hasAction('CanDeliverOrder'); // bool $adminOrder->hasStatus('Open'); // bool ``` ``` -------------------------------- ### Create a Checkout Order with Svea PHP SDK Source: https://github.com/nordkit/svea/blob/main/README.md Instantiate a CheckoutOrder with necessary details like currency, country code, merchant settings, and cart items. Numeric values for quantity, unit price, and VAT percent follow Svea's minor-unit convention. The returned order object provides its ID, embeddable snippet, and status. ```php use Svea\Checkout\Cart; use Svea\Checkout\CheckoutOrder; use Svea\Checkout\MerchantSettings; use Svea\Checkout\OrderRow; $order = Svea::checkout()->create(new CheckoutOrder( currency: 'SEK', countryCode: 'SE', locale: 'sv-SE', clientOrderNumber: 'ORD-001', merchantSettings: new MerchantSettings( pushUri: route('webhooks.svea'), termsUri: route('terms'), confirmationUri: route('checkout.confirmation'), checkoutUri: route('checkout'), ), cart: new Cart([ new OrderRow(quantity: 100, unitPrice: 29900, vatPercent: 2500, sku: 'TSHIRT-BLK-M', name: 'T-Shirt Black M'), new OrderRow(quantity: 200, unitPrice: 89900, vatPercent: 2500, sku: 'SNEAKER-WHT-42', name: 'Sneakers White 42'), ]), )); $order->id(); // '12345678' — store this as your Svea order ID $order->snippet(); // '' — embed in your checkout page $order->status(); // 'Created' | 'Final' | 'Cancelled' ``` -------------------------------- ### Create Svea Checkout Order (Named Constructor) Source: https://context7.com/nordkit/svea/llms.txt Use this method when all order details are available upfront. It constructs a CheckoutOrder object with all necessary parameters before creating the order. ```php use Svea\Checkout\Cart; use Svea\Checkout\CheckoutOrder; use Svea\Checkout\MerchantSettings; use Svea\Checkout\OrderRow; use Svea\Exceptions\SveaApiException; // Named-constructor style — all data available upfront try { $order = Svea::checkout()->create(new CheckoutOrder( currency: 'SEK', countryCode: 'SE', locale: 'sv-SE', clientOrderNumber: 'ORD-001', merchantSettings: new MerchantSettings( pushUri: route('webhooks.svea'), termsUri: route('terms'), confirmationUri: route('checkout.confirmation'), checkoutUri: route('checkout'), ), cart: new Cart([ new OrderRow(quantity: 100, unitPrice: 29900, vatPercent: 2500, sku: 'TSHIRT-BLK-M', name: 'T-Shirt Black M'), new OrderRow(quantity: 200, unitPrice: 89900, vatPercent: 2500, sku: 'SNEAKER-WHT-42', name: 'Sneakers White 42'), ]), )); } catch (SveaApiException $e) { // $e->statusCode, $e->sveaCode, $e->sveaMessage throw $e; } $order->id(); // '12345678' — store as your Svea order ID $order->snippet(); // '
...
' — embed in checkout page $order->status(); // 'Created' | 'Final' | 'Cancelled' $order->successful(); // bool $order->getLastResponse()->statusCode; // 201 ``` -------------------------------- ### Register Subscription using Fluent Builder Source: https://github.com/nordkit/svea/blob/main/README.md This fluent builder approach allows chaining subscription configuration methods. The `register()` method automatically handles both registration and verification. ```APIDOC ## Register Subscription using Fluent Builder ### Description This method provides a fluent interface for configuring and registering a subscription. It allows chaining calls to specify event types and the callback URL. The `register()` method implicitly calls `verify()` after successful registration. ### Method POST (implied by `register` operation) ### Endpoint `/subscriptions` (implied) ### Parameters #### Method Chaining - **on(...EventType)** - Specifies the event types for the subscription. - **notifyAt(string callbackUrl)** - Sets the callback URL for the subscription. ### Request Example ```php $subscription = Svea::subscriptions() ->on(EventType::CheckoutOrderCreated, EventType::CheckoutOrderDelivered) ->notifyAt('https://myapp.com/webhooks/svea') ->register(); // registers and verifies ``` ### Response #### Success Response (200) - **id** (string) - The unique identifier for the newly created subscription. - **callbackUrl** (string) - The registered callback URL. - **events** (array of EventType) - The list of event types subscribed to. - **isVerified** (bool) - Indicates if the subscription is currently verified. - **createdAt** (DateTimeImmutable|null) - The timestamp when the subscription was created. ### Response Example ```json { "id": "fbb6c74a-abcd-1234-efgh-0123456789ab", "callbackUrl": "https://myapp.com/webhooks/svea", "events": [ "CheckoutOrderCreated", "CheckoutOrderDelivered" ], "isVerified": true, "createdAt": "2023-10-27T10:00:00Z" } ``` ``` -------------------------------- ### PHP Package File Structure Source: https://github.com/nordkit/svea/blob/main/NAMING.md Overview of the directory and file structure for the nordkit/svea PHP package. ```php src/ ├── SveaClient.php # Main entry point — lazily resolves services ├── SveaResource.php # Base class for all API response objects │ ├── Admin/ │ ├── AdminService.php # order(string $id): AdminOrderRequest, task(string $url): TaskResponse │ ├── AdminOrderRequest.php # Fluent builder: get, deliver, cancel, add/update/replace rows │ ├── AdminOrderResponse.php # Typed response: status(), canDeliver(), deliveryRowIds(), etc. │ ├── AdminDeliveryRequest.php # Delivery-scoped: credit(), creditAmount() │ ├── AdminOrderRow.php # Fluent row builder: name, quantity, unitPrice, vatPercent, etc. │ ├── CreditRequest.php # Fluent refund builder: rows(), newRow(), send() │ ├── TaskResponse.php # Async task resource: reference(), completed(), failed() │ └── SveaOrderStatus.php # Enum: Open, Delivered, Cancelled, Final │ ├── Checkout/ │ ├── CheckoutService.php # create(), get(), update() │ ├── CheckoutOrder.php # Fluent order builder: currency, locale, addRow, merchantSettings │ └── OrderRow.php # Fluent checkout row builder │ ├── Exceptions/ │ ├── SveaApiException.php # 4xx/5xx non-specific errors │ ├── SveaAuthenticationException.php # 401 responses │ ├── SveaConnectionException.php # Transport-level failures (ConnectException) │ ├── SveaNotFoundException.php # 404 responses │ └── SveaRateLimitException.php # 429 responses │ ├── Laravel/ │ ├── SveaServiceProvider.php # Singleton binding, Wiretap integration, config publish │ └── Facades/ │ └── Svea.php # Facade → resolves Svea\SveaClient │ ├── Subscriptions/ │ └── SubscriptionService.php # register(), list(), get(), delete() │ ├── Support/ │ └── Conditionable.php # Trait: when() / unless() for builder chains │ ├── Testing/ │ ├── FakeSveaClient.php # fake() named constructor; fakeCheckout(), fakeAdmin(), fakeSubscriptions() │ ├── FakeCheckoutService.php # Records calls, seeds responses, assertion helpers │ ├── FakeAdminService.php # order() returns FakeAdminOrderRequest │ ├── FakeAdminOrderRequest.php # Mirrors AdminOrderRequest for tests │ ├── FakeAdminDeliveryRequest.php # Mirrors AdminDeliveryRequest for tests │ ├── FakeCreditRequest.php # Mirrors CreditRequest for tests │ ├── FakeSubscriptionService.php # Records calls, seeds responses │ └── SveaFakeAssertions.php # Shared: assertCalled(), assertNotCalled(), assertCalledTimes(), preventStrayRequests() │ ├── Transport/ │ ├── SveaConnector.php # HTTP transport: HMAC auth, retry, idempotency key, error mapping │ ├── SveaResponse.php # Wraps PSR-7: ->json, ->headers, ->statusCode, ->successful() │ └── RetryMiddleware.php # Exponential backoff on 429 / 5xx │ └── Webhooks/ └── WebhookService.php # verify(string $payload, string $signature): bool ``` -------------------------------- ### Retry Logic Configuration Source: https://github.com/nordkit/svea/blob/main/CONTRIBUTING.md This comment block outlines the retry strategy for network requests, including conditions for retrying (e.g., `ConnectException`, specific HTTP status codes) and the delay formula with jitter. It provides an example of retry timing based on the attempt number. ```comment # Retry on: ConnectException, 429, 500, 503 # Delay formula: min(1000 * 2^attempt, 32000) ms + random 0–1000 ms jitter # Example with max_retries=2: attempt 1 → ~2 s, attempt 2 → ~4 s ``` -------------------------------- ### Register Webhook Subscriptions with Svea Source: https://context7.com/nordkit/svea/llms.txt Manage Svea webhook subscriptions to receive order lifecycle events. Use the fluent builder for registration and auto-verification, or the direct add() method. Remember to verify subscriptions, especially after updating the callback URL. ```php use Svea\Subscriptions\EventType; // Fluent builder — registers and auto-verifies $subscription = Svea::subscriptions() ->on(EventType::CheckoutOrderCreated, EventType::CheckoutOrderDelivered) ->notifyAt('https://myapp.com/webhooks/svea') ->register(); ``` ```php // Direct add() method $subscription = Svea::subscriptions()->add( callbackUrl: 'https://myapp.com/webhooks/svea', eventTypes: [ EventType::CheckoutOrderCreated, EventType::CheckoutOrderDelivered, EventType::CheckoutOrderCreditSucceeded, EventType::CheckoutOrderCreditFailed, EventType::CheckoutOrderClosed, ], ); ``` ```php // Verify (required before Svea delivers events; Svea sends a Ping to your URL) Svea::subscriptions()->verify($subscription->id()); ``` ```php // Inspect subscription $subscription->id(); // 'fbb6c74a-...' $subscription->callbackUrl(); // 'https://myapp.com/webhooks/svea' $subscription->events(); // EventType[] $subscription->isVerified(); // bool $subscription->createdAt(); // \DateTimeImmutable|null ``` ```php // List all subscriptions $subscriptions = Svea::subscriptions()->list(); // array ``` ```php // Get by ID $subscription = Svea::subscriptions()->get('fbb6c74a-...'); ``` ```php // Update URL or events (URL change requires re-verification) $updated = Svea::subscriptions()->update( 'fbb6c74a-...', 'https://myapp.com/webhooks/svea-new', [EventType::CheckoutOrderCreated] ); Svea::subscriptions()->verify('fbb6c74a-...'); // required after URL change ``` ```php // Remove Svea::subscriptions()->remove('fbb6c74a-...'); ``` ```php // All 10 available event types: // EventType::CheckoutOrderCreated // EventType::CheckoutOrderUpdated // EventType::CheckoutOrderDelivered // EventType::CheckoutOrderCreditSucceeded // EventType::CheckoutOrderCreditFailed // EventType::CheckoutOrderClosed // EventType::CheckoutOrderPendingStatusReleased // EventType::StandaloneOrderPendingStatusReleased // EventType::StandaloneOrderClosed // EventType::Ping (verification ping — handle, don't subscribe) ``` -------------------------------- ### Pest Test Bootstrap Source: https://github.com/nordkit/svea/blob/main/CONTRIBUTING.md Configuration for Pest test suite, specifying which directories to run tests in. ```php in('Unit', 'Integration'); ``` -------------------------------- ### Webhook::constructEvent() Source: https://github.com/nordkit/svea/blob/main/CONTRIBUTING.md Details the `Webhook::constructEvent()` static method, highlighting its independence from dependency injection and its exception handling for signature verification. ```APIDOC ## `Webhook::constructEvent()` Pure static method — no dependency injection, no framework, works in any PHP context. Throws `SignatureVerificationException` on HMAC mismatch. ``` -------------------------------- ### List Subscriptions Source: https://github.com/nordkit/svea/blob/main/README.md Lists all current subscriptions, showing their ID, callback URL, verification status, and subscribed events. ```bash php artisan svea:subscription:list ``` -------------------------------- ### Create Checkout Order (Fluent Callback) Source: https://github.com/nordkit/svea/blob/main/README.md Creates a new checkout order using a fluent callback style, ideal for dynamic order creation, loops, or conditional rows. Numeric values must adhere to Svea's minor-unit convention. ```php $order = Svea::checkout()->create(function (CheckoutOrder $order) use ($cart) { $order ->currency('SEK') ->countryCode('SE') ->locale('sv-SE') ->clientOrderNumber($cart->reference) ->merchantSettings(fn (MerchantSettings $s) => $s ->pushUri(route('webhooks.svea')) ->termsUri(route('terms')) ->confirmationUri(route('checkout.confirmation')) ->checkoutUri(route('checkout'))); foreach ($cart->items as $item) { $order->addRow(fn (OrderRow $row) => $row ->sku($item->sku) ->name($item->name) ->quantity($item->qty * 100) // minor units: 100 = 1 unit ->unitPrice($item->unit_price) // incl. VAT, minor currency (öre) ->vatPercent($item->vat_percent) // minor units: 2500 = 25% ->unit('st')); } $order->when($cart->has_discount, fn ($o) => $o->addRow( fn (OrderRow $r) => $r->sku('DISC')->name('Discount')->unitPrice(-500)->quantity(100)->vatPercent(2500) )); }); ``` -------------------------------- ### Core SDK Directory Structure Source: https://github.com/nordkit/svea/blob/main/CONTRIBUTING.md Illustrates the directory layout for the core SDK, distinguishing between pure PHP components and optional Laravel integrations. ```plaintext src/ SveaClient.php ← pure PHP SveaResource.php ← pure PHP base class Support/ Conditionable.php ← tiny custom trait, no illuminate Laravel/ SveaServiceProvider.php ← illuminate/support only Svea.php ← facade Checkout/ Admin/ Subscriptions/ Webhooks/ Transport/ Exceptions/ Testing/ ``` -------------------------------- ### Update Subscription URL and Verify Source: https://github.com/nordkit/svea/blob/main/README.md Updates the subscription's callback URL and initiates the verification process in a single step. ```bash # Change URL and re-verify in one step php artisan svea:subscription:update {id} --url=https://new.myapp.com/... --verify ``` -------------------------------- ### Configure Svea Environment Variables Source: https://github.com/nordkit/svea/blob/main/README.md Add these variables to your `.env` file to configure the Svea integration. Ensure `SVEA_MERCHANT_ID`, `SVEA_SHARED_SECRET`, `SVEA_ENVIRONMENT`, and `SVEA_WEBHOOK_SECRET` are set. ```dotenv SVEA_MERCHANT_ID=your_merchant_id SVEA_SHARED_SECRET=your_shared_secret SVEA_ENVIRONMENT=test SVEA_WEBHOOK_SECRET=your_webhook_secret SVEA_SUBSCRIPTION_CALLBACK_URL=your_callback_url SVEA_MAX_RETRIES=0 SVEA_TIMEOUT=10 SVEA_CHECKOUT_URL= SVEA_ADMIN_URL= SVEA_SUBSCRIPTIONS_URL= ``` -------------------------------- ### Checkout Order Creation (Fluent Callback) Source: https://github.com/nordkit/svea/blob/main/README.md Creates a Svea Checkout order using a fluent callback style, which is more flexible for dynamic order building, such as within loops or conditional logic. It allows for composable order row additions. ```APIDOC ## Create Checkout Order (Fluent Callback) ### Description Creates a Svea Checkout order using a fluent callback style. This approach is beneficial for dynamically building orders, especially within loops or conditional statements. It allows for a more composable way to add order rows and configure order settings. ### Method ```php Svea::checkout()->create(function (CheckoutOrder $order) { ... }) ``` ### Parameters #### `CheckoutOrder` Callback Within the callback, you can chain methods on the `$order` object: - **currency(string $currency)**: Sets the order currency. - **countryCode(string $code)**: Sets the country code. - **locale(string $locale)**: Sets the locale. - **clientOrderNumber(string $number)**: Sets the client order number. - **merchantSettings(callable $callback)**: Configures merchant settings using a nested callback. - Inside the `merchantSettings` callback, configure URIs like `pushUri`, `termsUri`, `confirmationUri`, `checkoutUri`. - **addRow(callable $callback)**: Adds an order row using a nested callback. - Inside the `addRow` callback, configure `sku`, `name`, `quantity`, `unitPrice`, `vatPercent`, `unit` for the order row. - **when(bool $condition, callable $callback)**: Conditionally adds an order row or applies settings if the condition is true. #### Optional Chained Methods (same as Named Constructor) - **merchantData(string $data)** - **partnerKey(string $key)** - **recurring()** - **requireElectronicIdAuthentication()** - **metadata(array $metadata)** ### Response Example ```php $order = Svea::checkout()->create(function (CheckoutOrder $order) use ($cart) { $order ->currency('SEK') ->countryCode('SE') ->locale('sv-SE') ->clientOrderNumber($cart->reference) ->merchantSettings(fn (MerchantSettings $s) => $s ->pushUri(route('webhooks.svea')) ->termsUri(route('terms')) ->confirmationUri(route('checkout.confirmation')) ->checkoutUri(route('checkout'))); foreach ($cart->items as $item) { $order->addRow(fn (OrderRow $row) => $row ->sku($item->sku) ->name($item->name) ->quantity($item->qty * 100) // minor units: 100 = 1 unit ->unitPrice($item->unit_price) // incl. VAT, minor currency (öre) ->vatPercent($item->vat_percent) // minor units: 2500 = 25% ->unit('st')); } $order->when($cart->has_discount, fn ($o) => $o->addRow( fn (OrderRow $r) => $r->sku('DISC')->name('Discount')->unitPrice(-500)->quantity(100)->vatPercent(2500) )); }); ``` ```