Sklep albo portal klienta dołącza do zamówienia pliki do druku, a drukarnia sprawdza je w Prynt. Ten przepis wysyła plik do produktu albo do całego zamówienia i pobiera pliki zamówienia z powrotem, np. żeby pokazać je klientowi.
Czego potrzebujesz
Dział zatytułowany „Czego potrzebujesz”- Zakres
files:writedo wysyłania plików ifiles:readdo ich odczytu i pobierania. - Zakres
orders:read. Plik dołączasz do zamówienia albo produktu, które klucz widzi, a listę plików czytasz w zapytaniachorderiproduct. - Klienta API z przepisu Obsługa błędów i ponowień. Kod niżej wysyła żądania przez niego.
Jak działa wysyłka
Dział zatytułowany „Jak działa wysyłka”Treść pliku nie przechodzi przez API GraphQL: idzie prosto do magazynu plików, pod podpisany adres od Prynt.
-
Rozpocznij wysyłkę mutacją
initiateFileUpload. Podaj, do czego dołączasz plik (entityType:PRODUCTdla pliku do druku produktu alboORDERdla pliku całego zamówienia, ientityId), nazwę pliku, jego typ (mimeType) i dokładny rozmiar w bajtach (sizeBytes). Prynt zwracafileId, adresuploadUrl, metodęmethod, nagłówkirequiredHeadersi czasexpiresAt. -
Wyślij treść pliku jednym żądaniem metodą
methodnauploadUrl, z nagłówkami zrequiredHeadersi dokładniesizeBytesbajtami. Nie wysyłaj klucza API: adres jest podpisany i działa bez niego, a nagłówekAuthorizationpsuje podpis. -
Zakończ wysyłkę mutacją
completeFileUploadzfileId. Prynt mierzy plik w magazynie i dopiero wtedy pokazuje go w zamówieniu. Plik produktu, który nie jest jeszcze w produkcji (NO_FILES,FILES_TO_CHECK,WAITING_FOR_ADMIN_RESPONSE,WAITING_FOR_CLIENT_RESPONSE,FILES_ACCEPTED), trafia do sprawdzenia: produkt przechodzi w statusFILES_TO_CHECK, a jego pliki mająverificationStatus: TO_CHECK. Produkt w produkcji i dalej zostaje w swoim statusie.
- Adres wysyłki działa do
expiresAt(domyślnie godzinę). Nieudane wysłanie treści możesz powtórzyć pod ten sam adres przed tym czasem. Później zacznij od nowa odinitiateFileUpload: niezakończona wysyłka nie pojawi się w Prynt. - Zakończenie z kluczem idempotencji. Plik kończy się raz: drugie
completeFileUploadbez klucza kończy się błędem. Kod wysyła je z kluczemzakoncz-<fileId>, więc ponowienie zwraca zapisaną odpowiedź. - Typy i rozmiar. Prynt domyślnie przyjmuje formaty do druku i biurowe, do 5 GB w jednym
żądaniu. W
mimeTypepodaj jeden z typów:application/pdf,image/png,image/jpeg,image/tiff,image/webp,image/svg+xml,application/postscript,application/illustrator,application/x-indesign,application/vnd.adobe.photoshop,application/zip,application/x-rar-compressed,text/csvalbo typ pliku XLSX lub DOCX. Inny typ API odrzuca błędem walidacji, a komunikat błędu podaje listę typów, które przyjmuje Twoja drukarnia. - Nowy plik cofa akceptację. Plik dodany do produktu z zaakceptowanymi plikami
(
FILES_ACCEPTED) przestawia go z powrotem naFILES_TO_CHECK: drukarnia sprawdza pliki jeszcze raz. Poprawki wysyłaj tylko wtedy, gdy drukarnia o nie prosi. - Tylko zamówienia i produkty, bez archiwum. Klucz API dołącza pliki wyłącznie do
ORDERiPRODUCT, a nie do zarchiwizowanego zamówienia ani jego produktów. - Pobieranie przez podpisany adres.
downloadUrldziała bez klucza API przez ograniczony czas (domyślnie godzinę). Nie zapisuj go na później: po nowy adres zapytaj ponownie o plik. Plik bezdownloadUrl(pole może być puste) kod pomija, a katalog docelowy zakłada sam.
Przykłady pokazują adres zastępczy. Wpisz adres swojej drukarni, żeby go podmienić.
Wymaga curl, jq, uuidgen i skryptu prynt-call.sh z przepisu
Obsługa błędów i ponowień w tym samym katalogu.
#!/usr/bin/env bash# order-files.sh — pliki zamówienia (curl, jq; prynt-call.sh z przepisu „Obsługa błędów i ponowień”).# Wysyłka: ./order-files.sh upload PRODUCT <id produktu> ulotka-a5.pdf application/pdf# Pobranie: ./order-files.sh download <id zamówienia> <katalog>set -euo pipefail
body=$(mktemp) result=$(mktemp)trap 'rm -f "$body" "$result"' EXIT
upload() { local entity_type=$1 entity_id=$2 path=$3 mime_type=$4 jq -n --arg type "$entity_type" --arg id "$entity_id" --arg name "$(basename "$path")" \ --arg mime "$mime_type" --argjson size "$(wc -c <"$path" | tr -d ' ')" '{ query: "mutation StartUpload($input: InitiateUploadInput!) { initiateFileUpload(input: $input) { fileId uploadUrl method requiredHeaders expiresAt } }", variables: { input: { entityType: $type, entityId: $id, filename: $name, mimeType: $mime, sizeBytes: $size } } }' >"$body" IDEMPOTENCY_KEY=$(uuidgen) ./prynt-call.sh "$body" >"$result"
local file_id url method file_id=$(jq -r '.data.initiateFileUpload.fileId' "$result") url=$(jq -r '.data.initiateFileUpload.uploadUrl' "$result") method=$(jq -r '.data.initiateFileUpload.method' "$result") # Bez klucza API i dokładnie z nagłówkami od Prynt. local headers=() while IFS= read -r header; do headers+=(-H "$header"); done < <( jq -r '.data.initiateFileUpload.requiredHeaders // "{}" | fromjson | to_entries[] | "\(.key): \(.value)"' "$result" ) curl -sS --fail -X "$method" "${headers[@]}" --upload-file "$path" "$url"
jq -n --arg id "$file_id" '{ query: "mutation CompleteUpload($input: CompleteUploadInput!) { completeFileUpload(input: $input) { id originalFilename sizeBytes uploadStatus verificationStatus } }", variables: { input: { fileId: $id } } }' >"$body" IDEMPOTENCY_KEY="zakoncz-$file_id" ./prynt-call.sh "$body" | jq '.data.completeFileUpload'}
download() { local order_id=$1 directory=$2 jq -n --arg id "$order_id" '{ query: "query OrderFiles($id: ID!) { order(id: $id) { files { id originalFilename mimeType sizeBytes downloadUrl } products { number name files { id originalFilename mimeType sizeBytes downloadUrl } } } }", variables: { id: $id } }' >"$body" ./prynt-call.sh "$body" >"$result" mkdir -p "$directory" # Podpisany adres działa bez klucza API przez ograniczony czas: pobieraj od razu. Plik bez # zapisanej treści nie ma adresu: nie ma czego pobrać. jq -r '.data.order | (.files // []) + ([.products[]?.files // []] | add // []) | .[] | select(.downloadUrl != null) | "\(.id)-\(.originalFilename)\t\(.downloadUrl)"' "$result" | while IFS=$'\t' read -r name url; do curl -sS --fail -o "$directory/$name" "$url" echo "$directory/$name" done}
case ${1:-} in upload | download) "$@" ;; *) echo "Użycie: $0 upload <ORDER|PRODUCT> <id> <plik> <typ MIME> | download <id zamówienia> <katalog>" >&2; exit 2 ;;esac./order-files.sh upload PRODUCT "$PRODUCT_ID" ulotka-a5.pdf application/pdf./order-files.sh download "$ORDER_ID" pliki-zamowienia// order-files.mjs — Node.js 18+, klient API z przepisu „Obsługa błędów i ponowień”import { randomUUID } from 'node:crypto';import { mkdir, readFile, writeFile } from 'node:fs/promises';import path from 'node:path';import { pryntRequest } from './prynt-client.mjs';
const START_UPLOAD = `mutation StartUpload($input: InitiateUploadInput!) { initiateFileUpload(input: $input) { fileId uploadUrl method requiredHeaders expiresAt }}`;
const COMPLETE_UPLOAD = `mutation CompleteUpload($input: CompleteUploadInput!) { completeFileUpload(input: $input) { id originalFilename sizeBytes uploadStatus verificationStatus }}`;
const ORDER_FILES = `query OrderFiles($id: ID!) { order(id: $id) { files { id originalFilename mimeType sizeBytes downloadUrl } products { number name files { id originalFilename mimeType sizeBytes downloadUrl } } }}`;
/** * Dołącza plik z dysku do produktu (`entityType: 'PRODUCT'`, plik do druku) albo do całego * zamówienia (`'ORDER'`) w trzech krokach: Prynt podaje podpisany adres, treść idzie prosto do * magazynu plików, a `completeFileUpload` kończy wysyłkę. */export async function uploadFile({ entityType, entityId, path: filePath, mimeType }) { const content = await readFile(filePath); const { initiateFileUpload: upload } = await pryntRequest({ query: START_UPLOAD, variables: { input: { entityType, entityId, filename: path.basename(filePath), mimeType, sizeBytes: content.length, }, }, idempotencyKey: randomUUID(), }); // Bez klucza API i z nagłówkami od Prynt. Content-Length fetch ustawia sam z treści pliku. const headers = Object.entries(JSON.parse(upload.requiredHeaders ?? '{}')).filter( ([name]) => name.toLowerCase() !== 'content-length', ); const sent = await fetch(upload.uploadUrl, { method: upload.method, headers, body: content }); if (!sent.ok) throw new Error(`Wysyłka treści pliku: HTTP ${sent.status}`); const { completeFileUpload } = await pryntRequest({ query: COMPLETE_UPLOAD, variables: { input: { fileId: upload.fileId } }, idempotencyKey: `zakoncz-${upload.fileId}`, }); return completeFileUpload;}
/** Pobiera do katalogu `dir` (zakłada go) pliki zamówienia i jego produktów; zwraca pobrane. */export async function downloadOrderFiles(orderId, dir) { const { order } = await pryntRequest({ query: ORDER_FILES, variables: { id: orderId } }); if (!order) throw new Error(`Nie ma zamówienia ${orderId}`); const files = [...(order.files ?? []), ...(order.products ?? []).flatMap((p) => p.files ?? [])] // Plik bez zapisanej treści nie ma adresu: nie ma czego pobrać. .filter((file) => file.downloadUrl); await mkdir(dir, { recursive: true }); for (const file of files) { // Podpisany adres działa bez klucza API przez ograniczony czas: pobieraj od razu. const response = await fetch(file.downloadUrl); if (!response.ok) throw new Error(`Pobieranie ${file.originalFilename}: HTTP ${response.status}`); const target = path.join(dir, `${file.id}-${file.originalFilename}`); await writeFile(target, Buffer.from(await response.arrayBuffer())); } return files;}import { downloadOrderFiles, uploadFile } from './order-files.mjs';
const file = await uploadFile({ entityType: 'PRODUCT', entityId: productId, // id produktu z odpowiedzi createOrder path: 'ulotka-a5.pdf', mimeType: 'application/pdf',});const files = await downloadOrderFiles(orderId, 'pliki-zamowienia');# order_files.py — Python 3.9+, biblioteka requests, klient API z przepisu „Obsługa błędów i ponowień”import jsonimport uuidfrom pathlib import Path
import requests
from prynt_client import prynt_request
START_UPLOAD = """mutation StartUpload($input: InitiateUploadInput!) { initiateFileUpload(input: $input) { fileId uploadUrl method requiredHeaders expiresAt }}"""
COMPLETE_UPLOAD = """mutation CompleteUpload($input: CompleteUploadInput!) { completeFileUpload(input: $input) { id originalFilename sizeBytes uploadStatus verificationStatus }}"""
ORDER_FILES = """query OrderFiles($id: ID!) { order(id: $id) { files { id originalFilename mimeType sizeBytes downloadUrl } products { number name files { id originalFilename mimeType sizeBytes downloadUrl } } }}"""
def upload_file(entity_type, entity_id, path, mime_type): """Dołącza plik z dysku do produktu ("PRODUCT", plik do druku) albo całego zamówienia ("ORDER"): podpisany adres od Prynt, treść prosto do magazynu plików, potem completeFileUpload.""" content = Path(path).read_bytes() upload = prynt_request( START_UPLOAD, {"input": { "entityType": entity_type, "entityId": entity_id, "filename": Path(path).name, "mimeType": mime_type, "sizeBytes": len(content), }}, idempotency_key=str(uuid.uuid4()), )["initiateFileUpload"] # Bez klucza API i dokładnie z nagłówkami od Prynt. headers = json.loads(upload["requiredHeaders"] or "{}") sent = requests.request(upload["method"], upload["uploadUrl"], headers=headers, data=content, timeout=300) sent.raise_for_status() return prynt_request( COMPLETE_UPLOAD, {"input": {"fileId": upload["fileId"]}}, idempotency_key=f"zakoncz-{upload['fileId']}", )["completeFileUpload"]
def download_order_files(order_id, directory): """Pobiera do katalogu (zakłada go) pliki zamówienia i jego produktów; zwraca pobrane.""" order = prynt_request(ORDER_FILES, {"id": order_id})["order"] if order is None: raise LookupError(f"Nie ma zamówienia {order_id}") files = list(order["files"] or []) for product in order["products"] or []: files.extend(product["files"] or []) # Plik bez zapisanej treści nie ma adresu: nie ma czego pobrać. files = [file for file in files if file["downloadUrl"]] Path(directory).mkdir(parents=True, exist_ok=True) for file in files: # Podpisany adres działa bez klucza API przez ograniczony czas: pobieraj od razu. response = requests.get(file["downloadUrl"], timeout=300) response.raise_for_status() (Path(directory) / f"{file['id']}-{file['originalFilename']}").write_bytes(response.content) return filesfrom order_files import download_order_files, upload_file
# product_id: id produktu z odpowiedzi createOrderfile = upload_file("PRODUCT", product_id, "ulotka-a5.pdf", "application/pdf")files = download_order_files(order_id, "pliki-zamowienia")<?php// OrderFiles.php — PHP 8.1+ z rozszerzeniem curl, klient API z przepisu „Obsługa błędów i ponowień”require_once __DIR__ . '/PryntClient.php';
final class OrderFiles{ private const START_UPLOAD = <<<'GRAPHQL' mutation StartUpload($input: InitiateUploadInput!) { initiateFileUpload(input: $input) { fileId uploadUrl method requiredHeaders expiresAt } } GRAPHQL;
private const COMPLETE_UPLOAD = <<<'GRAPHQL' mutation CompleteUpload($input: CompleteUploadInput!) { completeFileUpload(input: $input) { id originalFilename sizeBytes uploadStatus verificationStatus } } GRAPHQL;
private const ORDER_FILES = <<<'GRAPHQL' query OrderFiles($id: ID!) { order(id: $id) { files { id originalFilename mimeType sizeBytes downloadUrl } products { number name files { id originalFilename mimeType sizeBytes downloadUrl } } } } GRAPHQL;
public function __construct(private readonly PryntClient $prynt = new PryntClient()) { }
/** * Dołącza plik z dysku do produktu ('PRODUCT', plik do druku) albo całego zamówienia ('ORDER'): * podpisany adres od Prynt, treść prosto do magazynu plików, potem completeFileUpload. */ public function upload(string $entityType, string $entityId, string $path, string $mimeType): array { $content = file_get_contents($path); $upload = $this->prynt->request(self::START_UPLOAD, ['input' => [ 'entityType' => $entityType, 'entityId' => $entityId, 'filename' => basename($path), 'mimeType' => $mimeType, 'sizeBytes' => strlen($content), ]], bin2hex(random_bytes(16)))['initiateFileUpload'];
// Bez klucza API i dokładnie z nagłówkami od Prynt. $headers = []; foreach (json_decode($upload['requiredHeaders'] ?? '{}', true) as $name => $value) { $headers[] = "$name: $value"; } $this->transfer($upload['uploadUrl'], [ CURLOPT_CUSTOMREQUEST => $upload['method'], CURLOPT_HTTPHEADER => $headers, CURLOPT_POSTFIELDS => $content, ]);
return $this->prynt->request( self::COMPLETE_UPLOAD, ['input' => ['fileId' => $upload['fileId']]], "zakoncz-{$upload['fileId']}", )['completeFileUpload']; }
/** Pobiera do katalogu (zakłada go) pliki zamówienia i jego produktów; zwraca pobrane. */ public function download(string $orderId, string $directory): array { $order = $this->prynt->request(self::ORDER_FILES, ['id' => $orderId])['order'] ?? throw new RuntimeException("Nie ma zamówienia $orderId"); $files = $order['files'] ?? []; foreach ($order['products'] ?? [] as $product) { array_push($files, ...($product['files'] ?? [])); } // Plik bez zapisanej treści nie ma adresu: nie ma czego pobrać. $files = array_values(array_filter($files, fn (array $file): bool => !empty($file['downloadUrl']))); if (!is_dir($directory) && !mkdir($directory, 0777, true) && !is_dir($directory)) { throw new RuntimeException("Nie można założyć katalogu $directory"); } foreach ($files as $file) { // Podpisany adres działa bez klucza API przez ograniczony czas: pobieraj od razu. $content = $this->transfer($file['downloadUrl'], []); file_put_contents("$directory/{$file['id']}-{$file['originalFilename']}", $content); } return $files; }
/** Żądanie do magazynu plików (bez klucza API); błąd HTTP rzuca wyjątek. */ private function transfer(string $url, array $options): string { $curl = curl_init($url); curl_setopt_array($curl, $options + [CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 300]); $body = curl_exec($curl); $status = curl_getinfo($curl, CURLINFO_RESPONSE_CODE); curl_close($curl); if ($body === false || $status >= 300) { throw new RuntimeException("Magazyn plików: HTTP $status"); } return $body; }}<?phprequire __DIR__ . '/OrderFiles.php';
$files = new OrderFiles();// $productId: id produktu z odpowiedzi createOrder$file = $files->upload('PRODUCT', $productId, 'ulotka-a5.pdf', 'application/pdf');$downloaded = $files->download($orderId, __DIR__ . '/pliki-zamowienia');Co dalej
Dział zatytułowany „Co dalej”- Nowy plik zmienia
Order.updatedAt, więc zamówienie z nowym plikiem, także dodanym przez drukarnię, znajdziesz synchronizacją przyrostową. Zobacz Zamówienia do księgowości. - Nowej wersji pliku nie wyślesz przez API, a listy
fileszamówienia i produktu pokazują tylko pierwszą wersję każdego pliku (versionNumber: 1,parentFileId: null). Gdy drukarnia doda w Prynt nową wersję, klucz dalej widzi i pobiera pierwszą. Poprawiony plik wysyłaj jako nowy plik.