Aller au contenu

omnitrade/shopify Référence de l’API

Étage
Fournisseur · Omnitrade
Version
1.x
Suite de conformité
branchée, CI non lue
Vérifié en réel
Son README ne le dit pas
Licence
MIT
composer require omnitrade/shopify:1.x-dev

Lue dans les fichiers PHP du paquet au commit 7a76c621b953 : ses espaces de noms, classes, interfaces et énumérations, leurs constantes, propriétés et méthodes publiques avec leur PHPDoc, et les options que prennent ses fabriques. Rien n’en est exécuté.

Omnitrade\Shopify

  • Api class

    One shop's APIs, for the gateway's actions.

    1 method · 3 properties

  • Documents class

    The GraphQL documents the actions send.

    10 constants

  • Products class

    Shopify's two product shapes, read as one Product.

    4 methods

  • ShopifyGatewayFactory class

    Shopify, a commerce platform: a sale paid on the shop's invoice page (a draft order), the shop's orders, its catalogue (products, variants, inventory) and its webhooks.

    16 options

Omnitrade\Shopify\Action

  • FetchInventoryAction class

    The variants' inventory: inventoryQuantity (summed over the locations) when inventoryItem.tracked, else a null quantity. The variants by their gids (or bare ids) through nodes(ids:), 250 at a time; none given, every variant of the shop through productVariants, page by page.

    3 methods

  • FetchOrderAction class

    A Shopify order, by its gid or its bare id.

    4 methods

  • FetchProductAction class

    One product: by its gid, its bare numeric id, its handle, or the address of a page of it - the storefront's /products/<handle> (the handle is the last segment) or the admin's /admin/products/<id>. Null when the shop has no such product.

    4 methods

  • FetchProductsAction class

    A page of the shop's products, by the Admin GraphQL products(...) query, the least recently updated first: the cursor is Shopify's endCursor, updatedSince becomes the search filter updated_at:>'...' and the query is appended to it as Shopify's own search text. Pass the same updatedSince and query with the cursor: Shopify's cursor is only a position.

    4 methods

  • FetchTransactionAction class

    What became of a draft order: completed into a paid order (PAID), deleted on Shopify (CANCELLED - the sale is not happening), or still open (PENDING).

    3 methods

  • NotifyAction class

    A Shopify webhook, its HMAC and its shop checked. The order topics read as what they mean: orders/paid is PAID, orders/cancelled CANCELLED, refunds/create REFUNDED; the rest (fulfilments...) is handed back with no status, for the application to read from $raw.

    3 methods

  • PurchaseAction class

    A draft order, paid on Shopify's invoice page: the buyer is sent there (PENDING with the invoice URL), and Shopify's orders/paid webhook or fetch() says when they paid. A draft order's lines take the prices they are given - a line with a variant gid as its reference names the variant, so Shopify counts its stock; any other is a custom line. The shipping and the discount are carried over whole. Shopify's checkout never redirects back: returnUrl is not used.

    4 methods

Omnitrade\Shopify\Api

  • AdminApi class

    The Admin GraphQL API of the configured shop: the client bound to one Endpoint.

    5 methods

  • Endpoint class

    One Shopify shop: where it is, which API version to speak, and its tokens.

    7 methods · 5 properties

  • GraphQL class

    POST a GraphQL document, get the `data` back or a ShopifyApiException.

    3 methods

  • Hmac class

    Shopify signs a webhook body with the app's API secret and sends the digest in X-Shopify-Hmac-Sha256: base64 of the raw binary hash, one value, no timestamp (X-Shopify-Triggered-At is outside the signature). Replay defence is the delivery id, X-Shopify-Webhook-Id, which the application keeps.

    2 methods

  • ShopifyApiException class

    Shopify said no, or said nothing. Carries the userErrors/errors it came with; never the access token (the headers are built at request time).

    2 methods · 1 property

Omnitrade\Shopify

Api class

final class Api Api.php:12

One shop's APIs, for the gateway's actions.

public readonly AdminApi $admin
public readonly Endpoint $endpoint
public readonly array $draftOrderTags

Api::__construct()

public function __construct(public readonly Endpoint $endpoint, HttpClientInterface $http, public readonly array $draftOrderTags = [], ?LoggerInterface $logger = null, float $retryDelay = 1.0)

Documents class

final class Documents Documents.php:6

The GraphQL documents the actions send.

const DRAFT_ORDER_CREATE = <<<'GRAPHQL' mutation DraftOrderCreate($input: DraftOrderInput!) { draftOrderCreate(input: $input) { draftOrder { id name invoiceUrl status totalPrice …
const DRAFT_ORDER = <<<'GRAPHQL' query DraftOrder($id: ID!) { draftOrder(id: $id) { id name status invoiceUrl totalPrice order { id name displayFinancialStatus display…
const ORDER = <<<'GRAPHQL' query Order($id: ID!) { order(id: $id) { id name createdAt displayFinancialStatus displayFulfillmentStatus totalPriceSet { shopMoney {…
const PRODUCT_FIELDS = <<<'GRAPHQL' fragment ProductFields on Product { id handle title descriptionHtml vendor productType status tags updatedAt onlineStoreUrl options { name o…

What the catalogue reads of a product: its texts, vendor, type, collections, options, metafields, media and variants (100 at most, Shopify's former limit - a product with more is read in part).

const PRODUCTS = <<<'GRAPHQL' query Products($first: Int!, $after: String, $query: String) { shop { currencyCode taxesIncluded } products(first: $first, after: $after, qu…

A page of products, the most recently updated last. The shop's currency and tax setting come along: the prices are in it. first: 50 by default rather than the permitted 250 - the query is costed by the nodes it can return (products x variants), and the cost bucket is waited for.

const PRODUCT = <<<'GRAPHQL' query Product($id: ID!) { shop { currencyCode taxesIncluded } product(id: $id) { ...ProductFields } } GRAPHQL . "\n" . self::PRODUCT_FIELDS
const SHOP = <<<'GRAPHQL' query Shop { shop { currencyCode taxesIncluded } } GRAPHQL

The shop's currency and tax setting, for a webhook's prices (they come bare).

const VARIANT_STOCKS = <<<'GRAPHQL' query VariantStocks($ids: [ID!]!) { nodes(ids: $ids) { ... on ProductVariant { id sku inventoryQuantity inventoryItem { id tracked } } }…

The variants' inventory, by their gids.

const STOCKS = <<<'GRAPHQL' query Stocks($first: Int!, $after: String) { productVariants(first: $first, after: $after) { pageInfo { hasNextPage endCursor } nodes …

Every variant's inventory, page by page.

const REFERENCE_KEY = 'omnitrade_reference'

The custom attribute both sides correlate on: the merchant's reference on the draft order.

Products class

final class Products Products.php:31

Shopify's two product shapes, read as one Product.

Shopify sends two different shapes for the same product and does not warn you:

fromGraphQL() what the products(...) and product(id:) queries return - "id" is a gid, variants are under variants.nodes, prices are decimal strings, the fields are camelCase. fromRest() what the products/create|update webhook POSTs - "id" is a bare number, "variants" is a flat array, the fields are snake_case, the tags one comma-separated string.

The webhook payload is REST-shaped even when the subscription was created through GraphQL, so both converge here, and the tests assert they give the same Product for the same product. Pure: arrays in, models out.

Products::fromGraphQL()

public static function fromGraphQL(array $node, string $currency, ?bool $taxIncluded = null): Product
$node

a products(...).nodes[] entry, or product(id:)

$taxIncluded

the shop's taxesIncluded

Products::fromRest()

public static function fromRest(array $payload, string $currency, ?bool $taxIncluded = null): Product
$payload

a products/create|update webhook body

Products::stock()

public static function stock(array $node): Stock

A variant's inventory, as VARIANT_STOCKS and STOCKS return it.

Products::status()

public static function status(mixed $status): string

"ACTIVE", "active" -> Product::ACTIVE; DRAFT, UNLISTED -> DRAFT; ARCHIVED.

ShopifyGatewayFactory class

final class ShopifyGatewayFactory extends GatewayFactory ShopifyGatewayFactory.php:35

Shopify, a commerce platform: a sale paid on the shop's invoice page (a draft order), the shop's orders, its catalogue (products, variants, inventory) and its webhooks.

options: shop_domain: '%env(SHOPIFY_SHOP_DOMAIN)%' # example.myshopify.com admin_token: '%env(SHOPIFY_ADMIN_TOKEN)%' # shpat_..., a custom app's Admin API token webhook_secret: '%env(SHOPIFY_WEBHOOK_SECRET)%' # the app's API secret, for notify() api_version: '2026-07' timeout: 15 draft_order_tags: [] # tags put on the draft orders it creates currency: null # the catalogue's prices' currency; null: the shop's retry_delay: 1 # seconds before retrying a 429 / 5xx (doubled each time, 3 attempts); a 429's Retry-After prevails

No authorizations, captures nor refunds from here: Shopify's own admin does those on its orders.

Options

OptionDefault
webhook_secretnull
api_version'2026-07'
timeout15
draft_order_tags[]
currencynull
retry_delay1
omnitrade.apifunction (Config $c) { $http = $this->http ?? HttpClient::create(); return new Api(new Endpoint($c['shop_domain'], $c['api_version'], $c['admin_token…
omnitrade.action.purchasenew PurchaseAction()
omnitrade.action.fetchnew FetchTransactionAction()
omnitrade.action.ordernew FetchOrderAction()
omnitrade.action.notifystatic fn(Config $c) => new NotifyAction($c['currency'] ?: null)
omnitrade.action.productsstatic fn(Config $c) => new FetchProductsAction($c['currency'] ?: null)
omnitrade.action.productstatic fn(Config $c) => new FetchProductAction($c['currency'] ?: null)
omnitrade.action.inventorynew FetchInventoryAction()
shop_domainrequired
admin_tokenrequired

Omnitrade\Shopify\Action

FetchInventoryAction class

final class FetchInventoryAction implements ActionInterface, ApiAwareInterface FetchInventoryAction.php:21

The variants' inventory: inventoryQuantity (summed over the locations) when inventoryItem.tracked, else a null quantity. The variants by their gids (or bare ids) through nodes(ids:), 250 at a time; none given, every variant of the shop through productVariants, page by page.

FetchInventoryAction::__construct()

public function __construct()

FetchInventoryAction::supports()

public function supports(Request $request): bool

FetchInventoryAction::execute()

public function execute(Request $request): void

FetchOrderAction class

final class FetchOrderAction implements ActionInterface, ApiAwareInterface FetchOrderAction.php:21

A Shopify order, by its gid or its bare id.

FetchOrderAction::__construct()

public function __construct()

FetchOrderAction::supports()

public function supports(Request $request): bool

FetchOrderAction::execute()

public function execute(Request $request): void

FetchOrderAction::order()

public static function order(array $order): PlatformOrder
$order

as Documents::ORDER returns it

FetchProductAction class

final class FetchProductAction implements ActionInterface, ApiAwareInterface FetchProductAction.php:23

One product: by its gid, its bare numeric id, its handle, or the address of a page of it - the storefront's /products/<handle> (the handle is the last segment) or the admin's /admin/products/<id>. Null when the shop has no such product.

FetchProductAction::__construct()

public function __construct(readonly ?string $currency = null)
$currency

the prices' currency; null: the shop's

FetchProductAction::supports()

public function supports(Request $request): bool

FetchProductAction::execute()

public function execute(Request $request): void

FetchProductAction::target()

public static function target(Reference $reference): array
Returns

the gid, or the handle

FetchProductsAction class

final class FetchProductsAction implements ActionInterface, ApiAwareInterface FetchProductsAction.php:22

A page of the shop's products, by the Admin GraphQL products(...) query, the least recently updated first: the cursor is Shopify's endCursor, updatedSince becomes the search filter updated_at:>'...' and the query is appended to it as Shopify's own search text. Pass the same updatedSince and query with the cursor: Shopify's cursor is only a position.

FetchProductsAction::__construct()

public function __construct(readonly ?string $currency = null)
$currency

the prices' currency; null: the shop's

FetchProductsAction::supports()

public function supports(Request $request): bool

FetchProductsAction::execute()

public function execute(Request $request): void
public static function search(?\DateTimeInterface $updatedSince, ?string $query): ?string

Shopify's search syntax: "updated_at:>'2026-10-01T00:00:00Z' margaux".

FetchTransactionAction class

final class FetchTransactionAction implements ActionInterface, ApiAwareInterface FetchTransactionAction.php:21

What became of a draft order: completed into a paid order (PAID), deleted on Shopify (CANCELLED - the sale is not happening), or still open (PENDING).

This is what covers a buyer who comes back before the webhook, or a webhook that never arrived.

FetchTransactionAction::__construct()

public function __construct()

FetchTransactionAction::supports()

public function supports(Request $request): bool

FetchTransactionAction::execute()

public function execute(Request $request): void

NotifyAction class

final class NotifyAction implements ActionInterface, ApiAwareInterface NotifyAction.php:39

A Shopify webhook, its HMAC and its shop checked. The order topics read as what they mean: orders/paid is PAID, orders/cancelled CANCELLED, refunds/create REFUNDED; the rest (fulfilments...) is handed back with no status, for the application to read from $raw.

The catalogue topics carry the catalogue: products/create and products/update the Product (the REST payload read like a GraphQL node), products/delete its gid alone (no product: it is gone), and inventory_levels/update one Stock - the level at one location, which names the inventory item rather than the variant: Stock::$reference and $item are then the InventoryItem's gid, to match on the variant's Stock::$item.

The reference is the draft order the sale began with (the transaction's reference) when the order names one, else the order's own gid; the merchant's reference travels in the note attributes (Documents::REFERENCE_KEY).

NotifyAction::__construct()

public function __construct(readonly ?string $currency = null)
$currency

the prices' currency; null: the shop's, asked once

NotifyAction::supports()

public function supports(Request $request): bool

NotifyAction::execute()

public function execute(Request $request): void

PurchaseAction class

final class PurchaseAction implements ActionInterface, ApiAwareInterface PurchaseAction.php:27

A draft order, paid on Shopify's invoice page: the buyer is sent there (PENDING with the invoice URL), and Shopify's orders/paid webhook or fetch() says when they paid. A draft order's lines take the prices they are given - a line with a variant gid as its reference names the variant, so Shopify counts its stock; any other is a custom line. The shipping and the discount are carried over whole. Shopify's checkout never redirects back: returnUrl is not used.

PurchaseAction::__construct()

public function __construct()

PurchaseAction::supports()

public function supports(Request $request): bool

PurchaseAction::execute()

public function execute(Request $request): void

PurchaseAction::input()

public static function input(Payment $payment, array $tags = []): array
Returns

a DraftOrderInput

Omnitrade\Shopify\Api

AdminApi class

class AdminApi AdminApi.php:6

The Admin GraphQL API of the configured shop: the client bound to one Endpoint.

AdminApi::__construct()

public function __construct(readonly GraphQL $graphql, readonly Endpoint $endpoint)

AdminApi::endpoint()

public function endpoint(): Endpoint

AdminApi::isConfigured()

public function isConfigured(): bool

AdminApi::query()

public function query(string $document, array $variables = []): array
Throws

ShopifyApiException

AdminApi::mutate()

public function mutate(string $document, array $variables, string $root): array

A mutation, with its userErrors raised rather than returned.

Endpoint class

final class Endpoint Endpoint.php:10

One Shopify shop: where it is, which API version to speak, and its tokens.

Every argument is nullable and normalised here, so an unset environment variable (null, not '') degrades to "not configured" instead of a type error.

public readonly string $shopDomain
public readonly string $apiVersion
public readonly string $adminToken
public readonly string $webhookSecret
public readonly int $timeout

Endpoint::__construct()

public function __construct(?string $shopDomain = '', ?string $apiVersion = '2026-07', ?string $adminToken = '', ?string $webhookSecret = '', ?int $timeout = 15)

Endpoint::isConfigured()

public function isConfigured(): bool

Whether there is enough here to call the Admin API at all.

Endpoint::canVerifyWebhooks()

public function canVerifyWebhooks(): bool

Endpoint::shop()

public function shop(): string

The shop as Shopify names it in X-Shopify-Shop-Domain.

Endpoint::adminUrl()

public function adminUrl(): string
public function adminLink(string $gid): string

The admin URL of one resource, for an "open in Shopify" link.

Endpoint::gid()

public static function gid(string $type, string|int $id): string

"gid://shopify/Order/123" from a bare id, or the gid as given.

GraphQL class

class GraphQL GraphQL.php:20

POST a GraphQL document, get the data back or a ShopifyApiException.

Knows nothing about orders or products: every document is passed in. It knows two things about Shopify: the Admin API is a leaky bucket costed per query (extensions.cost.throttleStatus), waited for when it runs low; and a THROTTLED answer is retried with backoff. The access token never reaches a log or an exception: not its message, its previous (a copy of the HTTP client's, Redaction::copy()) nor its trace's arguments.

GraphQL::__construct()

public function __construct(readonly HttpClientInterface $http, readonly ?LoggerInterface $logger = null, ?callable $sleep = null, readonly float $retryDelay = 1.0)

GraphQL::query()

public function query(string $url, array $headers, string $document, array $variables = [], int $timeout = 15): array
Returns

the data object

Throws

ShopifyApiException

GraphQL::assertNoUserErrors()

public function assertNoUserErrors(string $what, array $result): array

Raise a mutation's own userErrors: Shopify answers 200 with them when it refuses on business grounds.

Hmac class

final class Hmac Hmac.php:11

Shopify signs a webhook body with the app's API secret and sends the digest in X-Shopify-Hmac-Sha256: base64 of the raw binary hash, one value, no timestamp (X-Shopify-Triggered-At is outside the signature). Replay defence is the delivery id, X-Shopify-Webhook-Id, which the application keeps.

Hmac::verify()

public static function verify(string $payload, ?string $header, string $secret): bool

Hmac::sign()

public static function sign(string $payload, string $secret): string

The header Shopify would send for this body - used by the tests.

ShopifyApiException class

class ShopifyApiException extends ProviderException ShopifyApiException.php:11

Shopify said no, or said nothing. Carries the userErrors/errors it came with; never the access token (the headers are built at request time).

public readonly array $errors

ShopifyApiException::__construct()

public function __construct(string $message, public readonly array $errors = [], ?\Throwable $previous = null)

ShopifyApiException::fromErrors()

public static function fromErrors(string $what, array $errors): self