← 모든 레시피

거래명세서 · 인수증

거래명세서를 백그라운드에서 만들고, 끝나면 알림을 받고, 거래처가 준 계약서 PDF 와 한 파일로 묶습니다. 비동기 렌더 · 웹훅 · 파일 업로드와 병합을 배웁니다.

이 템플릿 가져오기

여기서 배우는 것

  • api-async
  • api-webhook
  • pdfop-upload
  • pdfop-info
  • pdfop-merge
  • node-line

편집기로 만들기

  1. 거래 항목 표와 인수 확인 서명란을 넣습니다. 앞의 청구서·계약서에서 한 것과 같습니다.
  2. 문서 자체는 편집기에서 끝납니다. 이 레시피가 가르치는 것은 그 다음 — 만든 파일로 무엇을 하느냐입니다.

스키마 · API 로 만들기

  1. 문서가 크거나 부르는 쪽이 기다릴 수 없으면 POST /v1/create-async 를 씁니다. 작업 id 를 즉시 돌려주고 큐에서 렌더합니다.
  2. webhook 을 함께 보내면 끝났을 때 그 주소로 POST 를 한 번 보냅니다. https 만 받고, 사설·내부 주소는 큐에 넣기 전에 거절합니다 — 렌더 쿼터를 쓰기 전에 알려 드리기 위해서입니다.
  3. 알림 본문에는 내려받기 주소가 없습니다. 저희 주소는 그 자체가 권한이라, 알림에 실으면 그 알림을 본 쪽이 문서를 가져갑니다. 주소는 알림을 받은 뒤 여러분의 키로 GET /v1/jobs/{id} 를 불러 얻으십시오.
  4. 알림은 증거가 아닙니다 — 서명이 없고 누구나 같은 형식으로 POST 할 수 있습니다. "가서 확인하라" 는 신호로만 쓰고, 무엇이 참인지는 언제나 GET /v1/jobs/{id} 로 정하십시오.
  5. 거래처가 준 PDF 는 POST /v1/files 로 올립니다. 본문에 파일을 그대로 담고 content-type 만 application/pdf 로 주면 됩니다. 10MB 이하이면서 100쪽 이하여야 하고, 24시간 뒤 지워집니다. 쪽수를 따로 막는 이유가 있습니다 — 3,000쪽 PDF 가 600KB 도 안 될 수 있어서 용량만으로는 걸리지 않고, 그렇게 통과한 문서는 받는 분 휴대폰에서 열리지 않습니다.
  6. 붙이기 전에 GET /v1/files/{id} 로 무엇인지부터 보십시오 — 쪽수, 쪽마다의 크기와 회전, 암호 여부를 알려줍니다. 암호가 걸린 PDF 는 저희가 열 수 없습니다.
  7. POST /v1/pdf/merge 에 files 를 준 순서대로 이어 붙입니다. 한 번에 20개까지입니다.

만든 뒤에

거래처 PDF 올리기

POST /v1/files

요청
(the body is the PDF itself · content-type: application/pdf)

무엇인지 먼저 보기

GET /v1/files/file_01J…

한 파일로 묶기

POST /v1/pdf/merge

요청
{
  "files": [
    "file_01J…",
    "file_01K…"
  ]
}

완성 문서

아래 JSON 이 위 두 순서가 만드는 바로 그 문서입니다. 저희 테스트가 이 문서를 검증하고 경고 없이 렌더되는지 확인하므로, 그대로 붙여 넣어 쓰셔도 됩니다.

예시
{
  "v": 1,
  "unit": "px",
  "page": {
    "size": "a4",
    "orientation": "portrait",
    "margin": {
      "t": 48,
      "r": 48,
      "b": 56,
      "l": 48
    },
    "header": "{{ data.company }}",
    "footer": "{{ data.no }} · [[page]] / [[pages]]"
  },
  "fonts": [],
  "styles": [
    {
      "id": "s_h1",
      "fontFamily": "Liberation Sans",
      "fontSize": 22,
      "fontWeight": 700,
      "italic": false,
      "color": "#111111",
      "bg": "transparent",
      "align": "left",
      "valign": "top",
      "lineHeight": 1.5,
      "border": "none",
      "radius": 0,
      "opacity": 1
    },
    {
      "id": "s_l",
      "fontFamily": "Liberation Sans",
      "fontSize": 12,
      "fontWeight": 400,
      "italic": false,
      "color": "#333333",
      "bg": "transparent",
      "align": "left",
      "valign": "top",
      "lineHeight": 1.5,
      "border": "none",
      "radius": 0,
      "opacity": 1
    },
    {
      "id": "s_tbl",
      "fontFamily": "Liberation Sans",
      "fontSize": 11,
      "fontWeight": 400,
      "italic": false,
      "color": "#111111",
      "bg": "transparent",
      "align": "left",
      "valign": "middle",
      "lineHeight": 1.5,
      "border": "none",
      "radius": 0,
      "opacity": 1
    }
  ],
  "nodes": [
    {
      "id": "n_title",
      "parent": "",
      "order": 0,
      "type": "label",
      "x": 0,
      "y": 0,
      "w": 400,
      "h": 28,
      "show": "",
      "styleId": "s_h1",
      "props": {
        "text": "Delivery note",
        "src": "",
        "fit": "",
        "thickness": 0,
        "stroke": "",
        "fill": "",
        "repeat": "",
        "as": "",
        "break": "",
        "keep": "",
        "padding": 0,
        "padY": 0,
        "padX": 0,
        "align": "",
        "valign": "",
        "wrap": "",
        "bind": "",
        "headerHeight": 0,
        "rowHeight": 0,
        "max": 0,
        "columns": [],
        "rows": 0,
        "cells": [],
        "chartKind": "",
        "codeKind": "",
        "ecc": "",
        "fieldKind": "",
        "fieldName": "",
        "options": [],
        "required": false,
        "readonly": false,
        "html": ""
      },
      "name": "Title"
    },
    {
      "id": "n_meta",
      "parent": "",
      "order": 1,
      "type": "label",
      "x": 0,
      "y": 34,
      "w": 697,
      "h": 18,
      "show": "",
      "styleId": "s_l",
      "props": {
        "text": "{{ data.customer }} · {{ data.no }}",
        "src": "",
        "fit": "",
        "thickness": 0,
        "stroke": "",
        "fill": "",
        "repeat": "",
        "as": "",
        "break": "",
        "keep": "",
        "padding": 0,
        "padY": 0,
        "padX": 0,
        "align": "",
        "valign": "",
        "wrap": "",
        "bind": "",
        "headerHeight": 0,
        "rowHeight": 0,
        "max": 0,
        "columns": [],
        "rows": 0,
        "cells": [],
        "chartKind": "",
        "codeKind": "",
        "ecc": "",
        "fieldKind": "",
        "fieldName": "",
        "options": [],
        "required": false,
        "readonly": false,
        "html": ""
      },
      "name": "Customer"
    },
    {
      "id": "n_items",
      "parent": "",
      "order": 2,
      "type": "table",
      "x": 0,
      "y": 76,
      "w": 697,
      "h": 180,
      "show": "",
      "styleId": "s_tbl",
      "props": {
        "text": "",
        "src": "",
        "fit": "",
        "thickness": 1,
        "stroke": "#c9ced6",
        "fill": "#f3f5f7",
        "repeat": "",
        "as": "",
        "break": "",
        "keep": "",
        "padding": 0,
        "padY": 5,
        "padX": 8,
        "align": "",
        "valign": "middle",
        "wrap": "",
        "bind": "data.items",
        "headerHeight": 26,
        "rowHeight": 22,
        "max": 100,
        "columns": [
          {
            "header": "Item",
            "cell": "{{ item.name }}",
            "width": 397,
            "align": "left"
          },
          {
            "header": "Qty",
            "cell": "{{ item.qty }}",
            "width": 120,
            "align": "right"
          },
          {
            "header": "Amount",
            "cell": "{{ format(item.amount, \"currency\", \"USD\", \"en-US\") }}",
            "width": 180,
            "align": "right"
          }
        ],
        "rows": 0,
        "cells": [],
        "chartKind": "",
        "codeKind": "",
        "ecc": "",
        "fieldKind": "",
        "fieldName": "",
        "options": [],
        "required": false,
        "readonly": false,
        "html": ""
      },
      "name": "Line items"
    },
    {
      "id": "n_rule",
      "parent": "",
      "order": 3,
      "type": "line",
      "x": 397,
      "y": 300,
      "w": 300,
      "h": 2,
      "show": "",
      "styleId": "s_l",
      "props": {
        "text": "",
        "src": "",
        "fit": "",
        "thickness": 1,
        "stroke": "#111111",
        "fill": "",
        "repeat": "",
        "as": "",
        "break": "",
        "keep": "",
        "padding": 0,
        "padY": 0,
        "padX": 0,
        "align": "",
        "valign": "",
        "wrap": "",
        "bind": "",
        "headerHeight": 0,
        "rowHeight": 0,
        "max": 0,
        "columns": [],
        "rows": 0,
        "cells": [],
        "chartKind": "",
        "codeKind": "",
        "ecc": "",
        "fieldKind": "",
        "fieldName": "",
        "options": [],
        "required": false,
        "readonly": false,
        "html": ""
      },
      "name": "Signature rule"
    },
    {
      "id": "n_ack",
      "parent": "",
      "order": 4,
      "type": "label",
      "x": 397,
      "y": 306,
      "w": 300,
      "h": 18,
      "show": "",
      "styleId": "s_l",
      "props": {
        "text": "Received by (signature)",
        "src": "",
        "fit": "",
        "thickness": 0,
        "stroke": "",
        "fill": "",
        "repeat": "",
        "as": "",
        "break": "",
        "keep": "",
        "padding": 0,
        "padY": 0,
        "padX": 0,
        "align": "",
        "valign": "",
        "wrap": "",
        "bind": "",
        "headerHeight": 0,
        "rowHeight": 0,
        "max": 0,
        "columns": [],
        "rows": 0,
        "cells": [],
        "chartKind": "",
        "codeKind": "",
        "ecc": "",
        "fieldKind": "",
        "fieldName": "",
        "options": [],
        "required": false,
        "readonly": false,
        "html": ""
      },
      "name": "Acknowledgement"
    }
  ]
}

요청 본문입니다. template 자리에 위 문서를, data 자리에 아래 데이터를 넣습니다.

요청
{
  "template": "(the document above)",
  "data": {
    "company": "BrewMyPDF",
    "customer": "Hanbit Corp.",
    "no": "DN-2026-0311",
    "items": [
      {
        "name": "Mug",
        "qty": 12,
        "amount": 180
      },
      {
        "name": "Sticker set",
        "qty": 40,
        "amount": 120
      }
    ]
  },
  "webhook": "https://example.com/hooks/BrewMyPDF"
}