대변전표 · 환불 명세
이미 발행한 청구서를 되돌리는 대변전표를 만듭니다. 원 문서를 가리키는 표기, 취소 도장 느낌의 투명도, 그리고 URL 대신 PDF 바이트를 그대로 받는 법을 배웁니다.
여기서 배우는 것
편집기로 만들기
- 청구서 템플릿을 복제해 시작합니다 — 대변전표는 같은 표에 부호만 반대인 문서입니다.
- 제목을 대변전표로 바꾸고, 그 아래에 원 청구서 번호를 적습니다. {{ upper(data.origin) }} 처럼 대문자로 통일해 두면 눈으로 대조하기 쉽습니다.
- 라벨을 하나 더 놓아 "취소" 를 큼직하게 적고, 투명도를 0.12 로 내립니다. 글자 위에 겹쳐도 본문을 가리지 않습니다.
- 겹치는 순서는 객체 트리의 위아래가 정합니다. 도장이 본문 위에 오도록 맨 앞으로 보냅니다.
- 금액은 음수로 들어옵니다. 통화 서식은 음수를 그 로케일 규칙대로 표시하므로 따로 손댈 것이 없습니다.
스키마 · API 로 만들기
- 회계 시스템이 파일을 바로 저장해야 한다면 export_type 을 "file" 로 보내십시오. 응답 본문이 곧 PDF 이고 content-type 은 application/pdf 입니다.
- "file" 은 POST /v1/create 에서만 씁니다. 비동기와 배치는 대답할 시점에 파일이 아직 없으므로 거절합니다.
- 그 밖의 값은 조용히 기본값으로 떨어뜨리지 않고 거절합니다 — 조용히 무시된 옵션은 오타를 영원히 감춥니다.
- upper · lower · trim · sub · replace · pad · split 같은 문자열 함수를 표현식에서 쓸 수 있습니다. replace 는 리터럴만 바꿉니다 — 정규식은 받지 않습니다.
완성 문서
아래 JSON 이 위 두 순서가 만드는 바로 그 문서입니다. 저희 테스트가 이 문서를 검증하고 경고 없이 렌더되는지 확인하므로, 그대로 붙여 넣어 쓰셔도 됩니다.
{
"v": 1,
"unit": "px",
"page": {
"size": "a4",
"orientation": "portrait",
"margin": {
"t": 48,
"r": 48,
"b": 56,
"l": 48
},
"header": "",
"footer": "{{ data.company }} · [[page]] / [[pages]]"
},
"fonts": [],
"styles": [
{
"id": "s_h1",
"fontFamily": "Liberation Sans",
"fontSize": 26,
"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": 13,
"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": 12,
"fontWeight": 400,
"italic": false,
"color": "#111111",
"bg": "transparent",
"align": "left",
"valign": "middle",
"lineHeight": 1.5,
"border": "none",
"radius": 0,
"opacity": 1
},
{
"id": "s_total",
"fontFamily": "Liberation Sans",
"fontSize": 18,
"fontWeight": 700,
"italic": false,
"color": "#111111",
"bg": "transparent",
"align": "right",
"valign": "top",
"lineHeight": 1.5,
"border": "none",
"radius": 0,
"opacity": 1
},
{
"id": "s_void",
"fontFamily": "Liberation Sans",
"fontSize": 96,
"fontWeight": 700,
"italic": false,
"color": "#d92d20",
"bg": "transparent",
"align": "center",
"valign": "top",
"lineHeight": 1.5,
"border": "none",
"radius": 0,
"opacity": 0.12
}
],
"nodes": [
{
"id": "n_title",
"parent": "",
"order": 0,
"type": "label",
"x": 0,
"y": 0,
"w": 460,
"h": 34,
"show": "",
"styleId": "s_h1",
"props": {
"text": "Credit 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_origin",
"parent": "",
"order": 1,
"type": "label",
"x": 0,
"y": 42,
"w": 460,
"h": 20,
"show": "",
"styleId": "s_l",
"props": {
"text": "Refund against invoice {{ upper(data.origin) }}",
"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": "Original"
},
{
"id": "n_items",
"parent": "",
"order": 2,
"type": "table",
"x": 0,
"y": 96,
"w": 697,
"h": 130,
"show": "",
"styleId": "s_tbl",
"props": {
"text": "",
"src": "",
"fit": "",
"thickness": 1,
"stroke": "#c9ced6",
"fill": "#f3f5f7",
"repeat": "",
"as": "",
"break": "",
"keep": "",
"padding": 0,
"padY": 6,
"padX": 8,
"align": "",
"valign": "middle",
"wrap": "",
"bind": "data.items",
"headerHeight": 28,
"rowHeight": 24,
"max": 100,
"columns": [
{
"header": "Refunded item",
"cell": "{{ item.name }}",
"width": 497,
"align": "left"
},
{
"header": "Amount",
"cell": "{{ format(item.amount, \"currency\", \"USD\", \"en-US\") }}",
"width": 200,
"align": "right"
}
],
"rows": 0,
"cells": [],
"chartKind": "",
"codeKind": "",
"ecc": "",
"fieldKind": "",
"fieldName": "",
"options": [],
"required": false,
"readonly": false,
"html": ""
},
"name": "Refunded items"
},
{
"id": "n_total",
"parent": "",
"order": 3,
"type": "label",
"x": 0,
"y": 240,
"w": 697,
"h": 26,
"show": "",
"styleId": "s_total",
"props": {
"text": "Refund total {{ format(sum(data.items, \"amount\"), \"currency\", \"USD\", \"en-US\") }}",
"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": "Total"
},
{
"id": "n_void",
"parent": "",
"order": 4,
"type": "label",
"x": 0,
"y": 300,
"w": 697,
"h": 120,
"show": "",
"styleId": "s_void",
"props": {
"text": "VOID",
"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": "Void stamp"
}
]
}요청 본문입니다. template 자리에 위 문서를, data 자리에 아래 데이터를 넣습니다.
{
"template": "(the document above)",
"data": {
"company": "brewmypdf",
"origin": "inv-2026-0042",
"items": [
{
"name": "Maintenance (3 months) refund",
"amount": -840
},
{
"name": "Over-billing adjustment",
"amount": -120
}
]
},
"export_type": "file"
}