Экосистема приложений Shopify обеспечивает миллионы магазинов пользовательской функциональностью. Создание приложений Shopify открывает возможности решать задачи мерчантов в масштабе или создавать кастомные интеграции для конкретных клиентов. Это руководство рассматривает разработку приложений Shopify с точки зрения senior-разработчика.
Зачем разрабатывать для Shopify
Разработка для Shopify предлагает привлекательные возможности:
- Огромный рынок: миллионы активных магазинов по всему миру
- Дистрибуция через App Store: встроенный маркетплейс для обнаружения
- Регулярная выручка: поддержка подписочной модели биллинга
- Хорошо документированные API: варианты REST и GraphQL
- Современный стек: App Bridge для бесшовной UI-интеграции
Типы приложений
Public Apps
- Публикуются в Shopify App Store
- Доступны всем мерчантам
- Разделение выручки с Shopify
- Строгий процесс ревью
Custom Apps
- Создаются для конкретных магазинов
- Прямая установка
- Без публикации в App Store
- Более простой процесс одобрения
Private Apps
- Устаревший вариант (постепенно выводится из использования)
- Вместо этого используйте custom apps
Начало работы
Предварительные требования
- Аккаунт Shopify Partner (бесплатно)
- Development store для тестирования
- ngrok или аналог для локальной разработки
Создание приложения в Partner Dashboard
- Перейдите в Partners → Apps → Create app
- Настройте URL приложения:
- App URL: https://your-app.com/ - Allowed redirection URL: https://your-app.com/auth/callback 3. Запишите ваши API-учётные данные: - Client ID - Client Secret
OAuth Authentication Flow
Шаг 1: Редирект в Shopify
Когда мерчант устанавливает ваше приложение:
<?php
$client_id = 'your-client-id';
$scopes = 'read_products,write_products,read_orders,write_orders';
$redirect_uri = 'https://your-app.com/auth/callback';
$shop = $_GET['shop'];
$nonce = bin2hex(random_bytes(12));
// Сохраните nonce в сессии для проверки
$_SESSION['oauth_nonce'] = $nonce;
$oauth_url = "https://{$shop}/admin/oauth/authorize?" . http_build_query([
'client_id' => $client_id,
'scope' => $scopes,
'redirect_uri' => $redirect_uri,
'state' => $nonce,
'grant_options[]' => 'per-user'
]);
header("Location: {$oauth_url}");
exit();
Шаг 2: Обработка callback
<?php
$client_id = 'your-client-id';
$client_secret = 'your-client-secret';
// Проверьте HMAC-подпись
$params = $_GET;
$hmac = $params['hmac'];
unset($params['hmac']);
ksort($params);
$computed_hmac = hash_hmac('sha256', http_build_query($params), $client_secret);
if (!hash_equals($hmac, $computed_hmac)) {
die('Invalid signature - possible attack');
}
// Проверьте nonce
if ($params['state'] !== $_SESSION['oauth_nonce']) {
die('Invalid state parameter');
}
// Обменяйте code на access token
$shop = $params['shop'];
$code = $params['code'];
$response = file_get_contents("https://{$shop}/admin/oauth/access_token", false, stream_context_create([
'http' => [
'method' => 'POST',
'header' => 'Content-Type: application/x-www-form-urlencoded',
'content' => http_build_query([
'client_id' => $client_id,
'client_secret' => $client_secret,
'code' => $code
])
]
]));
$data = json_decode($response, true);
$access_token = $data['access_token'];
// Храните access token безопасно (в базе данных)
saveAccessToken($shop, $access_token);
// Выполните редирект в приложение
header("Location: /app?shop={$shop}");
Интеграция с REST API
Выполнение API-запросов
<?php
function shopifyRequest($shop, $access_token, $endpoint, $method = 'GET', $data = null) {
$url = "https://{$shop}/admin/api/2024-01{$endpoint}";
$headers = [
"X-Shopify-Access-Token: {$access_token}",
"Content-Type: application/json"
];
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
if ($data && in_array($method, ['POST', 'PUT'])) {
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
}
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode >= 400) {
throw new Exception("API Error: {$httpCode} - {$response}");
}
return json_decode($response, true);
}
// Примеры
// Получить список products
$products = shopifyRequest($shop, $token, '/products.json');
// Получить один product
$product = shopifyRequest($shop, $token, '/products/123456789.json');
// Создать product
$newProduct = shopifyRequest($shop, $token, '/products.json', 'POST', [
'product' => [
'title' => 'New Product',
'body_html' => '<p>Description</p>',
'vendor' => 'My Store',
'product_type' => 'Widget',
'variants' => [
['price' => '19.99', 'sku' => 'WIDGET-001']
]
]
]);
// Обновить product
shopifyRequest($shop, $token, '/products/123456789.json', 'PUT', [
'product' => ['title' => 'Updated Title']
]);
// Удалить product
shopifyRequest($shop, $token, '/products/123456789.json', 'DELETE');
Интеграция с GraphQL API
GraphQL рекомендуется для новых приложений — он более эффективен и гибок:
<?php
function shopifyGraphQL($shop, $access_token, $query, $variables = []) {
$url = "https://{$shop}/admin/api/2024-01/graphql.json";
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
"X-Shopify-Access-Token: {$access_token}",
"Content-Type: application/json"
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
'query' => $query,
'variables' => $variables
]));
$response = curl_exec($ch);
curl_close($ch);
return json_decode($response, true);
}
// Запрос products
$query = <<<'GRAPHQL'
query getProducts($first: Int!) {
products(first: $first, query: "status:active") {
edges {
node {
id
title
description
variants(first: 10) {
nodes {
id
price
inventoryQuantity
}
}
images(first: 1) {
edges {
node {
url
}
}
}
}
}
pageInfo {
hasNextPage
endCursor
}
}
}
GRAPHQL;
$result = shopifyGraphQL($shop, $token, $query, ['first' => 50]);
// Mutation: обновить product
$mutation = <<<'GRAPHQL'
mutation updateProduct($input: ProductInput!) {
productUpdate(input: $input) {
product {
id
title
}
userErrors {
field
message
}
}
}
GRAPHQL;
$result = shopifyGraphQL($shop, $token, $mutation, [
'input' => [
'id' => 'gid://shopify/Product/123456789',
'title' => 'Updated via GraphQL'
]
]);
App Bridge UI
Встраивайте приложение бесшовно в Shopify Admin:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="shopify-api-key" content="<?php echo $client_id; ?>" />
<script src="https://cdn.shopify.com/shopifycloud/app-bridge.js"></script>
<link rel="stylesheet" href="https://unpkg.com/@shopify/polaris@latest/build/esm/styles.css" />
</head>
<body>
<!--Title bar с действиями-->
<ui-title-bar title="My App">
<button variant="primary" onclick="saveSettings()">Save</button>
<button onclick="showHelp()">Help</button>
</ui-title-bar>
<!--Меню навигации-->
<ui-nav-menu>
<a href="/dashboard" rel="home">Dashboard</a>
<a href="/products">Products</a>
<a href="/settings">Settings</a>
</ui-nav-menu>
<div id="app">
<h1>Welcome to My App</h1>
<button id="pick-product">Select Product</button>
<div id="selected-product"></div>
</div>
<script>
// Resource Picker
document.getElementById('pick-product').addEventListener('click', async () => {
const selected = await shopify.resourcePicker({
type: 'product',
multiple: false
});
if (selected && selected.length > 0) {
document.getElementById('selected-product').textContent =
`Selected: ${selected[0].title}`;
}
});
// Toast notifications
function showSuccess(message) {
shopify.toast.show(message, { duration: 3000 });
}
function showError(message) {
shopify.toast.show(message, { isError: true });
}
// Modal
async function showHelp() {
await shopify.modal.alert({
title: 'Help',
message: 'This app helps you manage products more efficiently.'
});
}
// Confirm dialog
async function confirmDelete() {
const confirmed = await shopify.modal.confirm({
title: 'Delete Item',
message: 'Are you sure? This cannot be undone.'
});
if (confirmed) {
// Proceed with deletion
}
}
// Get session token for authenticated API calls
async function callBackend() {
const token = await shopify.idToken();
const response = await fetch('/api/data', {
headers: {
'Authorization': `Bearer ${token}`
}
});
return response.json();
}
</script>
</body>
</html>
Проверка Session Token
Проверяйте запросы из вашего embedded app:
<?php
function verifySessionToken($token, $client_secret) {
$parts = explode('.', $token);
if (count($parts) !== 3) {
return false;
}
list($header, $payload, $signature) = $parts;
// Проверьте подпись
$expected_sig = rtrim(strtr(
base64_encode(hash_hmac('sha256', "{$header}.{$payload}", $client_secret, true)),
'+/', '-_'
), '=');
if (!hash_equals($expected_sig, $signature)) {
return false;
}
// Декодируйте и проверьте payload
$data = json_decode(base64_decode($payload), true);
// Проверьте срок действия
if ($data['exp'] < time()) {
return false;
}
return $data; // Содержит shop, sub (user) и т. д.
}
// Использование в API endpoint
$auth_header = $_SERVER['HTTP_AUTHORIZATION'] ?? '';
if (preg_match('/Bearer\s+(.+)/', $auth_header, $matches)) {
$token_data = verifySessionToken($matches[1], $client_secret);
if ($token_data) {
$shop = $token_data['dest']; // Домен shop
// Обработайте аутентифицированный запрос
}
}
Webhooks
Подписывайтесь на события магазина:
<?php
// Зарегистрируйте webhook через API
$webhook_data = [
'webhook' => [
'topic' => 'orders/create',
'address' => 'https://your-app.com/webhooks/orders',
'format' => 'json'
]
];
shopifyRequest($shop, $token, '/webhooks.json', 'POST', $webhook_data);
// Endpoint для webhook
// webhooks/orders.php
$raw_body = file_get_contents('php://input');
$hmac_header = $_SERVER['HTTP_X_SHOPIFY_HMAC_SHA256'];
// Проверьте webhook
$computed_hmac = base64_encode(hash_hmac('sha256', $raw_body, $client_secret, true));
if (!hash_equals($hmac_header, $computed_hmac)) {
http_response_code(401);
exit('Unauthorized');
}
// Обработайте webhook
$data = json_decode($raw_body, true);
$order_id = $data['id'];
$customer_email = $data['email'];
// Обработайте заказ...
processNewOrder($data);
http_response_code(200);
App Billing
Взимайте плату с мерчантов за ваше приложение:
<?php
// Создайте recurring charge
$charge = shopifyGraphQL($shop, $token, <<<'GRAPHQL'
mutation createSubscription($name: String!, $price: Decimal!, $returnUrl: URL!) {
appSubscriptionCreate(
name: $name
returnUrl: $returnUrl
lineItems: [{
plan: {
appRecurringPricingDetails: {
price: { amount: $price, currencyCode: USD }
interval: EVERY_30_DAYS
}
}
}]
) {
appSubscription {
id
status
}
confirmationUrl
userErrors {
field
message
}
}
}
GRAPHQL, [
'name' => 'Pro Plan',
'price' => '9.99',
'returnUrl' => 'https://your-app.com/billing/confirm'
]);
// Перенаправьте мерчанта на confirmation URL
if ($charge['data']['appSubscriptionCreate']['confirmationUrl']) {
header('Location: ' . $charge['data']['appSubscriptionCreate']['confirmationUrl']);
}
Тестирование
Development Store
- Создайте development store в Partner Dashboard
- Установите приложение напрямую (ревью не требуется)
- Протестируйте всю функциональность
Тестовые данные
# Shopify CLI для генерации тестовых данных
shopify populate products --count 20
shopify populate customers --count 10
shopify populate orders --count 5
Ключевые выводы
- Используйте GraphQL: более эффективно, чем REST, для сложных запросов
- Проверяйте всё: HMAC-подписи, session tokens, webhooks
- App Bridge для UI: бесшовный опыт для мерчанта
- Обрабатывайте rate limits: соблюдайте API throttling
- Защищайте tokens: никогда не раскрывайте access tokens на frontend
- Тщательно тестируйте: используйте development stores перед production
Разработка для Shopify — прибыльная возможность для разработчиков, которые освоят его API и ориентированный на мерчантов подход.