Zamówienie ze sklepu internetowego
Dla: Programista
Przykłady na tej stronie używają adresu . Zmień adres
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.
Czego potrzebujesz
Dział zatytułowany „Czego potrzebujesz”-
Klucz z zakresem
orders:write. Żeby później czytać status zamówienia, dodajorders: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 środowiskowejPRYNT_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 zakresorders: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ówclients:readiclients: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). - 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 (
-
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
updateOrderiupdateProduct. Inna treść z tym samym kluczem kończy się błędemAPI_IDEMPOTENCY_KEY_REUSED. - Zapisz
idz 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.
priceto cena netto całej pozycji, czyli wszystkich sztuk razem, a nie cena za sztukę.taxRatepodajesz w procentach (23), choć w odpowiedziachProduct.taxRatejest ułamkiem (0.23); wartość między 0 a 1 API odrzuca jako ułamek wysłany przez pomyłkę. Uwaga:taxRatekosztu 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
nullw polu, które go nie przyjmuje, API odrzuca. - Status prowadzi drukarnia. Zamówienie zaczyna jako
CREATED, a każdy jego produkt jakoNO_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 doSENT:updateOrderprzyjmuje tylko kolejny dozwolony status (np.CREATED→ACCEPTED, gdy zamówienie ma handlowca, albo anulowanieCANCELLED) 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:createOrderkończy się błędem walidacji, a zamówienie trzeba wyjaśnić z drukarnią.
Kupujący jako klient w Prynt
Dział zatytułowany „Kupujący jako klient w Prynt”Z PRYNT_BUYERS_AS_CLIENTS=1 kod przed zamówieniem znajduje kupującego w Prynt albo go zakłada
(funkcja ensureClient):
-
Szuka po identyfikatorze kupującego w sklepie. Klient założony przez sklep ma go w polu
externalRef, a filtrexternalReflistyclientsznajduje 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. -
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
taxIdentificationNumberszuka 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. -
Nikogo nie znalazł: zakłada klienta mutacją
createClientz kluczem idempotencjisklep-klient-<id kupującego>. Firma malegalNamei 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):
{ "number": "WWW-1042", "paid": true, "customer": { "id": 1042, "firstName": "Anna", "lastName": "Nowak", "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.
#!/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=$1body=$(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); ficlient=${own:-${PRYNT_SHOP_CLIENT_ID:-}}if [[ -z $client ]]; then echo "Brak PRYNT_SHOP_CLIENT_ID: id klienta sklepu w Prynt." >&2 exit 1fion_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'./send-order.sh zamowienie-sklepu.json// send-order.mjs — Node.js 18+, klient API z przepisu „Obsługa błędów i ponowień”import { pryntRequest } from './prynt-client.mjs';
const FIND_SHOP_CUSTOMER = `query FindShopCustomer($ref: String!) { clients(filter: { externalRef: $ref }) { items { id isActive } }}`;
const FIND_SHOP_COMPANY = `query FindShopCompany($nip: String!) { clients(filter: { taxIdentificationNumber: $nip }) { items { id taxIdentificationNumber externalRef isActive } }}`;
const LINK_SHOP_CUSTOMER = `mutation LinkShopCustomer($id: ID!, $ref: String!) { updateClient(id: $id, input: { externalRef: $ref }) { id }}`;
const CREATE_SHOP_CUSTOMER = `mutation CreateShopCustomer($input: CreateClientInput!) { createClient(input: $input) { id }}`;
const CREATE_ORDER = `mutation CreateOrder($input: CreateOrderInput!) { createOrder(input: $input) { id displayId products { id number name } }}`;
/** API odrzuca jawny null w polach, które go nie przyjmują: puste pola pomijamy. */function withoutEmpty(value) { if (Array.isArray(value)) return value.map(withoutEmpty); if (value === null || typeof value !== 'object') return value; return Object.fromEntries( Object.entries(value) .filter(([, item]) => item !== null && item !== undefined) .map(([key, item]) => [key, withoutEmpty(item)]), );}
/** NIP jako same cyfry: Prynt porównuje NIP dosłownie, więc tak go zapisujemy i szukamy. */const nipDigits = (nip) => nip?.replaceAll(/\D/g, '');
/** Telefon bez spacji, nawiasów i myślników: Prynt przyjmuje najwyżej 15 znaków. */const compactPhone = (phone) => phone?.replaceAll(/[\s()-]/g, '');
/** Kupujący jako nowy klient w Prynt: firma z NIP albo osoba prywatna. */export function customerInput(customer) { const { company } = customer; return withoutEmpty({ type: company ? 'COMPANY' : 'INDIVIDUAL', legalName: company?.name, taxIdentificationNumber: nipDigits(company?.nip), firstName: company ? undefined : customer.firstName, lastName: company ? undefined : customer.lastName, email: customer.email, phone: compactPhone(customer.phone), // Identyfikator kupującego w sklepie: po nim znajdziesz go przy następnym zamówieniu. externalRef: String(customer.id), });}
/** * Klient w Prynt dla kupującego, utworzony, jeśli go nie ma — albo `null`, gdy zamówienie ma * trafić na klienta sklepu: * 1. po identyfikatorze ze sklepu (`externalRef`): `null`, 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: wpisany w formularzu nie dowodzi, że kupujący działa * w imieniu firmy, więc niezweryfikowany kupujący z NIP firmy, którą drukarnia już ma, dostaje * `null`. Firmie bez identyfikatora ze sklepu dopisujemy go; firma połączona już z innym * kupującym zostaje przy nim (jedna firma, wielu kupujących); * 3. nikt nie pasuje: nowy klient, raz na kupującego. */export async function ensureClient(customer) { const ref = String(customer.id); const found = await pryntRequest({ query: FIND_SHOP_CUSTOMER, variables: { ref } }); const own = found.clients.items[0]; if (own) return own.isActive ? own.id : null; const nip = nipDigits(customer.company?.nip); if (nip) { const byNip = await pryntRequest({ query: FIND_SHOP_COMPANY, variables: { nip } }); // Filtr szuka fragmentu numeru: bierzemy tylko klienta z całym tym samym NIP. const company = byNip.clients.items.find((client) => client.taxIdentificationNumber === nip); if (company) { if (customer.company.verified !== true || !company.isActive) return null; if (company.externalRef === null) { await pryntRequest({ query: LINK_SHOP_CUSTOMER, variables: { id: company.id, ref } }); } return company.id; } } const data = await pryntRequest({ query: CREATE_SHOP_CUSTOMER, variables: { input: customerInput(customer) }, idempotencyKey: `sklep-klient-${ref}`, }); return data.createClient.id;}
/** Dane do faktury kupującego firmy, gdy zamówienie trafia na klienta sklepu. */function invoiceInput({ customer, billingAddress }) { const { company } = customer; if (!company) return undefined; return { companyName: company.name, nip: nipDigits(company.nip), street: billingAddress && `${billingAddress.street} ${billingAddress.buildingNumber ?? ''}`.trim(), zip: billingAddress?.zip, city: billingAddress?.city, country: billingAddress?.country, };}
/** * Zamówienie ze sklepu jako wejście `createOrder` dla klienta `clientId` w Prynt. Na kliencie * sklepu (`onShopClient`) kupujący nie ma karty w Prynt: dane jego firmy idą do faktury. */export function orderInput(shopOrder, clientId, onShopClient = false) { const { customer, address } = shopOrder; return withoutEmpty({ clientId, totalPallets: 0, totalPackages: 1, transportMethod: address ? 'COURIER' : 'PICKUP', paymentMethod: 'ONLINE_PREPAID', paymentStatus: shopOrder.paid ? 'PAID' : 'AWAITING_PAYMENT', contact: { firstName: customer.firstName, lastName: customer.lastName, email: customer.email, phone: customer.phone, }, invoice: onShopClient ? invoiceInput(shopOrder) : undefined, deliveries: address ? [ { contactName: `${customer.firstName} ${customer.lastName}`, street: address.street, buildingNumber: address.buildingNumber, zip: address.zip, city: address.city, country: address.country, phone: customer.phone, }, ] : [], products: shopOrder.items.map((item, index) => ({ number: index + 1, name: item.name, description: item.description, quantity: item.quantity, unit: 'PIECES', price: item.netTotal, // cena netto całej pozycji, nie za sztukę priceMarkup: 0, taxRate: item.vatPercent, // w procentach: 23 weight: item.weightKg ?? 0, })), });}
/** * Tworzy zamówienie w Prynt raz na zamówienie sklepu: klucz idempotencji wynika z numeru * zamówienia w sklepie, więc ponowienie (także po restarcie procesu, w ciągu 24 h) zwraca * to samo zamówienie zamiast tworzyć drugie. Zapisz zwrócone `id` w sklepie. Klient: jeden na * wszystkie zamówienia sklepu (`PRYNT_SHOP_CLIENT_ID`, id z adresu jego karty w Prynt), a z * `PRYNT_BUYERS_AS_CLIENTS=1` kupujący jako klient w Prynt (`ensureClient`) — klient sklepu * zostaje dla kupujących, których nie można zapisać na ich własnego klienta. */export async function sendOrder(shopOrder) { const own = process.env.PRYNT_BUYERS_AS_CLIENTS === '1' ? await ensureClient(shopOrder.customer) : null; const clientId = own ?? process.env.PRYNT_SHOP_CLIENT_ID; if (!clientId) throw new Error('Brak PRYNT_SHOP_CLIENT_ID: id klienta sklepu w Prynt.'); const data = await pryntRequest({ query: CREATE_ORDER, variables: { input: orderInput(shopOrder, clientId, own === null) }, idempotencyKey: `sklep-zamowienie-${shopOrder.number}`, }); return data.createOrder;}import { sendOrder } from './send-order.mjs';
// shopOrder: zamówienie ze sklepu, np. z powiadomienia o złożonym zamówieniuconst order = await sendOrder(shopOrder);await saveInShop(shopOrder.number, order.id); // Twoja funkcja: zapisz id zamówienia w Prynt# send_order.py — Python 3.9+, klient API z przepisu „Obsługa błędów i ponowień”import osimport re
from prynt_client import prynt_request
# Klient w Prynt, na którego zapisujesz zamówienia sklepu (id z adresu jego karty w Prynt).SHOP_CLIENT_ID = os.environ.get("PRYNT_SHOP_CLIENT_ID")# "1": kupujący jako klienci w Prynt (ensure_client); klient sklepu zostaje dla kupujących,# których nie można zapisać na ich własnego klienta.BUYERS_AS_CLIENTS = os.environ.get("PRYNT_BUYERS_AS_CLIENTS") == "1"
FIND_SHOP_CUSTOMER = """query FindShopCustomer($ref: String!) { clients(filter: { externalRef: $ref }) { items { id isActive } }}"""
FIND_SHOP_COMPANY = """query FindShopCompany($nip: String!) { clients(filter: { taxIdentificationNumber: $nip }) { items { id taxIdentificationNumber externalRef isActive } }}"""
LINK_SHOP_CUSTOMER = """mutation LinkShopCustomer($id: ID!, $ref: String!) { updateClient(id: $id, input: { externalRef: $ref }) { id }}"""
CREATE_SHOP_CUSTOMER = """mutation CreateShopCustomer($input: CreateClientInput!) { createClient(input: $input) { id }}"""
CREATE_ORDER = """mutation CreateOrder($input: CreateOrderInput!) { createOrder(input: $input) { id displayId products { id number name } }}"""
def without_empty(value): """API odrzuca jawny null w polach, które go nie przyjmują: puste pola pomijamy.""" if isinstance(value, dict): return {key: without_empty(item) for key, item in value.items() if item is not None} if isinstance(value, list): return [without_empty(item) for item in value] return value
def nip_digits(nip): """NIP jako same cyfry: Prynt porównuje NIP dosłownie, więc tak go zapisujemy i szukamy.""" return re.sub(r"\D", "", nip) if nip else None
def compact_phone(phone): """Telefon bez spacji, nawiasów i myślników: Prynt przyjmuje najwyżej 15 znaków.""" return re.sub(r"[\s()-]", "", phone) if phone else None
def customer_input(customer): """Kupujący jako nowy klient w Prynt: firma z NIP albo osoba prywatna.""" company = customer.get("company") return without_empty({ "type": "COMPANY" if company else "INDIVIDUAL", "legalName": company["name"] if company else None, "taxIdentificationNumber": nip_digits(company.get("nip")) if company else None, "firstName": None if company else customer.get("firstName"), "lastName": None if company else customer.get("lastName"), "email": customer.get("email"), "phone": compact_phone(customer.get("phone")), # Identyfikator kupującego w sklepie: po nim znajdziesz go przy następnym zamówieniu. "externalRef": str(customer["id"]), })
def ensure_client(customer): """Klient w Prynt dla kupującego, utworzony, jeśli go nie ma — albo None, gdy zamówienie ma trafić na klienta sklepu: 1) po identyfikatorze ze sklepu (None, 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 None. Firmie bez identyfikatora ze sklepu dopisujemy go; firma połączona już z innym kupującym zostaje przy nim; 3) inaczej nowy, raz na kupującego.""" ref = str(customer["id"]) found = prynt_request(FIND_SHOP_CUSTOMER, {"ref": ref})["clients"]["items"] if found: return found[0]["id"] if found[0]["isActive"] else None nip = nip_digits((customer.get("company") or {}).get("nip")) if nip: by_nip = prynt_request(FIND_SHOP_COMPANY, {"nip": nip})["clients"]["items"] # Filtr szuka fragmentu numeru: bierzemy tylko klienta z całym tym samym NIP. company = next((client for client in by_nip if client["taxIdentificationNumber"] == nip), None) if company: if customer["company"].get("verified") is not True or not company["isActive"]: return None if company["externalRef"] is None: prynt_request(LINK_SHOP_CUSTOMER, {"id": company["id"], "ref": ref}) return company["id"] data = prynt_request( CREATE_SHOP_CUSTOMER, {"input": customer_input(customer)}, idempotency_key=f"sklep-klient-{ref}", ) return data["createClient"]["id"]
def invoice_input(shop_order): """Dane do faktury kupującego firmy, gdy zamówienie trafia na klienta sklepu.""" company = shop_order["customer"].get("company") if not company: return None billing = shop_order.get("billingAddress") or {} street = f"{billing['street']} {billing.get('buildingNumber') or ''}".strip() if billing else None return { "companyName": company["name"], "nip": nip_digits(company.get("nip")), "street": street, "zip": billing.get("zip"), "city": billing.get("city"), "country": billing.get("country"), }
def order_input(shop_order, client_id, on_shop_client=False): """Zamówienie ze sklepu jako wejście createOrder dla klienta client_id w Prynt. Na kliencie sklepu (on_shop_client) kupujący nie ma karty w Prynt: dane jego firmy idą do faktury.""" customer, address = shop_order["customer"], shop_order.get("address") deliveries = [] if address: deliveries.append({ "contactName": f"{customer['firstName']} {customer['lastName']}", "street": address["street"], "buildingNumber": address["buildingNumber"], "zip": address["zip"], "city": address["city"], "country": address["country"], "phone": customer.get("phone"), }) return without_empty({ "clientId": client_id, "totalPallets": 0, "totalPackages": 1, "transportMethod": "COURIER" if address else "PICKUP", "paymentMethod": "ONLINE_PREPAID", "paymentStatus": "PAID" if shop_order["paid"] else "AWAITING_PAYMENT", "contact": { "firstName": customer["firstName"], "lastName": customer["lastName"], "email": customer.get("email"), "phone": customer.get("phone"), }, "invoice": invoice_input(shop_order) if on_shop_client else None, "deliveries": deliveries, "products": [ { "number": index + 1, "name": item["name"], "description": item.get("description"), "quantity": item["quantity"], "unit": "PIECES", "price": item["netTotal"], # cena netto całej pozycji, nie za sztukę "priceMarkup": 0, "taxRate": item["vatPercent"], # w procentach: 23 "weight": item.get("weightKg") or 0, } for index, item in enumerate(shop_order["items"]) ], })
def send_order(shop_order): """Tworzy zamówienie w Prynt raz na zamówienie sklepu (klucz idempotencji z numeru zamówienia w sklepie). Zapisz zwrócone id w sklepie.""" own = ensure_client(shop_order["customer"]) if BUYERS_AS_CLIENTS else None client_id = own or SHOP_CLIENT_ID if not client_id: raise ValueError("Brak PRYNT_SHOP_CLIENT_ID: id klienta sklepu w Prynt.") data = prynt_request( CREATE_ORDER, {"input": order_input(shop_order, client_id, own is None)}, idempotency_key=f"sklep-zamowienie-{shop_order['number']}", ) return data["createOrder"]from send_order import send_order
order = send_order(shop_order) # zamówienie ze sklepu jako słowniksave_in_shop(shop_order["number"], order["id"]) # Twoja funkcja: zapisz id zamówienia w Prynt<?php// SendOrder.php — PHP 8.1+, klient API z przepisu „Obsługa błędów i ponowień”require_once __DIR__ . '/PryntClient.php';
final class SendOrder{ private const FIND_SHOP_CUSTOMER = <<<'GRAPHQL' query FindShopCustomer($ref: String!) { clients(filter: { externalRef: $ref }) { items { id isActive } } } GRAPHQL;
private const FIND_SHOP_COMPANY = <<<'GRAPHQL' query FindShopCompany($nip: String!) { clients(filter: { taxIdentificationNumber: $nip }) { items { id taxIdentificationNumber externalRef isActive } } } GRAPHQL;
private const LINK_SHOP_CUSTOMER = <<<'GRAPHQL' mutation LinkShopCustomer($id: ID!, $ref: String!) { updateClient(id: $id, input: { externalRef: $ref }) { id } } GRAPHQL;
private const CREATE_SHOP_CUSTOMER = <<<'GRAPHQL' mutation CreateShopCustomer($input: CreateClientInput!) { createClient(input: $input) { id } } GRAPHQL;
private const CREATE_ORDER = <<<'GRAPHQL' mutation CreateOrder($input: CreateOrderInput!) { createOrder(input: $input) { id displayId products { id number name } } } GRAPHQL;
public function __construct(private readonly PryntClient $prynt = new PryntClient()) { }
/** API odrzuca jawny null w polach, które go nie przyjmują: puste pola pomijamy. */ private static function withoutEmpty(array $value): array { $kept = []; foreach ($value as $key => $item) { if ($item !== null) { $kept[$key] = is_array($item) ? self::withoutEmpty($item) : $item; } } return $kept; }
/** NIP jako same cyfry: Prynt porównuje NIP dosłownie, więc tak go zapisujemy i szukamy. */ private static function nipDigits(?string $nip): ?string { return $nip === null ? null : preg_replace('/\D/', '', $nip); }
/** Telefon bez spacji, nawiasów i myślników: Prynt przyjmuje najwyżej 15 znaków. */ private static function compactPhone(?string $phone): ?string { return $phone === null ? null : preg_replace('/[\s()-]/', '', $phone); }
/** Kupujący jako nowy klient w Prynt: firma z NIP albo osoba prywatna. */ public static function customerInput(array $customer): array { $company = $customer['company'] ?? null; return self::withoutEmpty([ 'type' => $company === null ? 'INDIVIDUAL' : 'COMPANY', 'legalName' => $company['name'] ?? null, 'taxIdentificationNumber' => self::nipDigits($company['nip'] ?? null), 'firstName' => $company === null ? ($customer['firstName'] ?? null) : null, 'lastName' => $company === null ? ($customer['lastName'] ?? null) : null, 'email' => $customer['email'] ?? null, 'phone' => self::compactPhone($customer['phone'] ?? null), // Identyfikator kupującego w sklepie: po nim znajdziesz go przy następnym zamówieniu. 'externalRef' => (string) $customer['id'], ]); }
/** * Klient w Prynt dla kupującego, utworzony, jeśli go nie ma — albo null, gdy zamówienie ma * trafić na klienta sklepu: 1) po identyfikatorze ze sklepu (null, 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 null. Firmie bez identyfikatora ze sklepu dopisujemy go; * firma połączona już z innym kupującym zostaje przy nim; 3) inaczej nowy, raz na kupującego. */ public function ensureClient(array $customer): ?string { $ref = (string) $customer['id']; $found = $this->prynt->request(self::FIND_SHOP_CUSTOMER, ['ref' => $ref])['clients']['items']; if ($found !== []) { return $found[0]['isActive'] ? $found[0]['id'] : null; } $nip = self::nipDigits($customer['company']['nip'] ?? null); if ($nip !== null && $nip !== '') { $byNip = $this->prynt->request(self::FIND_SHOP_COMPANY, ['nip' => $nip])['clients']['items']; // Filtr szuka fragmentu numeru: bierzemy tylko klienta z całym tym samym NIP. foreach ($byNip as $company) { if ($company['taxIdentificationNumber'] !== $nip) { continue; } if (($customer['company']['verified'] ?? false) !== true || !$company['isActive']) { return null; } if ($company['externalRef'] === null) { $this->prynt->request(self::LINK_SHOP_CUSTOMER, ['id' => $company['id'], 'ref' => $ref]); } return $company['id']; } } return $this->prynt->request( self::CREATE_SHOP_CUSTOMER, ['input' => self::customerInput($customer)], "sklep-klient-{$ref}", )['createClient']['id']; }
/** Dane do faktury kupującego firmy, gdy zamówienie trafia na klienta sklepu. */ private static function invoiceInput(array $shopOrder): ?array { $company = $shopOrder['customer']['company'] ?? null; if ($company === null) { return null; } $billing = $shopOrder['billingAddress'] ?? null; return [ 'companyName' => $company['name'], 'nip' => self::nipDigits($company['nip'] ?? null), 'street' => $billing === null ? null : trim("{$billing['street']} " . ($billing['buildingNumber'] ?? '')), 'zip' => $billing['zip'] ?? null, 'city' => $billing['city'] ?? null, 'country' => $billing['country'] ?? null, ]; }
/** * Zamówienie ze sklepu jako wejście createOrder dla klienta $clientId w Prynt. Na kliencie * sklepu ($onShopClient) kupujący nie ma karty w Prynt: dane jego firmy idą do faktury. */ public static function orderInput(array $shopOrder, string $clientId, bool $onShopClient = false): array { $customer = $shopOrder['customer']; $address = $shopOrder['address'] ?? null; $deliveries = $address === null ? [] : [[ 'contactName' => "{$customer['firstName']} {$customer['lastName']}", 'street' => $address['street'], 'buildingNumber' => $address['buildingNumber'], 'zip' => $address['zip'], 'city' => $address['city'], 'country' => $address['country'], 'phone' => $customer['phone'] ?? null, ]]; $products = []; foreach ($shopOrder['items'] as $index => $item) { $products[] = [ 'number' => $index + 1, 'name' => $item['name'], 'description' => $item['description'] ?? null, 'quantity' => $item['quantity'], 'unit' => 'PIECES', 'price' => $item['netTotal'], // cena netto całej pozycji, nie za sztukę 'priceMarkup' => 0, 'taxRate' => $item['vatPercent'], // w procentach: 23 'weight' => $item['weightKg'] ?? 0, ]; } return self::withoutEmpty([ 'clientId' => $clientId, 'totalPallets' => 0, 'totalPackages' => 1, 'transportMethod' => $address === null ? 'PICKUP' : 'COURIER', 'paymentMethod' => 'ONLINE_PREPAID', 'paymentStatus' => $shopOrder['paid'] ? 'PAID' : 'AWAITING_PAYMENT', 'contact' => [ 'firstName' => $customer['firstName'], 'lastName' => $customer['lastName'], 'email' => $customer['email'] ?? null, 'phone' => $customer['phone'] ?? null, ], 'invoice' => $onShopClient ? self::invoiceInput($shopOrder) : null, 'deliveries' => $deliveries, 'products' => $products, ]); }
/** * Tworzy zamówienie raz na zamówienie sklepu; zapisz zwrócone id w sklepie. Klient: jeden na * wszystkie zamówienia (PRYNT_SHOP_CLIENT_ID, id z adresu jego karty w Prynt), a z * PRYNT_BUYERS_AS_CLIENTS=1 kupujący jako klient w Prynt (ensureClient) — klient sklepu * zostaje dla kupujących, których nie można zapisać na ich własnego klienta. */ public function send(array $shopOrder): array { $own = getenv('PRYNT_BUYERS_AS_CLIENTS') === '1' ? $this->ensureClient($shopOrder['customer']) : null; $clientId = $own ?? (getenv('PRYNT_SHOP_CLIENT_ID') ?: null); if ($clientId === null) { throw new RuntimeException('Brak PRYNT_SHOP_CLIENT_ID: id klienta sklepu w Prynt.'); } $data = $this->prynt->request( self::CREATE_ORDER, ['input' => self::orderInput($shopOrder, $clientId, $own === null)], "sklep-zamowienie-{$shopOrder['number']}", ); return $data['createOrder']; }}<?phprequire __DIR__ . '/SendOrder.php';
$order = (new SendOrder())->send($shopOrder); // zamówienie ze sklepu jako tablicasaveInShop($shopOrder['number'], $order['id']); // Twoja funkcja: zapisz id zamówienia w PryntCo dalej
Dział zatytułowany „Co dalej”- Status zamówienia odczytasz zapytaniem
order(id: ...)z zakresemorders: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ą
updateOrderzpaymentStatus: PAID. Samą wpłatę możesz dodatkowo zarejestrować mutacjącreateOrderPayment, ale ona nie zmieniapaymentStatuszamówienia. - Pliki do druku od kupującego dołączysz do produktów zamówienia według przepisu Pliki do zamówienia.