חשבונית אוטומטית מ-Grow (משולם), כולל תשלומי Bit
דפי התשלום והקישורים של Grow מודיעים לשרת שלכם על כל תשלום, בכרטיס אשראי, ב-Bit או בהעברה. מההודעה הזאת אפשר להפיק מסמך ב-BillOS, ואמצעי התשלום מודפס עליו. כאן החיבור המלא.
שתי הודעות, אותו רעיון
| מאיפה התשלום | איך מקבלים הודעה |
|---|---|
| דף תשלום, קישור תשלום, הוראת קבע | עדכון שרת (Webhooks). התמיכה של Grow מפעילה אותו לחשבון, ומוסרים לה את הכתובת. |
| תהליך תשלום שנוצר ב-API | השדה notifyUrl בקריאה createPaymentProcess. |
בשני המקרים ההודעה לא חתומה, ולכן הכתובת נושאת סוד:
https://your-server.example.com/hooks/grow?key=LONG_RANDOM_SECRETמאמתים בלי חתימה
הסוד שבכתובת הוא הבדיקה. Grow מפרסמת גם את רשימת כתובות ה-IP שמהן היא שולחת, ואפשר להגביל את הנתיב אליהן בחומת האש.
// Grow signs nothing. The address it is given carries the secret:
// https://shop.example.com/hooks/grow?key=<GROW_WEBHOOK_SECRET>
// Grow also publishes the addresses it sends from, for a firewall rule in front of this.
export function verify({ query, rawBody, secret }) {
return sameSecret(query.get('key') || formFields(rawBody).webhookKey, secret)
}מההודעה לתשלום
הגוף הוא טופס, לא JSON. שתי ההודעות קוראות לאותם דברים בשמות קצת שונים:
| מה | עדכון שרת של דף | notifyUrl |
|---|---|---|
| מזהה העסקה | transactionCode | transactionId |
| הסכום, בשקלים | paymentSum | sum |
| אמצעי התשלום | transactionType: אשראי, Bit, PayBox, העברה בנקאית | transactionTypeId: 1 כרטיס, 6 Bit, 5 PayBox, 15 העברה |
| האם שולם | ההודעה נשלחת על תשלום | statusCode: 2 שולם |
| הלקוח | fullName, payerEmail, payerPhone | |
// The body is form-encoded. A page's update names the transaction `transactionCode` and the
// amount `paymentSum`; a createPaymentProcess callback names them `transactionId` and `sum`,
// with statusCode 2 for a paid transaction. Amounts are decimal shekels.
const METHODS = { Bit: 'bit', 6: 'bit', PayBox: 'paybox', 5: 'paybox', 'העברה בנקאית': 'transfer', 15: 'transfer' }
export function parse({ rawBody }) {
const f = formFields(rawBody)
const id = f.transactionCode || f.transactionId
const sum = f.paymentSum || f.sum
if (!id || !sum) return null
if (f.statusCode && f.statusCode !== '2') return null // 6 is a refund, 0 and 9 were not paid
return {
payment: {
source: name,
id,
amount: toMinor(sum),
currency: 'ILS',
method: METHODS[f.transactionType] || METHODS[f.transactionTypeId] || 'card',
description: f.paymentDesc || f.description || `עסקה ${f.asmachta || id}`,
customer: { name: f.fullName, email: f.payerEmail, phone: f.payerPhone },
items: [],
},
}
}מהתשלום למסמך
מכאן הקוד זהה לכל מקור תשלום. עוסק מורשה וחברה מקבלים חשבונית מס קבלה, ועוסק פטור מקבל קבלה. אמצעי התשלום עובר כמו שהוא, ולכן תשלום ב-Bit מודפס כ-Bit ותשלום ב-PayBox כ-PayBox.
export async function issueForPayment(payment, { apiKey, businessId }) {
const { business } = await call('GET', `/businesses/${businessId}`, { apiKey })
// The money is already in, so the document carries the sale and its payment together:
// a tax invoice/receipt (320). An exempt dealer (osek patur) may not issue a tax invoice,
// and gives a receipt (400) instead.
const docType = business.dealerType === 'patur' ? 400 : 320
// The items are printed only when they add up to what was charged. Shipping, a coupon or a
// rounding rule on the shop's side would otherwise make a document that disagrees with the
// payment, and the payment is the fact.
const itemsTotal = payment.items.reduce((sum, i) => sum + Math.round(i.quantity * i.unitAmount), 0)
const lines = payment.items.length && itemsTotal === payment.amount
? payment.items.map((i) => ({ description: i.description, quantity: i.quantity, unitPriceExVat: i.unitAmount }))
: [{ description: payment.description, quantity: 1, unitPriceExVat: payment.amount }]
const { name, email, phone, taxId } = payment.customer
const { document } = await call('POST', `/businesses/${businessId}/documents`, {
apiKey,
// The source's own payment id. A notification delivered twice lands on the same document.
idempotencyKey: `${payment.source}:${payment.id}`,
body: {
docType,
// The customer was charged a VAT-inclusive price. In gross mode unitPriceExVat carries
// that price, the total is exactly what was paid, and the VAT is derived from it.
priceMode: 'gross',
...(payment.currency === 'ILS' ? {} : { currency: payment.currency }),
party: { name: name || email || 'לקוח', ...(email ? { email } : {}), ...(phone ? { phone } : {}), ...(taxId ? { taxId } : {}) },
lines,
payments: [{ method: payment.method, amount: payment.amount }],
},
})
// A retry of a notification that was already handled: the document exists and is final.
if (document.status === 'issued') return document
// Issuing gives the document its number and signs the PDF. From here it never changes.
const issued = await call('POST', `/businesses/${businessId}/documents/${document.id}/issue`, { apiKey })
return issued.document
}ההסבר על כל החלטה בפונקציה הזאת נמצא בעמוד מתשלום למסמך.
כדאי לדעת
- עונים 200. מפתח ה-idempotency הוא מזהה העסקה, כך שהודעה שנשלחת שוב לא מפיקה מסמך שני.
- אישור קבלה. בתהליך שנוצר ב-API, שולחים ל-Grow את
approveTransactionאחרי שההודעה התקבלה. - אם Grow מפיקה אצלכם חשבוניות. בוחרים מקום אחד שמפיק. שני מסמכים לאותו תשלום הם שתי הכנסות בספרים.
- החזר. מבצעים אותו בקריאה
refundTransaction, ומפיקים את מסמך הזיכוי באותו מקום בקוד, עם מזהה ההחזר כמפתח idempotency.
שאלות נפוצות
איך מקבלים הודעה מ-Grow על תשלום?
לדפי תשלום ולקישורי תשלום יש עדכון שרת (Webhooks), שהתמיכה של Grow מפעילה לחשבון. למי שיוצר תהליך תשלום ב-API יש שדה notifyUrl בקריאה createPaymentProcess.
איך מאמתים הודעה של Grow?
Grow לא חותמת על ההודעה. שמים סוד בכתובת שמוסרים ל-Grow, כך שרק מי שמכיר את הכתובת המלאה יכול לשלוח אליה, ואפשר להוסיף כלל חומת אש לפי רשימת כתובות ה-IP ש-Grow מפרסמת.
אני עוסק פטור שגובה ב-Bit. מה מופק?
קבלה. עוסק פטור אינו מוציא חשבונית מס, והקוד בוחר את סוג המסמך לפי סוג העוסק של העסק. אמצעי התשלום על הקבלה יהיה Bit.
מה זה approveTransaction?
בתהליך תשלום שנוצר ב-API, Grow מבקשת שאחרי קבלת ההודעה תשלחו את הקריאה approveTransaction כאישור שקיבלתם אותה. לפי התיעוד של Grow העסקה מעובדת גם בלעדיה.
מה עושים בהחזר?
החזר ב-Grow הוא קריאת API שאתם מבצעים, refundTransaction. את מסמך הזיכוי ב-BillOS מפיקים באותו מקום בקוד.
המסמך הראשון, עוד היום
מפתח Sandbox בחינם, בלי כרטיס אשראי. הקוד שעובד מול הבדיקות עובד מול live בלי שינוי.
מפתח Sandbox בחינם
