Przejdź do głównej zawartości

Zamówienie ze sklepu internetowego

Dla: Programista

Klient składa zamówienie w sklepie internetowym, a sklep przekazuje je do Prynt jednym wywołaniem createOrder: z produktami, osobą kontaktową i adresem dostawy. Zamówienie pojawia się w Prynt jako nowe (CREATED), z integracją jako autorem, a drukarnia potwierdza je i prowadzi dalej jak każde inne.

  • Klucz z zakresem orders:write. Żeby później czytać status zamówienia, dodaj orders:read, a do wysyłania plików zakresy z przepisu Pliki do zamówienia.

  • Klienta sklepu w Prynt. Zamówienie zawsze należy do klienta w Prynt. Drukarnia dodaje w Prynt klienta „Sklep internetowy”; jego identyfikator to ostatnia część adresu karty klienta (/dashboard/clients/<id>), który podajesz w zmiennej środowiskowej PRYNT_SHOP_CLIENT_ID. Kod niżej obsługuje dwa sposoby:

    • jeden klient na cały sklep (domyślnie): każde zamówienie trafia na klienta sklepu, a dane firmy kupującego (nazwa, NIP, adres) na fakturę zamówienia (invoice). Wystarczy sam zakres orders:write;
    • kupujący jako klienci w Prynt (PRYNT_BUYERS_AS_CLIENTS=1): kod znajduje kupującego w Prynt albo go zakłada, a na kliencie sklepu zostawia tylko zamówienia kupujących, których nie może bezpiecznie zapisać na ich własnego klienta (zobacz niżej). Wymaga też zakresów clients:read i clients:write. Dobre dla sklepu B2B, w którym drukarnia chce widzieć kupujące firmy i ich zamówienia na kartach klientów.

    Kupującego i tak przekazujesz jako osobę kontaktową zamówienia (contact), a jego adres jako dostawę (deliveries).

  • Klienta API z przepisu Obsługa błędów i ponowień. Kod niżej wysyła żądania przez niego.

  • Jedno zamówienie sklepu, jeden klucz idempotencji. Klucz sklep-zamowienie-<numer> wynika z numeru zamówienia w sklepie, więc nie musisz go nigdzie zapisywać. Ponowienie, także po restarcie procesu, w ciągu 24 h zwraca to samo zamówienie zamiast tworzyć drugie. Szczegóły: Zapisy i idempotencja.
  • Ten sam klucz, ta sama treść. Jeśli zamówienie w sklepie zmieniło się po wysłaniu, nie wysyłaj go ponownie: zmień je w Prynt mutacjami updateOrder i updateProduct. Inna treść z tym samym kluczem kończy się błędem API_IDEMPOTENCY_KEY_REUSED.
  • Zapisz id z odpowiedzi przy zamówieniu w sklepie. Po nim odczytasz status zamówienia i dołączysz do niego pliki. displayId (np. 1084/2026) to numer, którym posługuje się drukarnia.
  • Ceny pozycji. price to cena netto całej pozycji, czyli wszystkich sztuk razem, a nie cena za sztukę. taxRate podajesz w procentach (23), choć w odpowiedziach Product.taxRate jest ułamkiem (0.23); wartość między 0 a 1 API odrzuca jako ułamek wysłany przez pomyłkę. Uwaga: taxRate kosztu dodatkowego (additionalCosts) podajesz już jako ułamek (0.23). Zapis cen wymaga tylko zakresu zapisu, ale ich odczyt już prices:read.
  • Puste pola pomijaj. Pole bez wartości po prostu nie trafia do żądania. Jawny null w polu, które go nie przyjmuje, API odrzuca.
  • Status prowadzi drukarnia. Zamówienie zaczyna jako CREATED, a każdy jego produkt jako NO_FILES (bez plików do druku): statusu produktu nie podajesz, prowadzą go pliki i produkcja. Integracja nie przeskoczy etapów zamówienia, np. od razu do SENT: updateOrder przyjmuje tylko kolejny dozwolony status (np. CREATED → ACCEPTED, gdy zamówienie ma handlowca, albo anulowanie CANCELLED) i odrzuca cofnięcie. Dalsze statusy zwykle ustawiają drukarnia i Prynt w trakcie produkcji.
  • Klient zablokowany przez drukarnię (np. za zaległe płatności) albo przez nią usunięty (isActive: false) nie przyjmuje zamówień z API: createOrder kończy się błędem walidacji, a zamówienie trzeba wyjaśnić z drukarnią.

Z PRYNT_BUYERS_AS_CLIENTS=1 kod przed zamówieniem znajduje kupującego w Prynt albo go zakłada (funkcja ensureClient):

  1. Szuka po identyfikatorze kupującego w sklepie. Klient założony przez sklep ma go w polu externalRef, a filtr externalRef listy clients znajduje go przy kolejnym zamówieniu. Ten identyfikator widzi tylko Twoja integracja: inne integracje i aplikacja mają własne. Klienta, którego drukarnia usunęła (isActive: false), API nie przyjmuje: zamówienie trafia wtedy na klienta sklepu.

  2. Firmę bez takiego identyfikatora szuka po NIP — tylko dla zweryfikowanego kupującego. Drukarnia mogła ją już dodać w Prynt. Kod wysyła NIP jako same cyfry, a że filtr taxIdentificationNumber szuka fragmentu numeru, porównuje cały NIP. Znalezioną firmę zapisuje na kupującego tylko wtedy, gdy sklep zweryfikował, że kupujący działa w jej imieniu (company.verified, np. zatwierdzone konto B2B); inaczej zamówienie trafia na klienta sklepu z firmą i NIP na fakturze. Firmie bez identyfikatora ze sklepu dopisuje go (updateClient), a firmę połączoną już z innym kupującym zostawia przy nim: jedna firma to jeden klient w Prynt z wieloma kupującymi.

  3. Nikogo nie znalazł: zakłada klienta mutacją createClient z kluczem idempotencji sklep-klient-<id kupującego>. Firma ma legalName i NIP, osoba prywatna imię i nazwisko.

Warunki handlowe nowego klienta — limit kredytowy, rabat, termin i formę płatności, podmiot wystawiający faktury — ustala drukarnia w Prynt: integracja ich nie podaje. Więcej o identyfikatorze externalRef i synchronizacji klientów: Klienci do CRM i z powrotem.

Każdy wariant przyjmuje zamówienie sklepu w takim kształcie (dopasuj go do swojego sklepu):

zamowienie-sklepu.json
{
"number": "WWW-1042",
"paid": true,
"customer": {
"id": 1042,
"firstName": "Anna",
"lastName": "Nowak",
"email": "[email protected]",
"phone": "+48 600 100 200"
},
"address": {
"street": "Długa",
"buildingNumber": "5",
"zip": "00-001",
"city": "Warszawa",
"country": "PL"
},
"items": [
{
"name": "Wizytówki 90×50",
"quantity": 500,
"netTotal": 120,
"vatPercent": 23,
"weightKg": 1.2
}
]
}

Zamówienie bez adresu ("address": null) trafia do Prynt jako odbiór osobisty. Kupujący firma ma w customer dodatkowo "company": { "name": "…", "nip": "…", "verified": true }, gdzie verified znaczy, że sklep potwierdził, że kupujący działa w imieniu tej firmy; adres na fakturę podaj w zamówieniu jako billingAddress w tym samym kształcie co address. Identyfikator klienta sklepu w Prynt kod czyta ze zmiennej środowiskowej PRYNT_SHOP_CLIENT_ID, sposób z PRYNT_BUYERS_AS_CLIENTS (1: kupujący jako klienci), a klucz API, jak zawsze, z PRYNT_API_KEY.

Przykłady pokazują adres zastępczy. Wpisz adres swojej drukarni, żeby go podmienić.

Wymaga curl, jq i skryptu prynt-call.sh z przepisu Obsługa błędów i ponowień w tym samym katalogu.

send-order.sh
#!/usr/bin/env bash
# send-order.sh — tworzy zamówienie z pliku JSON sklepu (curl, jq; prynt-call.sh z przepisu
# „Obsługa błędów i ponowień”). Użycie: ./send-order.sh zamowienie-sklepu.json
# Klient w Prynt: PRYNT_SHOP_CLIENT_ID (id z adresu jego karty) — jeden na zamówienia sklepu; z
# PRYNT_BUYERS_AS_CLIENTS=1 kupujący staje się klientem w Prynt (znaleziony albo utworzony), a
# klient sklepu zostaje dla kupujących, których nie można zapisać na ich własnego klienta.
set -euo pipefail
order=$1
body=$(mktemp) result=$(mktemp)
trap 'rm -f "$body" "$result"' EXIT
# Wysyła operację $1 ze zmiennymi $2 (JSON); odpowiedź zostaje w pliku $result.
request() {
jq -n --arg query "$1" --argjson variables "$2" '{ query: $query, variables: $variables }' >"$body"
./prynt-call.sh "$body" >"$result"
}
# Klient w Prynt dla kupującego (pusty wynik: zamówienie trafia na klienta sklepu):
# 1) po identyfikatorze ze sklepu (externalRef) — pusty, gdy drukarnia tego klienta usunęła;
# 2) firma po całym NIP — tylko dla kupującego, którego sklep zweryfikował jako tę firmę
# (company.verified): NIP jest publiczny, więc niezweryfikowany kupujący z NIP firmy, którą
# drukarnia już ma, dostaje pusty wynik. Firmie bez identyfikatora ze sklepu dopisujemy go;
# firma połączona już z innym kupującym zostaje przy nim;
# 3) inaczej nowy klient, raz na kupującego.
ensure_client() {
local ref nip verified company
ref=$(jq -r '.customer.id | tostring' "$order")
nip=$(jq -r '.customer.company.nip // "" | gsub("[^0-9]"; "")' "$order")
verified=$(jq -r '.customer.company.verified == true' "$order")
request 'query FindShopCustomer($ref: String!) { clients(filter: { externalRef: $ref }) { items { id isActive } } }' \
"$(jq -n --arg ref "$ref" '{ ref: $ref }')"
if [[ $(jq '.data.clients.items | length' "$result") -gt 0 ]]; then
jq -r '.data.clients.items[0] | if .isActive then .id else "" end' "$result"
return
fi
if [[ -n $nip ]]; then
request 'query FindShopCompany($nip: String!) { clients(filter: { taxIdentificationNumber: $nip }) { items { id taxIdentificationNumber externalRef isActive } } }' \
"$(jq -n --arg nip "$nip" '{ nip: $nip }')"
# Filtr szuka fragmentu numeru: bierzemy tylko klienta z całym tym samym NIP.
company=$(jq -c --arg nip "$nip" '[.data.clients.items[] | select(.taxIdentificationNumber == $nip)][0] // empty' "$result")
if [[ -n $company ]]; then
if [[ $verified != true || $(jq -r .isActive <<<"$company") != true ]]; then
return
fi
if [[ $(jq -r '.externalRef == null' <<<"$company") == true ]]; then
request 'mutation LinkShopCustomer($id: ID!, $ref: String!) { updateClient(id: $id, input: { externalRef: $ref }) { id } }' \
"$(jq -n --arg id "$(jq -r .id <<<"$company")" --arg ref "$ref" '{ id: $id, ref: $ref }')"
fi
jq -r .id <<<"$company"
return
fi
fi
IDEMPOTENCY_KEY="sklep-klient-$ref" request \
'mutation CreateShopCustomer($input: CreateClientInput!) { createClient(input: $input) { id } }' \
"$(jq '.customer | {
input: ({
type: (if .company then "COMPANY" else "INDIVIDUAL" end),
legalName: .company.name,
taxIdentificationNumber: (.company.nip // null | if . then gsub("[^0-9]"; "") else null end),
firstName: (if .company then null else .firstName end),
lastName: (if .company then null else .lastName end),
email: .email,
phone: (.phone // null | if . then gsub("[\\s()-]"; "") else null end),
externalRef: (.id | tostring)
} | with_entries(select(.value != null)))
}' "$order")"
jq -r '.data.createClient.id' "$result"
}
own=""
if [[ ${PRYNT_BUYERS_AS_CLIENTS:-} == 1 ]]; then own=$(ensure_client); fi
client=${own:-${PRYNT_SHOP_CLIENT_ID:-}}
if [[ -z $client ]]; then
echo "Brak PRYNT_SHOP_CLIENT_ID: id klienta sklepu w Prynt." >&2
exit 1
fi
on_shop_client=$([[ -z $own ]] && echo true || echo false)
jq --arg client "$client" --argjson onShopClient "$on_shop_client" '{
query: "mutation CreateOrder($input: CreateOrderInput!) { createOrder(input: $input) { id displayId products { id number name } } }",
variables: { input: {
clientId: $client,
totalPallets: 0,
totalPackages: 1,
transportMethod: (if .address then "COURIER" else "PICKUP" end),
paymentMethod: "ONLINE_PREPAID",
paymentStatus: (if .paid then "PAID" else "AWAITING_PAYMENT" end),
contact: (.customer | { firstName, lastName, email, phone }),
# Na kliencie sklepu kupujący nie ma karty w Prynt: dane jego firmy idą do faktury.
invoice: (if $onShopClient and .customer.company then {
companyName: .customer.company.name,
nip: (.customer.company.nip | gsub("[^0-9]"; "")),
street: (.billingAddress | if . then "\(.street) \(.buildingNumber // "")" | rtrimstr(" ") else null end),
zip: .billingAddress.zip,
city: .billingAddress.city,
country: .billingAddress.country
} else null end),
deliveries: (if .address then [{
contactName: "\(.customer.firstName) \(.customer.lastName)",
street: .address.street,
buildingNumber: .address.buildingNumber,
zip: .address.zip,
city: .address.city,
country: .address.country,
phone: .customer.phone
}] else [] end),
products: [.items | to_entries[] | {
number: (.key + 1),
name: .value.name,
description: .value.description,
quantity: .value.quantity,
unit: "PIECES",
price: .value.netTotal,
priceMarkup: 0,
taxRate: .value.vatPercent,
weight: (.value.weightKg // 0)
}]
} }
}
# API odrzuca jawny null w polach, które go nie przyjmują: puste pola pomijamy.
| walk(if type == "object" then with_entries(select(.value != null)) else . end)' "$order" >"$body"
# Klucz idempotencji z numeru zamówienia w sklepie: ponowienie nie utworzy drugiego zamówienia.
IDEMPOTENCY_KEY="sklep-zamowienie-$(jq -r .number "$order")" ./prynt-call.sh "$body" |
jq '.data.createOrder'
Okno terminala
./send-order.sh zamowienie-sklepu.json
  • Status zamówienia odczytasz zapytaniem order(id: ...) z zakresem orders:read. Zamiast pytać o każde zamówienie osobno, pobieraj co kilka minut tylko zmienione: zobacz Zamówienia do księgowości.
  • Opłacenie po złożeniu zamówienia zapisz mutacją updateOrder z paymentStatus: PAID. Samą wpłatę możesz dodatkowo zarejestrować mutacją createOrderPayment, ale ona nie zmienia paymentStatus zamówienia.
  • Pliki do druku od kupującego dołączysz do produktów zamówienia według przepisu Pliki do zamówienia.