Cookbook (Kumpulan Contoh Praktis)
Berbagai skenario ditampilkan sebagai ScriptDefinition yang lengkap. Untuk dasar sintaksisnya, lihat Katalog Statement dan Ekspresi nilai; untuk eksekusi dan batasan, lihat Semantik eksekusi, batasan, dan keamanan. Pada setiap contoh, nilai fields untuk penulisan berupa peta locale ({ "<locale>": nilai }), dan locale contoh diseragamkan menjadi en-US. Setiap contoh dijalankan secara inline pada jalur yang menangani permintaan panggilan, lalu mengembalikan hasilnya sebagai body respons panggilan itu. Pada contoh yang memiliki panggilan eksternal (Http, EmailSend), timeoutMs Statement tersebut ditambahkan ke anggaran waktu eksekusi (untuk Http, × (1 + retry)), dan jika Statement itu berada di dalam perulangan (Loop, ResourceForEach), nilainya dikalikan sebanyak batas atas iterasi (Anggaran waktu).
Daftar Isi
- CRUD Dasar: 1. Membuat dan mempublikasikan Content · 2. Update dengan nilai terkomputasi · 3. Kumpulkan daftar pesanan saya lalu kembalikan · 4. Baca satu, guard, lalu setujui
- Pencarian dan upsert: 5. slug upsert · 6. Kunci field dinamis dan patch locale
- API Eksternal: 7. Potong kredit di muka (CAS), panggilan LLM, refund · 8. URL gambar menjadi Media · 9. Gambar base64 menjadi Media · 10. Penanganan bersyarat setelah moderasi · 11. try/catch fallback · 12. Ringkasan dan tag dengan AI
- Paralel: 13. Menggabungkan setelah panggilan paralel · 14. Peninjauan pendaftaran
- Perulangan dan agregasi: 15. Membuat N dari input array · 16. penyemaian counted loop · 17. Penghapusan cascade · 18. Jumlah akumulasi loop · 19. Memproses seluruh item yang memenuhi kondisi secara massal · 20. Mengumpulkan id secara batch
- Saga dan konkurensi: 21. Saga pembayaran · 22. Kunci optimistis CAS
- Email: 23. Email notifikasi kepada pembeli tiap pesanan · 24. Template email yang disunting di Content Studio · 25. Newsletter yang ditulis pengelola
- Verifikasi tanda tangan: 26. Verifikasi tanda tangan webhook · 27. Verifikasi tanda tangan hash tanpa kunci
- Pencarian anggota: 28. Menemukan anggota lewat email lalu mengirim kupon dan email notifikasi
- Pengiriman file: 29. Mengirim foto produk ke layanan analisis gambar lalu mencatat hasilnya
- Transformasi file: 30. Salinan PDF e-book yang dibubuhi email pembeli
CRUD Dasar
1. Membuat dan mempublikasikan Content
{ "method": "Post",
"statements": [
{ "type": "ResourceCreate", "resource": "Content", "contentType": { "sys": { "id": "ct_post" } },
"fields": { "title": { "en-US": "{ /payload/fields/title }" }, "body": { "en-US": "{ /payload/fields/body }" } },
"publish": true, "name": "post" },
{ "type": "Return", "value": { "id": "{ /post/sys/id }" }, "statusCode": 201 } ] }2. Update dengan nilai terkomputasi (jumlah tampilan +1)
{ "method": "Post",
"statements": [
{ "type": "ResourcePatch", "resource": "Content", "target": { "sys": { "id": "{ /payload/sys/id }" } },
"fields": { "viewCount": { "en-US": { "$+": [ "{ /payload/fields/viewCount }", 1 ] } } } } ] }3. Kumpulkan daftar pesanan saya lalu kembalikan
{ "method": "Get",
"statements": [
{ "type": "SetVar", "var": "orders", "value": [] },
{ "type": "ResourceForEach", "resource": "Content", "contentType": { "sys": { "id": "ct_order" } },
"where": { "createdBy": ":self" }, "order": "-sys.createdAt", "from": "Current", "advanced": false,
"limit": 20, "name": "order",
"onEach": [
{ "type": "SetVar", "var": "orders", "value": { "$merge": [ "{ /vars/orders }", [ "{ /order }" ] ] } } ] },
{ "type": "Return", "value": { "orders": "{ /vars/orders }" } } ] }Dengan createdBy: ":self" ia menelusuri "hanya milik saya" dan mengumpulkan tiap item dengan SetVar lalu mengembalikannya. Karena ResourceForEach tidak mengikat hasil penelusuran sebagai koleksi, untuk mengembalikannya sebagai daftar Anda mengumpulkannya secara langsung seperti ini. Pada anggaran waktu, penelusuran dihitung sebagai waktu yang dideklarasikan onEach dikalikan jumlah item yang diproses. Di sini onEach tidak memuat panggilan eksternal sehingga waktu deklarasinya 0 dan anggaran dasar 30 detik menjadi batas yang sesungguhnya; jika Anda meletakkan panggilan eksternal pada onEach, perkalian itu masuk apa adanya ke anggaran, dan eksekusi terhenti begitu mencapai batas atas 180 detik (Anggaran waktu). Ketika Anda cukup membaca daftar lalu mengembalikannya, lebih baik memanggil langsung API daftar CDA/CMA dari frontend alih-alih menelusuri dengan Script.
4. Baca satu, guard, lalu setujui
{ "method": "Post",
"statements": [
{ "type": "ResourceRead", "resource": "Content", "target": { "sys": { "id": "{ /payload/fields/orderId }" } }, "name": "order" },
{ "type": "If", "condition": { "!=": [ "{ /order/fields/status/en-US }", "pending" ] },
"then": [ { "type": "Return", "value": { "reason": "not pending" }, "isError": true, "statusCode": 409 } ] },
{ "type": "ResourcePatch", "resource": "Content", "target": { "sys": { "id": "{ /order/sys/id }" } },
"fields": { "status": { "en-US": "approved" } }, "publish": true },
{ "type": "Return", "value": { "ok": true } } ] }Dengan mengikat satu item ke sebuah nama menggunakan ResourceRead, Anda dapat merujuknya langsung sebagai { /order/fields/... }. Jika tidak ada, pembacaan menghasilkan kesalahan (Anda bisa membungkusnya dengan Try).
Pencarian dan upsert
5. slug upsert (find-then-upsert)
{ "method": "Post",
"statements": [
{ "type": "ResourceFind", "resource": "Content", "contentType": { "sys": { "id": "ct_article" } },
"where": { "fields.slug": { "eq": "{ /payload/fields/slug }" } }, "name": "found" },
{ "type": "If", "condition": { "!!": "{ /found/sys/id }" },
"then": [
{ "type": "ResourcePatch", "resource": "Content", "target": { "sys": { "id": "{ /found/sys/id }" } },
"fields": { "body": { "en-US": "{ /payload/fields/body }" } } },
{ "type": "Return", "value": { "id": "{ /found/sys/id }", "op": "updated" } } ],
"else": [
{ "type": "ResourceCreate", "resource": "Content", "contentType": { "sys": { "id": "ct_article" } },
"fields": { "slug": { "en-US": "{ /payload/fields/slug }" }, "body": { "en-US": "{ /payload/fields/body }" } }, "name": "created" },
{ "type": "Return", "value": { "id": "{ /created/sys/id }", "op": "created" }, "statusCode": 201 } ] } ] }ResourceFind mengikat langsung kecocokan pertama (atau null jika tidak ada), dan { "!!": "{ /found/sys/id }" } bercabang berdasarkan ada tidaknya.
6. Kunci field dinamis dan patch locale dinamis
{ "method": "Patch",
"statements": [
{ "type": "ResourcePatch", "resource": "Content", "target": { "sys": { "id": "{ /payload/sys/id }" } },
"fields": { "{ /payload/fields/fieldKey }": { "{ /payload/fields/locale }": "{ /payload/fields/value }" } } } ] }Baik kunci field maupun kunci bucket locale sama-sama merupakan referensi { /ptr }. Gunakan ini saat Anda ingin menaruh terjemahan ke dalam bucket locale tertentu.
API Eksternal
7. Guard kredit, potong di muka (CAS), panggilan LLM, refund saat gagal (contoh utama)
{ "method": "Post",
"statements": [
{ "type": "ResourceFind", "resource": "Content", "contentType": { "sys": { "id": "ct_wallet" } },
"where": { "createdBy": ":self" }, "name": "wallet" },
{ "type": "If", "condition": { "<": [ "{ /wallet/fields/balance/en-US }", "{ /payload/fields/cost }" ] },
"then": [ { "type": "Return", "value": { "ok": false, "reason": "insufficient credit" }, "isError": true, "statusCode": 402 } ] },
{ "type": "Try",
"body": [
{ "type": "ResourcePatch", "resource": "Content", "target": { "sys": { "id": "{ /wallet/sys/id }" } },
"version": "{ /wallet/sys/version }",
"fields": { "balance": { "en-US": { "$-": [ "{ /wallet/fields/balance/en-US }", "{ /payload/fields/cost }" ] } } },
"name": "charged" } ],
"catch": [ { "type": "Return", "value": { "ok": false, "reason": "version conflict, coba lagi" }, "isError": true, "statusCode": 409 } ] },
{ "type": "Try",
"body": [
{ "type": "Http", "method": "POST", "url": "https://api.llm.com/v1/gen",
"headers": [ { "key": "Authorization", "value": "Bearer { /secrets/LLM_API_KEY }" } ],
"body": { "prompt": "{ /payload/fields/prompt }" }, "timeoutMs": 15000, "name": "resp" },
{ "type": "ResourceCreate", "resource": "Content", "contentType": { "sys": { "id": "ct_result" } },
"fields": { "text": { "en-US": "{ /resp/body/choices/0/message/content }" } }, "name": "out" },
{ "type": "Return", "value": { "ok": true, "id": "{ /out/sys/id }", "remaining": "{ /charged/fields/balance/en-US }" } } ],
"catch": [
{ "type": "ResourcePatch", "resource": "Content", "target": { "sys": { "id": "{ /wallet/sys/id }" } },
"fields": { "balance": { "en-US": { "$+": [ "{ /charged/fields/balance/en-US }", "{ /payload/fields/cost }" ] } } } },
{ "type": "Return", "value": { "ok": false, "reason": "generation failed, refunded" }, "isError": true, "statusCode": 502 } ] } ],
"secrets": [ { "key": "LLM_API_KEY", "value": "sk-...", "description": "API key untuk memanggil LLM" } ] }Guard memeriksa apakah saldo mencukupi terlebih dahulu, lalu memotong sebelum panggilan eksternal. Pemotongan itu memasang kunci optimistis (CAS) pada sys.version milik wallet. Jika eksekusi lain mengubah wallet di antara pembacaan saldo dan pemotongan, eksekusi di-abort karena versi tidak cocok dan catch mengembalikan 409. Karena tidak ada panggilan eksternal yang dilakukan, permintaan bersamaan tidak terkena pemotongan ganda. Hanya setelah pemotongan dipastikan, LLM baru dipanggil, dan jika panggilan itu gagal, catch menambahkan kembali jumlah yang dipotong (cost) untuk melakukan refund (kompensasi), lalu mengembalikan 502. Urutannya adalah memastikan penagihan sebelum panggilan eksternal yang tidak dapat dibatalkan, lalu melakukan kompensasi hanya saat gagal. API key LLM dideklarasikan di secrets dan dibaca di header sebagai { /secrets/LLM_API_KEY }. Untuk batasan kompensasi, lihat Tanpa transaksi dan kompensasi pada Semantik eksekusi.
8. Menjadikan gambar (URL) sebagai Media lalu melampirkannya ke Content
{ "method": "Post",
"statements": [
{ "type": "Http", "method": "POST", "url": "https://api.img.com/gen",
"headers": [ { "key": "x-api-key", "value": "{ /secrets/IMAGE_API_KEY }" } ],
"body": { "prompt": "{ /payload/fields/prompt }" }, "name": "gen" },
{ "type": "ResourceCreate", "resource": "Media",
"fields": { "title": { "en-US": "{ /payload/fields/prompt }" },
"file": { "en-US": { "source": "{ /gen/body/data/0/url }", "encoding": "url" } } }, "name": "img" },
{ "type": "ResourceCreate", "resource": "Content", "contentType": { "sys": { "id": "ct_artwork" } },
"fields": { "prompt": { "en-US": "{ /payload/fields/prompt }" }, "image": { "en-US": "{ /img/sys/id }" } } } ],
"secrets": [ { "key": "IMAGE_API_KEY", "value": "...", "description": "API key pembuatan gambar" } ] }Buat Media dengan sebuah name, dan ResourceCreate (Content) menaruh { /img/sys/id } ke dalam field referensi. Media memakai model fields yang sama dengan Content. file adalah instruksi ingest { source, encoding }. Penyerapan file tidak mendeklarasikan waktu sehingga diambil dari anggaran dasar 30 detik, dan tidak termasuk dalam batas panggilan eksternal per definisi (Batasan statis).
9. Gambar base64 menjadi Media
{ "method": "Post",
"statements": [
{ "type": "Http", "method": "POST", "url": "https://api.img.com/gen",
"headers": [ { "key": "x-api-key", "value": "{ /secrets/IMAGE_API_KEY }" } ],
"body": { "prompt": "{ /payload/fields/prompt }" }, "name": "gen" },
{ "type": "ResourceCreate", "resource": "Media",
"fields": { "file": { "en-US": { "source": "{ /gen/body/data/0/b64_json }", "encoding": "base64" } } } } ],
"secrets": [ { "key": "IMAGE_API_KEY", "value": "...", "description": "API key pembuatan gambar" } ] }10. Publikasi atau penghapusan bersyarat setelah moderasi
{ "method": "Post",
"statements": [
{ "type": "Http", "method": "POST", "url": "https://api.mod.com/check",
"headers": [ { "key": "x-api-key", "value": "{ /secrets/MODERATION_API_KEY }" } ],
"body": { "text": "{ /payload/fields/body }" }, "name": "mod" },
{ "type": "If", "condition": { "==": [ "{ /mod/body/flagged }", true ] },
"then": [ { "type": "ResourceDelete", "resource": "Content", "target": { "sys": { "id": "{ /payload/sys/id }" } } } ],
"else": [ { "type": "ResourcePublish", "resource": "Content", "target": { "sys": { "id": "{ /payload/sys/id }" } } } ] } ],
"secrets": [ { "key": "MODERATION_API_KEY", "value": "...", "description": "API key moderasi" } ] }11. try/catch: fallback saat kegagalan eksternal
{ "method": "Post",
"statements": [
{ "type": "Try",
"body": [
{ "type": "Http", "method": "POST", "url": "https://primary.api/gen",
"headers": [ { "key": "x-api-key", "value": "{ /secrets/GEN_API_KEY }" } ],
"body": { "prompt": "{ /payload/fields/prompt }" }, "timeoutMs": 8000, "name": "resp" },
{ "type": "ResourceCreate", "resource": "Content", "contentType": { "sys": { "id": "ct_result" } },
"fields": { "text": { "en-US": "{ /resp/body/text }" }, "source": { "en-US": "primary" } } } ],
"catch": [
{ "type": "ResourceCreate", "resource": "Content", "contentType": { "sys": { "id": "ct_result" } },
"fields": { "text": { "en-US": "Pembuatan gagal" }, "error": { "en-US": "{ /error/message }" }, "source": { "en-US": "fallback" } } } ] } ],
"secrets": [ { "key": "GEN_API_KEY", "value": "...", "description": "API key pembuatan" } ] }12. Mengisi ringkasan dan tag artikel dengan AI
{ "method": "Post",
"statements": [
{ "type": "Http", "method": "POST", "url": "https://api.llm.com/v1/gen",
"headers": [ { "key": "Authorization", "value": "Bearer { /secrets/LLM_API_KEY }" } ],
"body": { "prompt": "{ /payload/fields/body }", "response_format": { "type": "json_object" } },
"timeoutMs": 15000, "name": "resp" },
{ "type": "Try",
"body": [
{ "type": "ParseJson", "name": "ai", "value": "{ /resp/body/choices/0/message/content }" },
{ "type": "ResourcePatch", "resource": "Content", "target": { "sys": { "id": "{ /payload/sys/id }" } },
"fields": { "summary": { "en-US": "{ /ai/summary }" },
"tags": { "en-US": "{ /ai/tags }" } } },
{ "type": "Return", "value": { "ok": true, "tags": "{ /ai/tags }" } } ],
"catch": [
{ "type": "Return", "value": { "ok": false, "reason": "model did not return JSON" }, "isError": true, "statusCode": 502 } ] } ],
"secrets": [ { "key": "LLM_API_KEY", "value": "sk-...", "description": "API key untuk memanggil LLM" } ] }Begitu artikel dibuat, model mengisi ringkasan dan tag-nya (dipasang ke Content.Create sebagai aksi terhubung sebuah Webhook). Responsnya di sini datang dalam dua lapis. responseType pada Http default-nya Json, jadi envelope respons dari API sudah berupa objek, tetapi jawaban yang dibuat model ada di dalamnya, di choices/0/message/content, sebagai string. Karena itu satu lapis lagi dibuka dengan ParseJson sebelum nilainya bisa diambil sebagai { /ai/summary } dan { /ai/tags }. Tag ditulis apa adanya sebagai array ke field Array (elemen ShortText).
Meski kontrak dipasang dengan keluaran terstruktur (response_format), yang datang bukan JSON ketika respons terpotong oleh batas panjang atau model menolak permintaan. Karena itu parsing dibungkus Try, yang mengubah kegagalan parsing menjadi 502. Pesan kegagalan memuat teks yang coba di-parsing, sehingga terlihat apa yang kembali. Untuk API yang envelope-nya saja sudah bukan JSON, beri Http sebuah responseType: "Text" lalu serahkan { /resp/body } langsung (lihat Http dan ParseJson).
Paralel
13. Menggabungkan 2 panggilan eksternal paralel menjadi Content
{ "method": "Post",
"statements": [
{ "type": "Parallel", "branches": [
[ { "type": "Http", "method": "GET", "url": "https://api.a.com/x", "name": "a" } ],
[ { "type": "Http", "method": "GET", "url": "https://api.b.com/y", "name": "b" } ] ] },
{ "type": "ResourceCreate", "resource": "Content", "contentType": { "sys": { "id": "ct_merged" } },
"fields": { "left": { "en-US": "{ /a/body/value }" }, "right": { "en-US": "{ /b/body/value }" } } } ] }14. Peninjauan pendaftaran: skor paralel, lalu keputusan berbasis and
{ "method": "Post",
"statements": [
{ "type": "Parallel", "branches": [
[ { "type": "Http", "method": "POST", "url": "https://api.fraud.com/score",
"headers": [ { "key": "x-api-key", "value": "{ /secrets/FRAUD_API_KEY }" } ],
"body": { "email": "{ /payload/fields/email }" }, "name": "fraud" } ],
[ { "type": "Http", "method": "GET", "url": "https://api.credit.com/v1/{ /payload/fields/userId }/score",
"headers": [ { "key": "x-api-key", "value": "{ /secrets/CREDIT_API_KEY }" } ], "name": "credit" } ] ] },
{ "type": "If",
"condition": { "and": [ { "<": [ "{ /fraud/body/risk }", 0.5 ] }, { ">=": [ "{ /credit/body/score }", 700 ] } ] },
"then": [
{ "type": "ResourceCreate", "resource": "Content", "contentType": { "sys": { "id": "ct_account" } },
"fields": { "email": { "en-US": "{ /payload/fields/email }" }, "status": { "en-US": "approved" } } },
{ "type": "Return", "value": { "decision": "approved" }, "statusCode": 201 } ],
"else": [ { "type": "Return", "value": { "decision": "manual-review" }, "statusCode": 202 } ] } ],
"secrets": [ { "key": "FRAUD_API_KEY", "value": "...", "description": "API key skor penipuan" },
{ "key": "CREDIT_API_KEY", "value": "...", "description": "API key skor kredit" } ] }Anda juga dapat menyisipkan { /ptr } ke dalam jalur URL. Hasil branch dirujuk setelah join. Panggilan eksternalnya ada 2. Karena jumlah panggilan eksternal yang dapat dimuat satu definisi merupakan batas per paket (lihat Paket Harga), pastikan jumlahnya berada dalam batas itu.
Perulangan dan agregasi
Pada anggaran waktu, Loop dan ResourceForEach dihitung sebagai waktu yang dideklarasikan body (onEach) dikalikan batas atas iterasi. Contoh-contoh pada bagian ini tidak memuat panggilan eksternal di body-nya sehingga waktu deklarasinya 0, dan karena itu anggaran dasar 30 detik menjadi batas yang sesungguhnya. Di titik inilah perulangan benar-benar terhenti (Anggaran waktu, Loop).
15. N Content dari input array (Loop over)
{ "method": "Post",
"statements": [
{ "type": "Loop", "over": "{ /payload/fields/items }", "name": "item", "maxIterations": 100,
"body": [
{ "type": "ResourceCreate", "resource": "Content", "contentType": { "sys": { "id": "ct_item" } },
"fields": { "name": { "en-US": "{ /item/name }" }, "qty": { "en-US": "{ /item/qty }" } } } ] } ] }16. counted loop (for): menyemai slot
{ "method": "Post",
"statements": [
{ "type": "Loop", "for": { "from": 1, "to": 5 }, "name": "i", "maxIterations": 100,
"body": [
{ "type": "ResourceCreate", "resource": "Content", "contentType": { "sys": { "id": "ct_slot" } },
"fields": { "index": { "en-US": "{ /i }" }, "status": { "en-US": "open" } } } ] } ] }for mencakup from hingga to, inklusif (literal integer, step default-nya 1). name mengikat penghitung saat ini ke { /i }.
17. Penghapusan cascade (ForEach, Delete)
{ "method": "Delete",
"statements": [
{ "type": "ResourceForEach", "resource": "Content", "contentType": { "sys": { "id": "ct_comment" } },
"where": { "fields.postId": { "eq": "{ /payload/sys/id }" } }, "from": "Current", "advanced": false, "name": "comment",
"onEach": [ { "type": "ResourceDelete", "resource": "Content", "target": { "sys": { "id": "{ /comment/sys/id }" } } } ] },
{ "type": "ResourceDelete", "resource": "Content", "target": { "sys": { "id": "{ /payload/sys/id }" } } } ] }ResourceForEach memaginasi kecocokan secara internal dan menghapus tiap item, sehingga tanpa pagination manual ia menghapus semua komentar yang memenuhi kondisi (hingga batas atas platform) lalu menghapus postingannya sendiri. Karena onEach tidak mendeklarasikan waktu, anggaran waktu eksekusi definisi ini adalah 30 detik.
18. Akumulasi loop: penjumlahan SetVar
{ "method": "Post",
"statements": [
{ "type": "SetVar", "var": "total", "value": 0 },
{ "type": "Loop", "over": "{ /payload/fields/items }", "name": "row", "maxIterations": 100,
"body": [ { "type": "SetVar", "var": "total", "value": { "$+": [ "{ /vars/total }", "{ /row/qty }" ] } } ] },
{ "type": "ResourceCreate", "resource": "Content", "contentType": { "sys": { "id": "ct_summary" } },
"fields": { "totalQty": { "en-US": "{ /vars/total }" } } } ] }19. Memproses seluruh item yang memenuhi kondisi secara massal
{ "method": "Post",
"statements": [
{ "type": "ResourceForEach", "resource": "Content", "contentType": { "sys": { "id": "ct_post" } },
"where": { "fields.status": { "eq": "draft" } }, "order": "sys.createdAt,sys.id",
"from": "Current", "advanced": false, "name": "post",
"onEach": [
{ "type": "ResourcePublish", "resource": "Content", "target": { "sys": { "id": "{ /post/sys/id }" } } } ] } ] }Karena ResourceForEach memaginasi kecocokan secara internal, cursor loop (Loop while + akumulasi SetVar) tidak diperlukan. Ia menemukan semua draft yang memenuhi kondisi lalu mempublikasikan tiap item. Jika jumlahnya sangat banyak sehingga sulit diselesaikan seluruhnya, tetapkan batas atas yang diproses sekaligus dengan limit, dan jadikan where sebagai kondisi "belum diproses" agar dapat dilanjutkan lewat eksekusi ulang.
20. Mengumpulkan id secara batch dari daftar email (merge)
{ "method": "Post",
"statements": [
{ "type": "SetVar", "var": "ids", "value": [] },
{ "type": "SetVar", "var": "missing", "value": [] },
{ "type": "Loop", "over": "{ /payload/fields/emails }", "name": "email", "maxIterations": 100,
"body": [
{ "type": "ResourceFind", "resource": "Content", "contentType": { "sys": { "id": "ct_account" } },
"where": { "fields.email": { "eq": "{ /email }" } }, "name": "acc" },
{ "type": "If", "condition": { "!!": "{ /acc/sys/id }" },
"then": [ { "type": "SetVar", "var": "ids", "value": { "$merge": [ "{ /vars/ids }", [ "{ /acc/sys/id }" ] ] } } ],
"else": [ { "type": "SetVar", "var": "missing", "value": { "$merge": [ "{ /vars/missing }", [ "{ /email }" ] ] } } ] } ] },
{ "type": "ResourceCreate", "resource": "Content", "contentType": { "sys": { "id": "ct_campaign" } },
"fields": { "recipients": { "en-US": "{ /vars/ids }" }, "unresolved": { "en-US": "{ /vars/missing }" } } } ] }Pembacaan (ResourceFind) bukan panggilan eksternal, sehingga diizinkan di dalam body Loop. Keberadaan dan ketiadaan masing-masing diakumulasikan dengan merge.
Saga dan konkurensi
21. Saga pembayaran (Try/catch/finally)
{ "method": "Post",
"statements": [
{ "type": "ResourceCreate", "resource": "Content", "contentType": { "sys": { "id": "ct_order" } },
"fields": { "sku": { "en-US": "{ /payload/fields/sku }" }, "status": { "en-US": "reserved" } },
"publish": false, "name": "order" },
{ "type": "Try",
"body": [
{ "type": "Http", "method": "POST", "url": "https://api.pay.com/charge",
"headers": [ { "key": "Authorization", "value": "Bearer { /secrets/PAYMENT_API_KEY }" } ],
"body": { "amount": "{ /payload/fields/amount }", "ref": "{ /order/sys/id }" }, "timeoutMs": 10000, "name": "pay" },
{ "type": "ResourcePatch", "resource": "Content", "target": { "sys": { "id": "{ /order/sys/id }" } },
"fields": { "status": { "en-US": "paid" }, "txId": { "en-US": "{ /pay/body/transactionId }" } }, "publish": true },
{ "type": "Return", "value": { "orderId": "{ /order/sys/id }", "status": "paid" }, "statusCode": 201 } ],
"catch": [
{ "type": "ResourceDelete", "resource": "Content", "target": { "sys": { "id": "{ /order/sys/id }" } } },
{ "type": "Return", "value": { "reason": "payment failed", "detail": "{ /error/message }" }, "isError": true, "statusCode": 402 } ],
"finally": [
{ "type": "ResourceCreate", "resource": "Content", "contentType": { "sys": { "id": "ct_paylog" } },
"fields": { "orderRef": { "en-US": "{ /order/sys/id }" }, "amount": { "en-US": "{ /payload/fields/amount }" } } } ] } ],
"secrets": [ { "key": "PAYMENT_API_KEY", "value": "sk-...", "description": "Secret key API pembayaran" } ] }Setelah memesan (draft): saat pembayaran berhasil, ia mengonfirmasi, mempublikasikan, dan mengembalikan 201; saat gagal, catch menghapus pemesanan (kompensasi) dan mengembalikan 402; finally selalu mencatat log. Kompensasi berbasis penghapusan menghasilkan sys.id yang baru, sehingga termasuk dalam batasan di mana referensi menjadi rusak (lihat Tanpa transaksi dan kompensasi pada Semantik eksekusi).
22. Kunci optimistis CAS
{ "method": "Post",
"statements": [
{ "type": "ResourceFind", "resource": "Content", "contentType": { "sys": { "id": "ct_stock" } },
"where": { "fields.sku": { "eq": "{ /payload/fields/sku }" } }, "name": "stock" },
{ "type": "If", "condition": { "<": [ "{ /stock/fields/qty/en-US }", "{ /payload/fields/amount }" ] },
"then": [ { "type": "Return", "value": { "reason": "out of stock" }, "isError": true, "statusCode": 409 } ] },
{ "type": "Try",
"body": [
{ "type": "ResourcePatch", "resource": "Content", "target": { "sys": { "id": "{ /stock/sys/id }" } },
"version": "{ /stock/sys/version }",
"fields": { "qty": { "en-US": { "$-": [ "{ /stock/fields/qty/en-US }", "{ /payload/fields/amount }" ] } } } },
{ "type": "Return", "value": { "ok": true } } ],
"catch": [
{ "type": "Return", "value": { "reason": "version conflict, coba lagi" }, "isError": true, "statusCode": 409 } ] } ] }Baca stok untuk memperoleh sys.version terbaru, lalu potong dengan versi itu (version). Jika eksekusi lain mengubah nilai di antara pembacaan dan penulisan, eksekusi di-abort karena versi tidak cocok dan catch mengembalikan 409. Guard kehabisan stok berada di luar Try (pengembalian dini yang normal).
23. Email notifikasi kepada pembeli tiap pesanan (ForEach + EmailSend)
{ "method": "Post",
"statements": [
{ "type": "ResourceForEach", "resource": "Content", "contentType": { "sys": { "id": "ct_order" } },
"where": { "fields.notified": { "ne": true } }, "order": "sys.createdAt,sys.id",
"from": "Current", "advanced": false, "name": "order",
"onEach": [
{ "type": "EmailSend", "account": { "sys": { "id": "eml_orders" } },
"toServiceUser": { "sys": { "id": "{ /order/fields/buyer/en-US/sys/id }" } },
"subject": "Pengiriman telah dimulai",
"body": "<p>Pengiriman produk yang Anda pesan telah dimulai.</p>" },
{ "type": "ResourcePatch", "resource": "Content", "target": { "sys": { "id": "{ /order/sys/id }" } },
"fields": { "notified": { "en-US": true } } } ] } ] }Ia menelusuri pesanan yang belum dikirimi notifikasi (fields.notified yang bukan true), mengirim email kepada pembeli tiap pesanan, lalu segera menandai notified. Karena EmailSend hanya menerima 1 penerima per email, pengiriman banyak email dilakukan per item dengan ResourceForEach seperti ini (onEach dapat memuat panggilan eksternal). Jika diberikan lewat toServiceUser, alamat anggota tidak masuk ke ruang variabel Script melainkan di-resolve tepat sebelum pengiriman. Karena where dijadikan "belum diproses" dan penyelesaian ditandai di akhir onEach, meski terputus di tengah, eksekusi ulang akan melanjutkan dari pesanan yang tersisa (jika penandaan gagal tepat setelah efek samping berhasil, item itu dapat terkirim ganda pada eksekusi berikutnya; at-least-once).
24. Mengirim dengan template email yang disunting di Content Studio (templateBody)
{ "method": "Post",
"statements": [
{ "type": "ResourceRead", "resource": "Content", "name": "tpl",
"target": { "sys": { "id": "cnt_mail_shipping" } } },
{ "type": "ResourceRead", "resource": "Content", "name": "order",
"target": { "sys": { "id": "{ /payload/orderId }" } } },
{ "type": "EmailSend", "account": { "sys": { "id": "eml_orders" } },
"toServiceUser": { "sys": { "id": "{ /order/fields/buyer/en-US/sys/id }" } },
"subject": "Pengiriman telah dimulai (nomor pesanan { /order/sys/id })",
"templateBody": {
"source": "{ /tpl/fields/html/en-US }",
"data": { "name": "{ /order/fields/buyerName/en-US }",
"tracking": "{ /order/fields/trackingNo/en-US }" } } },
{ "type": "Return", "value": { "sent": true } } ] }Template diletakkan di Content (Field html pada cnt_mail_shipping) dan hanya bagian kosongnya yang diisi. Meskipun penanggung jawab pemasaran memperbaiki kalimatnya di Content Studio, Script tetap apa adanya. Templatenya berbentuk seperti ini.
<p>Halo { /name }, pengiriman produk yang Anda pesan telah dimulai.</p>
<p>Nomor resi: { /tracking }</p>source harus berupa satu pointer yang menunjuk Content yang dibaca definisi ini. Letakkan ResourceRead yang membaca template sebelum EmailSend. Karena Statement dijalankan sesuai urutan penulisannya, menunjuk nama yang belum dibaca membuat nilainya kosong dan eksekusi gagal. Yang dibaca bagian kosong pada template hanyalah data, sehingga template tidak dapat membaca /payload secara langsung. Bagian kosong yang menunjuk kunci yang tidak ada di data menjadi huruf kosong.
25. Newsletter yang ditulis pengelola untuk para pelanggan (autoEscape)
{ "method": "Post",
"statements": [
{ "type": "ResourceRead", "resource": "Content", "name": "issue",
"target": { "sys": { "id": "{ /payload/issueId }" } } },
{ "type": "ResourceRead", "resource": "Content", "name": "tpl",
"target": { "sys": { "id": "cnt_mail_newsletter" } } },
{ "type": "ResourceForEach", "resource": "Content", "contentType": { "sys": { "id": "ct_subscriber" } },
"where": { "fields.active": { "eq": true } }, "order": "sys.createdAt,sys.id",
"from": "Current", "advanced": false, "name": "sub",
"onEach": [
{ "type": "EmailSend", "account": { "sys": { "id": "eml_news" } },
"to": "{ /sub/fields/email/en-US }",
"subject": "{ /issue/fields/title/en-US }",
"autoEscape": false,
"templateBody": {
"source": "{ /tpl/fields/html/en-US }",
"data": { "article": "{ /issue/fields/bodyHtml/en-US }" } } } ] } ] }Body newsletter adalah HTML yang ditulis pengelola sebagai teks kaya, sehingga jika dibiarkan apa adanya tagnya akan terlihat sebagai huruf. Jika autoEscape disetel false, markup yang ada di dalam data akan dirender.
Buat cakupan penyetelan false itu sesempit mungkin. Pengaturan ini berlaku pada seluruh nilai yang diinterpolasi dalam satu email EmailSend tersebut. Karena itu, data pada contoh ini hanya memuat satu nilai article yang ditulis pengelola. Jika nilai yang datang dari luar, seperti nama pelanggan, dicampurkan ke data yang sama, markup di dalam nilai itu ikut dirender. Jika nilai semacam itu dibutuhkan, letakkan di sisi template atau pisahkan pengirimannya. Template itu sendiri pun selalu dirender mentah tanpa dipengaruhi autoEscape, jadi pastikan nilai yang masuk ke template maupun ke data hanya berasal dari tangan orang yang memiliki izin tulis pada Content tersebut.
Verifikasi tanda tangan
Penyedia pembayaran (PG, MoR) melampirkan tanda tangan pada body ketika mengirim webhook. Pihak penerima harus memastikan tanda tangan itu dapat direproduksi dengan kunci rahasia yang dimilikinya sebelum melakukan apa pun. Dua contoh di bawah adalah dua cara yang memang benar-benar berbeda. Yang satu membuat kode memakai kunci rahasia (keyed), yang lain menghitung digest dengan menyambung field-field dan kunci rahasia.
26. Verifikasi tanda tangan webhook (menguraikan header terbungkus, replay window)
{ "method": "Post",
"statements": [
{ "type": "Regex", "name": "sig", "mode": "Capture",
"pattern": "^t=(\\d+),v1=([0-9a-f]{64})$", "value": "{ /headers/x-provider-signature }" },
{ "type": "If", "condition": { "==": [ "{ /sig }", null ] },
"then": [ { "type": "Return", "value": { "ok": false, "reason": "malformed signature header" }, "isError": true, "statusCode": 400 } ] },
{ "type": "Signature", "name": "verified", "algorithm": "SHA256",
"secret": "{ /secrets/WEBHOOK_SIGNING_SECRET }",
"value": "{ /sig/1 }.{ /rawPayload }", "expected": "{ /sig/2 }" },
{ "type": "If", "condition": { "!": "{ /verified }" },
"then": [ { "type": "Return", "value": { "ok": false, "reason": "signature mismatch" }, "isError": true, "statusCode": 401 } ] },
{ "type": "If", "condition": { ">=": [ { "-": [ "{ /now/seconds }", "{ /sig/1 }" ] }, 300 ] },
"then": [ { "type": "Return", "value": { "ok": false, "reason": "timestamp outside the replay window" }, "isError": true, "statusCode": 401 } ] },
{ "type": "ResourceFind", "resource": "Content", "contentType": { "sys": { "id": "ct_order" } },
"where": { "fields.orderId": { "eq": "{ /payload/data/orderId }" } }, "name": "order" },
{ "type": "If", "condition": { "==": [ "{ /order }", null ] },
"then": [ { "type": "Return", "value": { "ok": false, "reason": "unknown order" }, "isError": true, "statusCode": 404 } ] },
{ "type": "ResourcePatch", "resource": "Content", "target": { "sys": { "id": "{ /order/sys/id }" } },
"fields": { "status": { "en-US": "paid" }, "paidAt": { "en-US": "{ /now/iso }" } }, "publish": true },
{ "type": "Return", "value": { "ok": true } } ],
"secrets": [ { "key": "WEBHOOK_SIGNING_SECRET", "value": "whsec_9f2c1b7ae4", "description": "Kunci tanda tangan webhook penyedia pembayaran" } ] }Karena penyedia mengirim timestamp dan kode bersama dalam satu header (t=1492774577,v1=<hex 64 karakter>), Anda tidak dapat menyusun pesan yang ditandatangani sebelum menguraikan header itu. Karena itu urutannya ditetapkan seperti ini.
CapturepadaRegexmenguraikan header menjadi{ /sig/1 }(timestamp) dan{ /sig/2 }(kode). Indeks0adalah keseluruhan kecocokan dan mulai dari1adalah grup tangkapan. Jika formatnya tidak sesuai,{ /sig }bernilainullsehingga permintaan dikembalikan di tempat dengan400.Signaturemenjadikan"<timestamp>.<body asli>"sebagai pesan, membuat kode, lalu membandingkannya dengan{ /sig/2 }. Intinya adalah mengambil pesan dari teks asli sebelum parsing ({ /rawPayload }). Jika/payloadyang sudah di-parsing dijadikan string kembali, spasi dan notasi angka akan dinormalkan sehingga tidak kembali menjadi byte yang ditandatangani pihak lain. Menempatkan dua pointer bersebelahan di dalam satu string akan langsung menyambungkannya, sehingga tidak perlu operator.- Jika
{ /verified }bernilaifalse, hasilnya401. Tanda tangan yang salah dan header yang tidak ada keduanya menjadi satu, yaitufalse(kita tidak memberi tahu pengirim bagian mana yang salah). - Meski tanda tangannya benar, permintaan yang sudah lama tetap ditolak.
{ /now/seconds }adalah waktu ketika eksekusi ini dimulai, jadi kita melihat apakah selisihnya dengan timestamp yang dibawa tanda tangan melampaui replay window (di sini 300 detik). Timestamp datang dari header sebagai string, tetapi operasi aritmetika mengubahnya menjadi angka. - Baru setelah lolos sampai sini, pesanan dicari dan statusnya diubah.
Karena tidak ada panggilan eksternal, tidak ada waktu yang dideklarasikan, sehingga eksekusinya selesai dalam anggaran dasar 30 detik dan penyedia menerima responsnya di tempat. Karena kunci tanda tangan dideklarasikan di secrets, WEEGLOO mengenkripsi dan menyimpannya dengan aman. Di Statement, kunci itu dibaca sebagai { /secrets/WEBHOOK_SIGNING_SECRET }. Namun, nilai aslinya terlihat ketika Script ini diambil, jadi persempit role yang dapat membaca Script ini (secrets pada Model keamanan).
Ada dua cara agar penyedia dapat memanggil pintu ini, dan penentunya adalah apakah penyedia itu dapat mengirim header kustom.
- Jika bisa mengirim, terbitkan token yang hanya memuat izin Execute untuk Script tersebut, minta penyedia menyertakannya pada header
Authorization, lalu memanggil/execute. Role yang dipersempit ke satu Script tertentu dibahas di izin script pada SpaceRole, dan tokennya di Space Access Token. Cara ini adalah yang utama. - Jika tidak bisa mengirim (penyedia yang hanya dapat mendaftarkan URL callback dan tidak memiliki pengaturan untuk melampirkan header), aktifkan
anonymousCallEnabledpada Script tersebut lalu daftarkan alamat/execute/anonymoussebagai callback. Saat itu eksekusinya memakai identitas penulis, sehinggaupdatedBydari pesanan yang diubah Script ini juga menjadi penulis, dan karena tidak ada autentikasi, verifikasi tanda tangan di atas menjadi satu-satunya autentikasi pintu ini. Syarat dan aturan penyimpanannya dibahas di Panggilan anonim.
Definisi di atas apa adanya sudah memenuhi syarat Script anonim. Ia tidak memakai filter createdBy: ":self", dan permintaan yang tidak lolos tanda tangan serta replay window diputus dengan Return sebelum menyentuh apa pun.
27. Verifikasi tanda tangan hash tanpa kunci
{ "method": "Post",
"statements": [
{ "type": "Hash", "name": "expectedSign", "algorithm": "SHA256", "encoding": "HexUpper",
"value": "{ /payload/orderId }{ /payload/amount }{ /secrets/MERCHANT_KEY }" },
{ "type": "If", "condition": { "!=": [ "{ /expectedSign }", "{ /payload/signature }" ] },
"then": [ { "type": "Return", "value": { "ok": false, "reason": "signature mismatch" }, "isError": true, "statusCode": 401 } ] },
{ "type": "ResourcePatch", "resource": "Content", "target": { "sys": { "id": "{ /payload/orderRef }" } },
"fields": { "status": { "en-US": "paid" } }, "publish": true },
{ "type": "Return", "value": { "ok": true } } ],
"secrets": [ { "key": "MERCHANT_KEY", "value": "9f2c1b7ae4", "description": "Kunci merchant yang dibagikan dengan penyedia pembayaran" } ] }Ini cara "menyambung field-field yang sudah ditentukan dengan kunci rahasia lalu menghitung SHA256", bukan HMAC. Hash tidak memiliki field secret. Kunci rahasia dideklarasikan di secrets, lalu ditulis sebagai { /secrets/MERCHANT_KEY } di dalam value pada posisi tempat skema itu meletakkan kunci. Setiap skema menempatkan kunci di depan, di belakang, atau di tengah, dan dengan menuliskannya seperti ini, posisi mana pun dapat dinyatakan.
encoding disesuaikan dengan notasi pihak lain (Hex, HexUpper, Base64, Base64Url). Berbeda dengan Signature, hasilnya berupa string sehingga Anda harus membandingkannya sendiri, dan perbandingan itu adalah perbandingan kesetaraan biasa. Karena batas atas value adalah 128 karakter, untuk skema yang menghitung atas seluruh body gunakan Signature.
Pencarian anggota
28. Menemukan anggota lewat email lalu mengirim kupon dan email notifikasi
{ "method": "Post",
"statements": [
{ "type": "ResourceFind", "resource": "ServiceUser",
"where": { "sys.email": { "eq": "{ /payload/fields/email }" } }, "name": "member" },
{ "type": "If", "condition": { "==": [ "{ /member }", null ] },
"then": [ { "type": "Return", "value": { "ok": false, "reason": "member not found" }, "isError": true, "statusCode": 404 } ] },
{ "type": "ResourceCreate", "resource": "Content", "contentType": { "sys": { "id": "ct_coupon" } },
"fields": { "code": { "en-US": "WELCOME-{ /member/sys/id }" },
"owner": { "en-US": "{ /member/sys/id }" } }, "publish": false, "name": "coupon" },
{ "type": "EmailSend", "account": { "sys": { "id": "eml_orders" } },
"toServiceUser": { "sys": { "id": "{ /member/sys/id }" } },
"subject": "Kupon Anda telah diterbitkan",
"body": "<p>Kupon { /coupon/fields/code/en-US } telah diberikan kepada { /member/nickname }.</p>" },
{ "type": "Return", "value": { "ok": true, "memberId": "{ /member/sys/id }" } } ] }Ia menemukan anggota hanya dengan satu email, lalu memakai sys.id-nya sebagai pemilik kupon dan penerima email. Aturan ketika membaca direktori anggota adalah sebagai berikut.
- Karena
sys.emaildisimpan terenkripsi, ia hanya menerima operator keluarga kecocokan persis (eq,ne,in,nin). Jika Anda memberi operator lain sepertiprefix, yang terjadi bukan 0 hasil secara senyap melainkan eksekusi yang gagal. - Jika tidak ada kecocokan,
ResourceFindmengikatnull, sehingga percabangan keberadaannya berbentuk sama seperti ketika mencari Content. - Berbeda dengan Content dan Media, field anggota bukan peta locale. Rujuk apa adanya seperti
{ /member/nickname }. - Ketika mengirim email, jangan mengeluarkan alamatnya, melainkan berikan
sys.idpadatoServiceUser. Engine me-resolve alamat tepat sebelum pengiriman sehingga alamat anggota tidak masuk ke ruang variabel Script. - Untuk menyimpan definisi ini,
settingspada SpaceRole penulis harus memuatSETTING_SERVICE_LOGIN. Statement yang membuat atau menghapus anggota tidak dapat disimpan dengan role apa pun (Menangani direktori anggota).
EmailSend menambahkan timeoutMs (atau 10 detik jika tidak dideklarasikan) ke anggaran waktu eksekusi, dan dihitung sebagai satu terhadap batas panggilan eksternal per definisi.
Pengiriman file
29. Mengirim foto produk ke layanan analisis gambar lalu mencatat hasilnya (multipartBody)
{ "method": "Post",
"statements": [
{ "type": "ResourceRead", "resource": "Content", "target": { "sys": { "id": "{ /payload/sys/id }" } }, "name": "product" },
{ "type": "ResourceRead", "resource": "Media",
"target": { "sys": { "id": "{ /product/fields/photo/en-US/sys/id }" } }, "name": "photo" },
{ "type": "Try",
"body": [
{ "type": "Http", "method": "POST", "url": "https://api.vision.example/v2/analyze",
"headers": [ { "key": "x-api-key", "value": "{ /secrets/VISION_API_KEY }" } ],
"multipartBody": [
{ "name": "image", "url": "{ /photo/fields/file/en-US/url }" },
{ "name": "language", "value": "ko" },
{ "name": "features", "value": "labels" },
{ "name": "features", "value": "text" } ],
"timeoutMs": 20000, "name": "analysis" },
{ "type": "ResourcePatch", "resource": "Content", "target": { "sys": { "id": "{ /product/sys/id }" } },
"fields": { "aiLabels": { "en-US": "{ /analysis/body/labels }" },
"aiText": { "en-US": "{ /analysis/body/text }" } } },
{ "type": "Return", "value": { "ok": true, "labels": "{ /analysis/body/labels }" } } ],
"catch": [
{ "type": "Return", "value": { "ok": false, "reason": "image analysis failed", "detail": "{ /error/message }" }, "isError": true, "statusCode": 502 } ] } ],
"secrets": [ { "key": "VISION_API_KEY", "value": "...", "description": "API key analisis gambar" } ] }Begitu penjual mendaftarkan produk, label dan teks di dalam foto produk diekstrak lalu dicatat ke produk tersebut (dipasang ke Content.Create sebagai aksi terhubung sebuah Webhook). Media dibaca melalui field referensi photo pada Content produk, lalu url file tersebut ditulis pada part file di multipartBody. api.vision.example adalah layanan fiktif untuk contoh, dan diasumsikan layanan ini mengembalikan labels (array string) dan text (string). aiLabels adalah field Array (elemen ShortText), sehingga array ditulis apa adanya. Sesuaikan jalur { /analysis/body/... } dengan bentuk respons layanan yang benar-benar Anda gunakan.
- Susunan part: Satu elemen array adalah satu part, dan part dikirim sesuai urutan array.
imageadalah part file yang menetapkanurl, sedangkan sisanya adalah part teks yang menetapkanvalue. Jika nama yang sama dipakai beberapa kali sepertifeatures, masing-masing dikirim sebagai part terpisah. Content-Type: Seluruh permintaan selalu dikirim sebagaimultipart/form-data, jadi jangan menetapkannya diheaders. Partimagetidak menetapkancontentType, sehingga memakaiContent-Typeyang direspons oleh URL file. Nama file juga tidak ditetapkan, sehingga segmen jalur terakhir dari URL file menjadi nama file.- Penanganan kegagalan:
Httptersebut gagal jika file melampaui batas unduhan, jika file tidak dapat diterima dari URL file, atau jika waktunya habis.Httpjuga gagal jika layanan tujuan merespons dengan status 400 atau lebih. Contoh ini tidak menetapkanignoreStatusCode(nilai defaultfalse), sehingga dalam kasus mana pun eksekusi berpindah kecatch, danReturnpadacatchmengembalikan status502. Produk tidak diubah. Cara penanganan per jenis kegagalan dibahas di Dua cara Http gagal. - Anggaran waktu: Pada paket Pro, Script ini diberi sekitar 60,5 detik (dasar 30 detik +
timeoutMs20 detik + waktu unduh satu foto sekitar 10,5 detik). Cara perhitungannya dibahas di Anggaran waktu.
Untuk API yang mengirim satu file sebagai seluruh body (bentuk PUT ke URL unggahan), gunakan urlBody sebagai ganti multipartBody. Aturan kedua field itu dan urutan penentuan Content-Type dibahas di Mengirim file sebagai body.
Transformasi file
30. Salinan PDF e-book yang dibubuhi email pembeli (PdfWatermark)
{ "method": "Post",
"payloadSchema": {
"type": "object", "required": [ "fields" ],
"properties": {
"fields": {
"type": "object", "required": [ "ebookMediaId", "email", "orderId" ],
"properties": {
"ebookMediaId": { "type": "string" },
"email": { "type": "string", "minLength": 1, "maxLength": 41, "not": { "pattern": "[^ -~]" } },
"orderId": { "type": "string", "minLength": 1, "maxLength": 20, "not": { "pattern": "[^A-Za-z0-9-]" } } } } } },
"statements": [
{ "type": "ResourceRead", "resource": "Media",
"target": { "sys": { "id": "{ /payload/fields/ebookMediaId }" } }, "name": "ebook" },
{ "type": "ResourceCreate", "resource": "Media", "publish": false,
"fields": {
"title": { "en-US": "{ /ebook/fields/title/en-US }" },
"file": { "en-US": {
"source": "{ /ebook/fields/file/en-US/url }", "encoding": "url",
"transformations": [
{ "type": "PdfWatermark", "text": "{ /payload/fields/email } · { /payload/fields/orderId }" } ] } } },
"name": "copy" },
{ "type": "Return", "value": { "mediaId": "{ /copy/sys/id }" } } ] }Alur ini membuat salinan e-book yang akan diserahkan kepada pembeli setelah pembayaran dikonfirmasi. Payload permintaan menerima id Media e-book asli, email pembeli, dan nomor pesanan. Media asli dibiarkan apa adanya, lalu dibuat Media baru yang setiap halamannya dibubuhi email · nomor pesanan secara diagonal, dan id Media baru itu dikembalikan.
- Panjang teks: Teks tetap di luar template adalah
·sepanjang 3 karakter.payloadSchemamembatasi email hingga 41 karakter dan nomor pesanan hingga 20 karakter, sehingga teks yang dibubuhkan tidak melebihi 64 karakter. Jika salah satu dari kedua nilai memuat satu saja karakter di luar karakter yang diizinkan (termasuk baris baru), permintaan ditolak sebelum eksekusi, sehingga tahap transformasi tidak akan gagal karena teksnya. Semua karakter cetak ASCII yang diterima email termasuk dalam karakter yang dapat dipakai. - Warna:
colordihilangkan, sehingga teks dibubuhkan dengan nilai default#808080(abu-abu). - Publikasi: Salinan ini ditujukan untuk satu pembeli, sehingga
publishdiatur kefalse. Salinan tidak dikirimkan melalui CDA/ACDA. - Penanganan kegagalan: Jika file asli bukan PDF atau terenkripsi,
ResourceCreategagal dengan status 422 dan Media baru tidak dibuat. Daftar lengkap kasus kegagalan dibahas di Kasus ketika transformasi file gagal. - Anggaran waktu: Waktu untuk menerima dan mentransformasi file diambil dari anggaran dasar 30 detik (Anggaran waktu).
- Konfirmasi pembelian: Contoh ini tidak memiliki langkah untuk memeriksa pembayaran dan pembelian. Jika dibiarkan seperti ini, pemanggil yang memiliki izin eksekusi dapat membuat salinan dengan id Media apa pun. Saat dipakai sungguhan, baca Content pesanan terlebih dahulu untuk memastikan pembeli dan produknya cocok, lalu lanjutkan.
Dokumen terkait
- Katalog Statement: field dan hasil dari Statement yang dipakai dalam contoh.
- Ekspresi nilai: referensi, JsonLogic, dan aturan peta locale.
- Semantik eksekusi, batasan, dan keamanan: urutan eksekusi, kompensasi, penguncian optimistis, dan batasan.
- Ikhtisar Script: struktur tingkat atas dan waktu yang diberikan untuk satu eksekusi.
