Pereiti prie turinio
ShopentlyLearn

Produkto kainos keitimas

Produkto kainos keitimas. Parametrai: PRODUCT_ID, MARKET_ID. Kodo pavyzdžiai: cURL, PHP, Python.

Parametrai

  • SHOPENTLY_SHOP_URL — Ištekliaus URL.
  • SHOPENTLY_ACCESS_TOKEN — Trumpalaikė Bearer prieigos priemonė.
  • PRODUCT_ID — Produkto identifikatorius.
  • MARKET_ID — Konteksto prieigos taško grąžintas rinkos identifikatorius.
  • NEW_AMOUNT — Dešimtainė suma, užkoduota JSON eilute; null tik ten, kur leidžiama.
  • IDEMPOTENCY_KEY — Vienos loginės įrašymo operacijos unikalus identifikatorius.

PHP 8.3 (64-bit, ext-curl, JSON) · Python 3.12 + requests · cURL 8 + jq 1.7.

Visas pavyzdys

cURL

#!/bin/sh
# POSIX shell, curl 8+, jq 1.7. Credentials stay in environment variables.
set -eu
: "${SHOPENTLY_SHOP_URL:?}"
base=${SHOPENTLY_SHOP_URL%/}
case "$base" in https://*) ;; *) echo 'Use an HTTPS origin' >&2; exit 1;; esac
authority=${base#https://}
case "$authority" in ''|*/*|*\?*|*\#*|*@*) echo 'Use an HTTPS origin' >&2; exit 1;; esac
work=$(mktemp -d)
trap 'rm -rf "$work"' EXIT HUP INT TERM
chmod 700 "$work"
encode() { printf '%s' "$1" | jq -sRr @uri; }
api() {
    method=$1; path=$2; body=${3-}; key=${4-}
    scheme=Bearer; credential=${SHOPENTLY_ACCESS_TOKEN-}
    if [ "$path" = /api/v1/auth/token ]; then scheme=ApiKey; credential=${SHOPENTLY_API_KEY-}; fi
    [ -n "$credential" ] || { echo 'Missing credential' >&2; return 1; }
    set -- --disable --globoff --silent --show-error --proto '=https' --connect-timeout 5 --max-time 30 \
        --request "$method" --header "Authorization: $scheme $credential" \
        --dump-header "$work/headers" --output "$work/body" --write-out '%{http_code}'
    if [ -n "$key" ]; then set -- "$@" --header "Idempotency-Key: $key"; fi
    if [ -n "$body" ]; then set -- "$@" --header 'Content-Type: application/json' --data-raw "$body"; fi
    status=$(curl "$@" --url "$base$path") || return 1
    case "$status" in
        2??) if [ -s "$work/body" ]; then jq -e . "$work/body"; else printf 'null\n'; fi ;;
        *) printf 'HTTP %s\n' "$status" >&2
           jq -c '.error | {code, fields}' "$work/body" >&2 2>/dev/null || true
           return 1 ;;
    esac
}

: "${MARKET_ID:?}" "${PRODUCT_ID:?}" "${NEW_AMOUNT:?}" "${IDEMPOTENCY_KEY:?}"
context=$(api GET /api/v1/context)
market=$(printf '%s' "$context" | jq -ec --arg id "$MARKET_ID" '.data.markets[] | select(.id == $id and .price_list != null)')
base_market=$(printf '%s' "$context" | jq -er '.data.base_market_id')
kind=market_fixed
[ "$MARKET_ID" != "$base_market" ] || kind=base
path="/api/v1/products/$(encode "$PRODUCT_ID")?market_id=$(encode "$MARKET_ID")"
product=$(api GET "$path")
body=$(printf '%s' "$product" | jq -c --argjson market "$market" --arg kind "$kind" --arg amount "$NEW_AMOUNT" '{
    expected_version: .data.version, pricing: {
        kind: $kind, amount: $amount, currency: $market.price_list.currency,
        price_list_id: $market.price_list.id, expected_price_list_version: $market.price_list.version,
        expected_version: .data.price_version, tax_included: $market.price_list.tax_included}}')
# Persist this key and body before sending. Retry an uncertain write unchanged.
api PATCH "$path" "$body" "$IDEMPOTENCY_KEY"

PHP

<?php
declare(strict_types=1);
// PHP 8.3+, 64-bit, ext-curl. Credentials stay in environment variables.
function env(string $name): string {
    $value = getenv($name);
    if ($value === false || $value === '' || strpbrk($value, "\r\n") !== false) {
        throw new RuntimeException('Invalid environment variable: ' . $name);
    }
    return $value;
}
$base = rtrim(env('SHOPENTLY_SHOP_URL'), '/');
$origin = parse_url($base);
if (!$origin || ($origin['scheme'] ?? '') !== 'https' || empty($origin['host']) ||
    isset($origin['user']) || isset($origin['pass']) || isset($origin['path']) ||
    isset($origin['query']) || isset($origin['fragment']) || PHP_INT_SIZE !== 8) {
    throw new RuntimeException('Use a 64-bit PHP runtime and an HTTPS origin');
}
class ApiError extends RuntimeException {
    public function __construct(public int $status, public array $headers, public array $error) {
        parent::__construct('HTTP ' . $status . '; code=' . ($error['code'] ?? '') .
            '; request_id=' . ($headers['x-request-id'] ?? ''));
    }
}
function api(string $method, string $path, array $query = [], ?array $body = null, ?string $key = null, bool $apiKey = false): ?array {
    global $base;
    $credential = env($apiKey ? 'SHOPENTLY_API_KEY' : 'SHOPENTLY_ACCESS_TOKEN');
    $headers = ['Authorization: ' . ($apiKey ? 'ApiKey ' : 'Bearer ') . $credential];
    if ($key !== null) $headers[] = 'Idempotency-Key: ' . $key;
    if ($body !== null) $headers[] = 'Content-Type: application/json';
    $url = $base . $path . ($query ? '?' . http_build_query($query, '', '&', PHP_QUERY_RFC3986) : '');
    $responseHeaders = [];
    $h = curl_init($url);
    curl_setopt_array($h, [
        CURLOPT_CUSTOMREQUEST => $method, CURLOPT_HTTPHEADER => $headers,
        CURLOPT_RETURNTRANSFER => true, CURLOPT_FOLLOWLOCATION => false,
        CURLOPT_PROTOCOLS => CURLPROTO_HTTPS, CURLOPT_SSL_VERIFYPEER => true,
        CURLOPT_SSL_VERIFYHOST => 2, CURLOPT_CONNECTTIMEOUT => 5, CURLOPT_TIMEOUT => 30,
        CURLOPT_HEADERFUNCTION => static function ($h, string $line) use (&$responseHeaders): int {
            if (str_contains($line, ':')) {
                [$name, $value] = explode(':', $line, 2);
                $responseHeaders[strtolower(trim($name))] = trim($value);
            }
            return strlen($line);
        },
    ]);
    if ($body !== null) {
        $json = $apiKey && $body === [] ? '{}' : json_encode($body, JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE);
        curl_setopt($h, CURLOPT_POSTFIELDS, $json);
    }
    $raw = curl_exec($h);
    $status = curl_getinfo($h, CURLINFO_RESPONSE_CODE);
    $error = curl_error($h);
    curl_close($h);
    if ($raw === false) throw new RuntimeException('Transport error: ' . $error);
    if ($status < 200 || $status >= 300) {
        $data = json_decode($raw, true);
        throw new ApiError($status, $responseHeaders, is_array($data) ? ($data['error'] ?? []) : []);
    }
    return $raw === '' ? null : json_decode($raw, true, 512, JSON_THROW_ON_ERROR | JSON_BIGINT_AS_STRING);
}
function output(mixed $value): void {
    echo json_encode($value, JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE), PHP_EOL;
}
$context = api('GET', '/api/v1/context')['data'];
$matches = array_values(array_filter($context['markets'], fn($m) => $m['id'] === env('MARKET_ID')));
if (count($matches) !== 1 || !$matches[0]['price_list']) throw new RuntimeException('Select a market with an active price list');
$market = $matches[0]; $priceList = $market['price_list'];
$path = '/api/v1/products/' . rawurlencode(env('PRODUCT_ID'));
$query = ['market_id' => $market['id']];
$product = api('GET', $path, $query)['data'];
$body = ['expected_version' => $product['version'], 'pricing' => [
    'kind' => $market['id'] === $context['base_market_id'] ? 'base' : 'market_fixed',
    'amount' => env('NEW_AMOUNT'), 'currency' => $priceList['currency'],
    'price_list_id' => $priceList['id'], 'expected_price_list_version' => $priceList['version'],
    'expected_version' => $product['price_version'], 'tax_included' => $priceList['tax_included'],
]];
// Persist this key and body before sending. Retry an uncertain write unchanged.
output(api('PATCH', $path, $query, $body, env('IDEMPOTENCY_KEY')));

Python

"""Run with Python 3.12 and requests. Credentials stay in environment variables."""
import json
import os
import random
import time
from urllib.parse import quote, urlencode, urlsplit
import requests

def env(name):
    value = os.environ[name]
    if not value or "\r" in value or "\n" in value:
        raise ValueError("Invalid environment variable: " + name)
    return value

base = env("SHOPENTLY_SHOP_URL").rstrip("/")
origin = urlsplit(base)
if origin.scheme != "https" or not origin.hostname or origin.username or origin.password or origin.path or origin.query or origin.fragment:
    raise ValueError("SHOPENTLY_SHOP_URL must be an HTTPS origin")

class ApiError(RuntimeError):
    def __init__(self, response):
        self.status = response.status_code
        self.request_id = response.headers.get("X-Request-ID", "")
        self.retry_after = response.headers.get("Retry-After", "")
        try:
            self.error = response.json().get("error", {})
        except (ValueError, AttributeError):
            self.error = {}
        super().__init__(f"HTTP {self.status}; code={self.error.get('code', '')}; request_id={self.request_id}")

def api(method, path, *, query=None, body=None, key=None, api_key=False):
    credential = env("SHOPENTLY_API_KEY" if api_key else "SHOPENTLY_ACCESS_TOKEN")
    headers = {"Authorization": ("ApiKey " if api_key else "Bearer ") + credential}
    if key:
        headers["Idempotency-Key"] = key
    if body is not None:
        headers["Content-Type"] = "application/json"
    response = requests.request(method, base + path, params=query, headers=headers,
        data=json.dumps(body, ensure_ascii=False, allow_nan=False).encode() if body is not None else None,
        timeout=(5, 30), allow_redirects=False)
    if not 200 <= response.status_code < 300:
        raise ApiError(response)
    return response.json() if response.content else None

def segment(value):
    return quote(value, safe="")

# The chosen market must expose its own active price list.
context = api("GET", "/api/v1/context")["data"]
market = next(m for m in context["markets"] if m["id"] == env("MARKET_ID"))
price_list = market["price_list"]
if not price_list:
    raise ValueError("Selected market has no active price list")
path = "/api/v1/products/" + segment(env("PRODUCT_ID"))
query = {"market_id": market["id"]}
product = api("GET", path, query=query)["data"]
body = {"expected_version": product["version"], "pricing": {
    "kind": "base" if market["id"] == context["base_market_id"] else "market_fixed",
    "amount": env("NEW_AMOUNT"), "currency": price_list["currency"],
    "price_list_id": price_list["id"], "expected_price_list_version": price_list["version"],
    "expected_version": product["price_version"], "tax_included": price_list["tax_included"]}}
# Persist this key and body before sending. Retry an uncertain write unchanged.
print(json.dumps(api("PATCH", path, query=query, body=body, key=env("IDEMPOTENCY_KEY"))))

Kaip tai veikia

Palaikomus išteklius kurkite su POST, iš dalies atnaujinkite su PATCH ir šalinkite su DELETE. Kiekvienam ištekliaus įrašymui reikia Idempotency-Key. Vienai loginei operacijai sukurkite vieną raktą; ją kartodami naudokite tą patį raktą, metodą, tikslą ir duomenis.

Prieš redaguodami perskaitykite išteklių ir pagal dokumentaciją pateikite expected_version. Kainų pakeitimams gali reikėti ir produkto, kainos, produkto varianto kainos ar kainoraščio versijų. Versijai pasikeitus, skaitykite iš naujo ir suderinkite pakeitimą, užuot priverstinai įrašę pasenusius duomenis.

Pakartota sėkminga operacija grąžina išsaugotą rezultatą, dar kartą neatlikdama pakeitimo. Tas pats raktas kitai užklausai sukelia idempotency.key_reused. Visi pavyzdžiai yra sintetiniai; prieš vykdydami pakeiskite identifikatorius reikšmėmis iš savo parduotuvės.