การแมปข้อมูลการขายของ Smaregi กับข้อมูลการขายของ Receipt Roller
Receipt Roller เชื่อมต่อกับผู้ให้บริการ POS หลายรายรวมถึง Smaregi ไม่ว่าจะนำเข้าข้อมูลการขายจากผู้ให้บริการรายใด ภายในระบบจะถูกจัดการด้วยโมเดลข้อมูลการขายร่วม(PosTransactionDto)โดยมีการปรับมาตรฐานฟิลด์เฉพาะของแต่ละผู้ให้บริการ บทความนี้อธิบายทางเทคนิคว่าข้อมูลการขายของ Smaregi Platform API ถูกจับคู่เข้ากับโมเดลภายในของ Receipt Roller อย่างไร
บทความนี้เป็นเอกสารอ้างอิงสำหรับนักพัฒนาและผู้ดูแลระบบที่ต้องการทำความเข้าใจข้อกำหนดภายในของการเชื่อมต่อ POS สำหรับขั้นตอนการตั้งค่าเชื่อมต่อทั่วไป ดูได้ที่ การตั้งค่าเชื่อมต่อ POS ของ Smaregi สำหรับข้อกำหนดโมเดลมาตรฐานแยกตามฟิลด์ ดูได้ที่ ข้อกำหนด PosTransactionDto (ฟิลด์อ้างอิง)
การเทียบโครงสร้างแบบลำดับชั้น
Smaregi Platform API มีโครงสร้างลำดับชั้น 4 ระดับ ได้แก่ สัญญา・สาขา・เครื่องขาย・รายการขาย ฝั่ง Receipt Roller มีลำดับชั้น 4 ระดับเช่นกัน คือ บัญชีธุรกิจ・สาขา・เครื่อง POS・รายการขาย ซึ่งออกแบบให้ตรงกันแบบหนึ่งต่อหนึ่ง
- สัญญา(contractId) ↔ บัญชีธุรกิจของ Receipt Roller เมื่อติดตั้งแอป "Receipt Roller Link" ใน Smaregi สัญญานั้นจะถูกเชื่อมโยงกับบัญชีธุรกิจ 1 บัญชีของ Receipt Roller(
SmaregiContract.OrganizationId) - สาขาของ Smaregi(storeId) ↔ สาขาของ Receipt Roller เชื่อมโยงกับเครื่อง POS ฝั่ง Receipt Roller ผ่าน
PosTerminal.SmaregiStoreId - เครื่องขายของ Smaregi(terminalId) ↔ เครื่อง POS ของ Receipt Roller เก็บไว้ใน
PosTerminal.SmaregiTerminalIdโดย 1 เครื่อง POS ของ Receipt Roller ตรงกับ 1 เครื่องขายของ Smaregi(แคชเชียร์) - รายการขาย(transactionHeadId) ↔ รายการขายของ Receipt Roller เก็บ ID เดียวกันไว้เป็นทั้ง
PosTransaction.TransactionIdและExternalTransactionId
การแมปฟิลด์ของส่วนหัวรายการขาย
ความสัมพันธ์ระหว่าง response ของ Smaregi /pos/transactions(SmaregiTransaction)กับ PosTransactionDto ของ Receipt Roller มีดังนี้
| Smaregi | Receipt Roller | หมายเหตุ |
|---|---|---|
transactionHeadId | TransactionId / ExternalTransactionId | บันทึกค่าเดียวกันไว้ใน 2 ฟิลด์ เป็นคีย์สำหรับการ upsert แบบคงเส้นคงวา |
transactionDateTime | TransactionDateTime | แปลงสตริง JST เป็น UTC แล้วบันทึก |
| (สกุลเงิน) | Currency | บันทึกเป็น JPY คงที่ |
storeId | (ไม่บันทึกโดยตรง) | บันทึกไว้แล้วเป็น PosTerminal.SmaregiStoreId ตอนเชื่อมต่อ ฝั่งรายการขายจะแทนที่ด้วย PosTerminal.StoreId(ID สาขาของ Receipt Roller) |
terminalId | (ไม่บันทึกโดยตรง) | บันทึกไว้แล้วเป็น PosTerminal.SmaregiTerminalId ตอนเชื่อมต่อ ฝั่งรายการขายจะแทนที่ด้วย PosTerminal.TerminalId(ID เครื่อง POS ของ Receipt Roller) |
customerId / customerCode | ExternalIds.ProviderCustomerId(และส่งต่อไปยังการระบุ CRM) | ส่งต่อไปยังตรรกะการระบุ CRM ในฐานะคีย์จับคู่ลูกค้า(อธิบายด้านล่าง)รวมถึงเก็บไว้ในรายการขายเป็น ID สำหรับอ้างอิงข้ามผู้ให้บริการด้วย |
staffId | StaffId | บันทึกตามเดิม |
staffName | StaffName | หากเป็นค่าว่างจะดึงจาก /pos/staffs/{staffId} มาเติมเต็ม |
total | TotalAmount | Smaregi ส่งค่าตัวเลขเป็นสตริงด้วย จึงใช้ decimal.TryParse |
subtotal | SubtotalAmount | หากเป็น 0 จะคำนวณจาก total - tax |
tax | TaxAmount | แปลงสตริงเช่นเดียวกับข้างต้น |
discount | DiscountAmount | เช่นเดียวกับข้างต้น |
status / cancelDivision | Status | ดูตารางแปลงสถานะด้านล่าง |
receiptNumber / terminalTransactionId | ReceiptNumber | หมายเลขใบเสร็จที่ออกโดยเครื่อง |
payments[] | Payments[] / PaymentMethod | เก็บการชำระทุกรายการพร้อมจำนวนเงินที่ชำระจริงไว้ใน Payments[](รองรับการชำระแบบแยก)ส่วน PaymentMethod เก็บรายการที่มีจำนวนเงินมากที่สุด(อธิบายด้านล่าง) |
การแปลงรหัสสถานะ
ค่าสถานะของ Smaregi ส่งกลับมาเป็นสตริงตัวเลข ภายใน Receipt Roller จะปรับให้เป็นป้ายกำกับแบบสตริง
| ค่าของ Smaregi | ความหมาย | Receipt Roller Status |
|---|---|---|
"1" | เสร็จสมบูรณ์ | Completed |
"2" | ยกเลิก | Voided |
"3" | คืนเงิน | Refunded |
| ค่าไม่ทราบ・สตริงว่าง | — | Pending(สำรอง) |
มีบางกรณีที่ต้องใช้ทั้ง status และ cancelDivision ร่วมกันขึ้นอยู่กับข้อกำหนด ระบบจะตรวจสอบค่าทั้งสองเพื่อจำแนกให้ถูกต้อง หากตรวจพบค่าใดค่าหนึ่ง
การปรับมาตรฐานวิธีการชำระเงิน
Smaregi ส่งข้อมูลการชำระเงินมาในอาร์เรย์ payments(Receipt Roller ร้องขอด้วย with_payments=all)โดยแต่ละองค์ประกอบคือการชำระ 1 รายการ ประกอบด้วยชื่อวิธีการชำระเงิน จำนวนเงินที่ชำระจริง(paidAmount)และอาจมี 「支払金種コード」(รหัสประเภทการชำระเงิน, denominationCode)หรือแฟล็ก 「有価証券」(บัตรกำนัลหรือคูปองที่มีมูลค่า, paymentSecuritiesFlag)มาด้วย(หากเป็นการชำระแบบแยกจะมีหลายองค์ประกอบ)Receipt Roller เก็บการชำระทุกรายการพร้อมจำนวนเงินที่ชำระจริงไว้ใน Payments[] และใบเสร็จจะแสดงทีละบรรทัดต่อการชำระ 1 รายการ ส่วน PaymentMethod จะเก็บ "วิธีการชำระเงินหลัก" เพียง 1 รายการไว้ใช้ในรายการขายและตัวกรอง โดยเลือกรายการที่มีจำนวนเงินมากที่สุด การชำระแต่ละรายการจะถูกจำแนกตามลำดับในตารางด้านล่าง(ใช้กฎแรกที่ตรง)
| เงื่อนไข | ผลการจำแนกของ Receipt Roller |
|---|---|
| ชื่อมีคำว่า 「現金」 ซึ่งหมายถึงเงินสด(ยกเว้นชื่อที่มี 「券」 ซึ่งหมายถึงบัตรหรือคูปอง เช่น 「現金券」) | Cash |
| มีแฟล็ก 「有価証券」(เช่น 「商品券」, 「QUOカード」) | คงชื่อที่ร้านตั้งไว้ |
| มี 「支払金種コード」 | CREDIT, UNIONPAY ฯลฯ → CreditCard / SUICA, ID, WAON ฯลฯ → IC / PAYPAY, AUPAY, LINEPAY ฯลฯ → QR / DEBIT, PREPAID และรหัสอื่น → คงชื่อ 「支払金種名」(ชื่อประเภทการชำระเงิน, denominationName)ของ Smaregi(เช่น 「デビット」) |
| ไม่มีรหัส ตัดสินจากชื่อ | ตรวจตามลำดับจากซ้ายไปขวา ใช้รายการแรกที่ตรง — 「ポイント」(คะแนนสะสม)→ คงชื่อเดิม / Suica, iD, WAON, 「電子マネー」(เงินอิเล็กทรอนิกส์)ฯลฯ → IC / 「デビット」(เดบิต), 「プリペイド」(เติมเงินล่วงหน้า), 「商品券」(บัตรกำนัล), 「QUOカード」, 「図書カード」 ฯลฯ → คงชื่อเดิม / 「PayPayカード」, 「au PAYカード」 → CreditCard / PayPay, au PAY, QR ฯลฯ → QR / VISA, JCB, 「クレジット」(เครดิต)ฯลฯ → CreditCard / 「会員カード」(บัตรสมาชิก), 「バリューカード」 → คงชื่อเดิม / ชื่ออื่นที่มี 「カード」(บัตร)→ CreditCard ดังนั้น 「VISAデビット」 และ 「WAONポイント」 จึงคงชื่อเดิม |
| ตัดสินจากชื่อไม่ได้(เช่น 「Airペイ」, 「店舗端末」 ซึ่งหมายถึงเครื่องรับชำระเงินของร้าน) | หากจำนวนเงินอยู่ในส่วนของ creditTotal ที่ยังไม่มีรายการใดรองรับ จะจัดเป็น CreditCard หากไม่ใช่จะคงชื่อที่ร้านตั้งไว้ |
ยอดเงินที่ไม่มีรายการใดใน payments รองรับ จะเติมจาก cashTotal ของส่วนหัวเป็น Cash และจาก creditTotal เป็น CreditCard เฉพาะส่วนที่ยังขาด จึงไม่มีการบันทึกยอดเดียวกันซ้ำ PaymentMethod จะเป็น Other เฉพาะเมื่อไม่มีการชำระเงินเลย วิธีที่จำแนกไม่ได้จะคงชื่อที่ร้านตั้งไว้ และยังค้นหาได้ด้วยตัวกรอง "อื่น ๆ"
QR และเงินอิเล็กทรอนิกส์จะเก็บชื่อวอลเล็ตไว้ด้วย ใบเสร็จจึงแสดงเป็น 「お支払い(QRコード決済・PayPay)」 และ 「お支払い(電子マネー・Suica)」 บัตรเครดิตแสดงเป็น 「お支払い(クレジットカード)」 เงินสดแสดงเป็น 「お支払い(現金)」 พร้อม 「お預り」(เงินที่รับมา)และ 「お釣り」(เงินทอน)เมื่อ POS ส่งยอดที่มากกว่า 0 มา ส่วนวิธีที่คงชื่อเดิมจะแสดงตามชื่อนั้น เช่น 「お支払い(デビット)」
สถานะล่าสุดของการพัฒนาจริงดูได้จาก source code(SmaregiTransactionMapper)
การแมปข้อมูลรายละเอียด (line item)
นอกเหนือจากส่วนหัวรายการขาย ระบบจะดึงข้อมูลรายละเอียดจาก /pos/transactions/{id}/details แล้วแปลงเป็นอาร์เรย์ของ PosTransactionLineItemDto ก่อนบันทึกแบบ serialize ลงใน LineItemsJson
Smaregi SmaregiTransactionDetail | Receipt Roller PosTransactionLineItemDto | หมายเหตุ |
|---|---|---|
productName | ProductName | คัดลอกตามเดิม |
quantity | Quantity | ปัดเศษสตริงเป็นจำนวนเต็ม หากน้อยกว่าหรือเท่ากับ 0 จะปรับเป็น 1 |
price | UnitPrice | แปลงสตริง |
subtotal | Subtotal | แปลงสตริง |
taxRate | TaxRate | แปลงสตริง |
tax | TaxAmount | จำนวนภาษีระดับรายละเอียด เก็บเป็นจำนวนเงินไม่ใช่แค่อัตราภาษี |
productId | CatalogObjectId / ProductId | ID แคตตาล็อกสำหรับเชื่อมโยงระหว่างระบบ |
categoryName | Category | คัดลอกตามเดิม |
ItemCount(จำนวนแต้มซื้อทั้งหมดของรายการขาย)คำนวณจากผลรวมของ Quantity ในรายละเอียด
สำหรับส่วนลดระดับรายละเอียด มีแผนจะแมปหลังจากยืนยันชื่อฟิลด์ที่เกี่ยวข้องใน transactionDetails ของ Smaregi ก่อนจนกว่าจะยืนยันได้ DiscountAmount จะเป็น null และเก็บไว้ใน payload ดิบ(RawJson)เพื่อป้องกันการสูญหายของข้อมูล
การเก็บข้อมูลดิบ (RawJson)
เมื่อนำเข้าข้อมูล ระบบจะบันทึก payload ดิบต้นฉบับของส่วนหัวรายการขาย + รายละเอียดไว้ใน RawJson เป็นมาตรการความปลอดภัยเพื่อให้สามารถกู้คืนฟิลด์ที่ไม่ได้แปลงเป็นโมเดลได้ในภายหลัง ใช้งานภายในเซิร์ฟเวอร์เท่านั้น(ไม่ส่งคืนผ่าน API)
การระบุข้อมูลลูกค้า (การเชื่อมต่อ CRM)
customerId / customerCode ที่ดึงมาจาก Smaregi จะถูกส่งไปยังบริการระบุตัวตน CRM(IPosCustomerMatchService)ในฐานะคีย์จับคู่ลูกค้า รวมถึงเก็บไว้ใน ExternalIds.ProviderCustomerId เป็น ID สำหรับอ้างอิงข้ามผู้ให้บริการด้วย หากจับคู่สำเร็จ รายการขายจะถูกเชื่อมโยงเข้ากับโปรไฟล์ลูกค้า CRM ของ Receipt Roller โดยอัตโนมัติ และนำไปใช้ในการคำนวณประวัติการซื้อรายบุคคลหรือคะแนนความเสี่ยงในการเลิกใช้บริการ
ประเภทหลักของคีย์จับคู่มีดังนี้(อ้างอิงจาก PosCustomerMatchDto ในการพัฒนาจริง)
- หมายเลขสมาชิก(รหัสสมาชิก)—
customerCodeของ Smaregi เป็นต้น - ID ลูกค้าภายนอก —
customerIdของ Smaregi - ที่อยู่อีเมล/หมายเลขโทรศัพท์ — เฉพาะกรณีที่ดึงได้จาก Smaregi
รายการขายที่จับคู่ไม่สำเร็จ สามารถให้ลูกค้าเชื่อมโยงด้วยตนเองในภายหลังผ่านการสแกน QR ที่หน้าร้านได้เช่นกัน
การเติมเต็มข้อมูลพนักงาน
มีบางกรณีที่ส่วนหัวรายการขายมีเพียง staffId เท่านั้น ในกรณีนั้นระบบจะเรียก /pos/staffs/{staffId} แยกต่างหากเพื่อดึง displayName แล้วบันทึกลงใน StaffName แม้การดึงชื่อพนักงานจะล้มเหลว การนำเข้ารายการขายเองจะไม่ล้มเหลวไปด้วย แต่จะบันทึกเป็นค่าว่างไว้(เพื่อป้องกันข้อมูลรายการขายสูญหาย)
การแยกแยะเส้นทางการดึงข้อมูล (Source)
แม้เป็นรายการขายเดียวกัน ระบบจะแยกแยะด้วยฟิลด์ Source ว่าถูกนำเข้าผ่านเส้นทางใด
Webhook— เส้นทางที่ถูกพุชแบบเรียลไทม์จาก SmaregiSync— เส้นทางการซิงค์ด้วยตนเองที่ผู้ดูแลร้านค้ากดปุ่ม "ซิงค์กับ Smaregi"API— การดึงข้อมูลผ่าน Platform API เส้นทางอื่นๆ(เช่น การดึงข้อมูลทันทีตอนสแกน QR)
แม้ transactionHeadId เดียวกันจะมาถึงผ่านหลายเส้นทาง จะไม่มีรายการขายซ้ำเกิดขึ้น เนื่องจากใช้ ID เดียวกันเป็นคีย์ในการ upsert ค่า Source จะสะท้อนเส้นทางที่นำเข้าล่าสุด
การจัดการโซนเวลา
Smaregi Platform API ส่งคืนวันเวลาในรูปแบบ ISO 8601 ของ JST(+09:00)ฝั่ง Receipt Roller จะบันทึกทั้งหมดเป็น UTC และแปลงเป็น JST เมื่อแสดงผล เมื่อส่งฟิลเตอร์วันเวลาไปยัง Platform API ก็จะแปลงจาก UTC เป็น JST แล้วเข้ารหัส URL ก่อนส่ง(เพื่อไม่ให้เครื่องหมาย + ของ +09:00 ถูกตีความเป็นช่องว่าง)
ความคงเส้นคงวา (Idempotency)
การ upsert รายการขายใช้ transactionHeadId เป็นคีย์ แม้รายการขายเดียวกันจะมาถึงทั้งผ่าน Webhook และการซิงค์ด้วยตนเอง ผลลัพธ์จะรวมเป็นเรคคอร์ดเดียว UpdatedAt จะถูกอัปเดตตามเวลาที่นำเข้าล่าสุด แต่ TransactionDateTime(เวลาที่เกิดรายการขาย)จะไม่เปลี่ยนแปลง
ภาพรวมของไปป์ไลน์การนำเข้า
- เกิดรายการขายในฝั่ง Smaregi → Webhook ถูกส่งจาก Smaregi Platform ไปยัง Receipt Roller
- Endpoint รับ Webhook ของ Receipt Roller ดำเนินการยืนยันตัวตนด้วย custom header
- ดึง
transactionHeadIds(อาจมีหลายรายการ)จาก payload - สำหรับแต่ละ ID จะดึงส่วนหัวรายการขาย + รายละเอียด + ข้อมูลพนักงานจาก Platform API
- แปลงเป็น
PosTransactionDtoด้วยSmaregiTransactionMapper - ส่งคีย์จับคู่ลูกค้าไปยัง
IPosCustomerMatchServiceเพื่อดำเนินการระบุตัวตนกับ CRM - บันทึกด้วย
PosTransactionService.UpsertAsync(รายการซ้ำจะถูกเขียนทับ) - อัปเดต
PosTerminal.LastTransactionAt(สำหรับใช้แสดงรายการขายล่าสุดผ่านการสแกน QR) - หากระบุลูกค้าได้ จะส่งต่อไปยังบริการกระจายอัตโนมัติ(การส่งใบเสร็จอิเล็กทรอนิกส์)
บทความที่เกี่ยวข้อง
- ข้อกำหนด PosTransactionDto (ฟิลด์อ้างอิง) — ข้อกำหนดโดยละเอียดของแต่ละฟิลด์
- การแมปข้อมูลการขายของ Square — เวอร์ชัน Square
- การตั้งค่าเชื่อมต่อ POS ของ Smaregi — ขั้นตอนการเชื่อมต่อ
- การเชื่อมต่อกับ POS
- การตั้งค่าเชื่อมต่อ POS ของ Square
- การซิงค์ข้อมูลสมาชิกแบบสองทิศทางกับ Smaregi / Square
- ประวัติการซื้อรายบุคคล (การเชื่อมต่อ POS)
-
การแมปข้อมูลสินค้า Smaregi กับข้อมูลสินค้าของ Receipt Rollerอธิบายทีละฟิลด์ว่าข้อมูลหลักสินค้าของ Smaregi ถูกแปลงเป็นข้อมูลสินค้าของ Receipt Roller (ProductDto) อย่างไร ครอบคลุมความสัมพันธ์ของรหัสสินค้า ชื่อสินค้า หมวดหมู่ ราคา ต้นทุน และอัตราภาษี รวมถึงพฤติกรรมเฉพาะของ Smaregi
-
การแมปข้อมูลพนักงาน Smaregi กับข้อมูลพนักงานของ Receipt Rollerอธิบายทีละฟิลด์ว่าข้อมูลพนักงานหลักของ Smaregi ถูกแปลงเป็นข้อมูลพนักงานของ Receipt Roller (StaffDto) อย่างไร ครอบคลุมตรรกะการแยกชื่อ-นามสกุลจาก staffName, การแมป staffCode ไปยัง EmployeeNumber และการข้ามพนักงานที่ถูกซ่อนด้วย displayFlag
-
การแมปข้อมูลสมาชิก Smaregi กับข้อมูลลูกค้าของ Receipt Rollerอธิบายทีละฟิลด์ว่าข้อมูลหลักสมาชิกของ Smaregi ถูกแปลงเป็นข้อมูลลูกค้าของ Receipt Roller (CrmCustomerDto) อย่างไร ครอบคลุมความสัมพันธ์ของหมายเลขสมาชิก ชื่อ-นามสกุล ข้อมูลติดต่อ ลำดับความสำคัญของหมายเลขโทรศัพท์ และจังหวะเวลาการซิงค์ผ่าน Webhook