ทำไมคืนเงินลูกค้าแล้ว แต่เงินยังหายไปจากกระเป๋าเรา?
ในโลกของการพัฒนาซอฟต์แวร์ที่เชื่อมต่อกับระบบการเงิน โดยเฉพาะการใช้ Stripe Connect (บริการรับชำระเงินสำหรับแพลตฟอร์ม) มือใหม่หลายคนมักเข้าใจผิดว่าการสั่ง Refund (การคืนเงินให้ลูกค้า) คือการกดปุ่ม "ย้อนกลับ" ทุกอย่างที่เกิดขึ้นในธุรกรรมนั้นให้หายไปเหมือนไม่เคยมีอะไรเกิดขึ้น แต่ในความเป็นจริง ระบบการเงินออนไลน์ซับซ้อนกว่านั้นมากครับ
ลองนึกภาพตามนะครับ เวลาคุณไปซื้อของที่ห้างสรรพสินค้า แล้วห้างนั้นแบ่งเงินให้ร้านค้าที่มาเช่าที่ขายของ เมื่อคุณทำเรื่องขอคืนเงิน ห้างต้องทำมากกว่าแค่คืนเงินให้คุณ เพราะห้างต้องไปดึงเงินคืนจากร้านค้าที่รับเงินไปแล้วด้วย หากระบบโค้ดของคุณสั่งแค่ "คืนเงินให้ลูกค้า" โดยไม่บอกให้ระบบไป "ดึงเงินจากร้านค้า" กลับมาด้วย เงินส่วนนั้นก็จะหายไปจากบัญชีของแพลตฟอร์มคุณทันที
ปัญหาเรื่อง Refund ที่ทำให้เงินหายไปนี้ไม่ใช่บั๊ก (Bug) ของ Stripe แต่มันคือการทำงานตามคำสั่งที่คุณเขียนไว้เป๊ะๆ ครับ Stripe จะทำตามคำสั่งที่คุณส่งผ่าน API (ช่องทางให้โปรแกรมคุยกัน) เท่านั้น หากคุณสั่งแค่คืนเงินลูกค้า ระบบก็จะคืนเงินลูกค้าให้ แต่ไม่ได้ไปยุ่งกับเงินส่วนที่โอนเข้าบัญชีร้านค้าไปแล้ว ซึ่งนี่คือจุดที่โปรแกรมเมอร์มือใหม่มักจะมองข้ามไปจนส่งผลเสียต่อกระแสเงินสดของบริษัทในระยะยาว
โครงสร้างการเงินที่คุณต้องรู้: มากกว่าแค่การรับเงิน
การทำธุรกรรมผ่าน Stripe Connect ไม่ได้มีแค่การ "รับ" แล้ว "จบ" ไปครับ แต่มันประกอบด้วย 3 ส่วนสำคัญที่ทำงานแยกกันอย่างชัดเจน ได้แก่ 1. Charge (การตัดเงินจากลูกค้า) 2. Application Fee (ค่าธรรมเนียมแพลตฟอร์มที่เราเก็บ) และ 3. Transfer (เงินส่วนที่เหลือที่โอนเข้าบัญชีร้านค้า)
ลองจินตนาการว่าลูกค้าจ่ายเงินมา 100 บาท โดยเราหักค่าธรรมเนียมไว้ 10 บาท และโอนให้ร้านค้าไป 90 บาท หากลูกค้าขอคืนเงิน 100 บาท แล้วคุณสั่งคืนเงินแค่ตัว Charge ระบบจะดึงเงิน 100 บาทจากกระเป๋าแพลตฟอร์มของคุณไปคืนลูกค้า แต่เงิน 90 บาทที่ร้านค้าได้รับไปแล้วนั้นยังคงอยู่ที่ร้านค้าเหมือนเดิม นี่คือที่มาของคำว่า "เงินหาย" เพราะแพลตฟอร์มคุณขาดทุนไปฟรีๆ 90 บาทครับ
โปรแกรมเมอร์ที่เพิ่งเริ่มทำงานกับระบบ Payment Gateway (ระบบชำระเงิน) มักจะลืมไปว่าทั้ง 3 ส่วนนี้มี "วงจรชีวิต" แยกจากกันโดยสิ้นเชิง การคืนเงินในส่วนของ Charge ไม่ได้หมายความว่าระบบจะไปยุ่งกับ Transfer ที่โอนไปแล้วโดยอัตโนมัติ คุณต้องเป็นคนเขียนโค้ดสั่งการให้ชัดเจนว่าต้องการให้เกิดอะไรขึ้นกับเงินแต่ละส่วนครับ
วิธีเขียนโค้ดคืนเงินให้ถูกต้อง: อย่าลืม Flag สำคัญ
การสั่ง refund ใน Stripe ผ่านโค้ดนั้นง่ายมาก แต่ถ้าคุณใช้คำสั่งแบบพื้นฐานที่สุด คุณจะพลาดการควบคุมเงินส่วนที่โอนให้ร้านค้าไปครับ เราต้องใช้ตัวแปรพิเศษที่เรียกว่า Parameters (ค่าพารามิเตอร์ที่ส่งไปกำหนดการทำงาน) เพื่อบอกให้ Stripe จัดการเรื่องเงินส่วนที่เหลือให้ครบถ้วน
หัวใจสำคัญคือการตั้งค่า refund_application_fee และ reverse_transfer ให้เป็น true ครับ หากคุณไม่ใส่ค่าเหล่านี้ ระบบจะถือว่าคุณไม่ต้องการดึงเงินส่วนไหนกลับมาเลย ซึ่งเป็นค่าเริ่มต้น (Default) ที่มักจะทำให้บริษัทขาดทุนโดยไม่รู้ตัวเมื่อมีการคืนเงินเกิดขึ้นในระบบ
// โค้ดตัวอย่างการคืนเงินที่ถูกต้องและปลอดภัย
const refund = await stripe.refunds.create({
charge: 'ch_123456789', // ID ของการจ่ายเงินที่ต้องการคืน
refund_application_fee: true, // คืนค่าธรรมเนียมแพลตฟอร์มด้วย
reverse_transfer: true, // ดึงเงินที่โอนให้ร้านค้ากลับมาด้วย
});
// โค้ดนี้จะสั่งให้ Stripe คืนเงินลูกค้า และดึงเงินส่วนที่เหลือกลับจากร้านค้าคืนเข้าแพลตฟอร์ม
จากตัวอย่างโค้ดด้านบน การใส่ reverse_transfer: true คือจุดชี้ขาดครับ มันบอกให้ Stripe ไปสั่งให้บัญชีร้านค้าที่ได้รับเงินไปแล้ว โอนเงินจำนวนนั้นกลับมาที่แพลตฟอร์มของคุณ เพื่อให้ยอดการเงินสมดุล (Reconciliation) หากคุณเขียนเพียงแค่คำสั่งคืนเงินธรรมดาโดยไม่มี Flag เหล่านี้ ผลลัพธ์ที่ได้คือเงินในบัญชีบริษัทคุณจะค่อยๆ หายไปเรื่อยๆ ตามจำนวนการคืนเงินครับ
ทำไมปัญหาถึงไม่ถูกตรวจพบจนกว่าจะสายเกินไป?
เหตุผลที่ปัญหานี้เป็น "ภัยเงียบ" เพราะมันไม่ได้สร้าง Error (ข้อผิดพลาด) หรือแจ้งเตือนใดๆ ให้คุณเห็นเลยครับ ทุกครั้งที่คุณสั่งคืนเงิน ระบบ API จะตอบกลับมาว่า 200 OK ซึ่งหมายความว่าทุกอย่างทำงานปกติ แต่ในเชิงธุรกิจ คุณกำลังสูญเสียเงินจำนวนเล็กน้อยต่อธุรกรรม ซึ่งยากจะสังเกตเห็นได้จากหน้าจอ Dashboard ทั่วไป
ลองนึกภาพว่าถ้าคุณเสียเงิน 90 บาทจากการคืนเงิน 1 ครั้ง มันอาจดูไม่มาก แต่ถ้ามีการคืนเงินวันละ 10 ครั้ง ผ่านไปหนึ่งเดือนคุณจะสูญเสียเงินไปถึง 27,000 บาทโดยไม่มีบันทึกแจ้งเตือนใดๆ ว่า "เงินหาย" เพราะระบบมองว่าการคืนเงินนั้นสำเร็จลุล่วงไปแล้ว คุณต้องเป็นคนสร้างระบบตรวจสอบ Reconciliation (การกระทบยอด) ด้วยตัวเองครับ
ในฐานะนักพัฒนา คุณควรหมั่นตรวจสอบบันทึกธุรกรรมรายสัปดาห์เสมอครับ อย่าเชื่อแค่ว่าถ้าโค้ดไม่พังแปลว่าระบบทำงานได้ถูกต้อง การเขียนโปรแกรมสายการเงินต้องการความละเอียดสูงกว่าการเขียนแอปทั่วไป คุณต้องเปรียบเทียบยอดเงินที่เข้าและออกจริงกับฐานข้อมูลในระบบของคุณ เพื่อให้มั่นใจว่าเงินแต่ละบาทถูกจัดการอย่างถูกต้องครับ
การคืนเงินบางส่วน: โจทย์ยากที่ต้องตัดสินใจ
การคืนเงินเต็มจำนวนนั้นง่ายเพราะมันมี "คำตอบที่ถูก" เพียงหนึ่งเดียว แต่เมื่อต้อง Partial Refund (การคืนเงินเพียงบางส่วน) คุณต้องเลือกว่าจะแบ่งความสูญเสียอย่างไรครับ จะให้ร้านค้าคืนเงินตามสัดส่วน หรือจะให้แพลตฟอร์มรับภาระทั้งหมด ซึ่ง Stripe เองก็ไม่มีทางรู้ว่าธุรกิจของคุณต้องการอะไร
สมมติว่าลูกค้าจ่าย 100 บาท แล้วขอคืน 40 บาท คุณมีทางเลือกคือ 1. ให้ร้านค้าคืน 36 บาท และคุณคืนค่าธรรมเนียม 4 บาท (สัดส่วน 10%) หรือ 2. ให้ร้านค้าคืน 40 บาทเต็มๆ โดยที่คุณไม่เสียค่าธรรมเนียมเลย หรือ 3. แพลตฟอร์มควักเงินคืนให้ลูกค้าเอง 40 บาทโดยไม่ไปยุ่งกับร้านค้า ซึ่งวิธีที่ 3 นี้คือวิธีที่โค้ดส่วนใหญ่ที่เขียนมาแบบผิดๆ มักจะทำโดยไม่ตั้งใจครับ
การตัดสินใจนี้ไม่ใช่เรื่องของ API แต่เป็นเรื่องของ Business Logic (ตรรกะทางธุรกิจ) ครับ คุณต้องคุยกับฝ่ายบัญชีหรือเจ้าของผลิตภัณฑ์ให้ชัดเจนว่าเมื่อเกิดการคืนเงินบางส่วน ใครจะเป็นคนรับผิดชอบเงินในส่วนนั้น และเขียนโค้ดให้สอดคล้องกับนโยบายบริษัท ไม่ใช่ปล่อยให้ค่าเริ่มต้นของระบบจัดการให้คุณครับ
สรุป: การเขียนโค้ดการเงินคือความรับผิดชอบ
การเป็นโปรแกรมเมอร์ที่เก่งไม่ได้วัดกันแค่ว่าเขียนโค้ดให้รันผ่าน แต่หมายถึงการเข้าใจ "ผลกระทบ" ของโค้ดที่มีต่อธุรกิจด้วย การจัดการเรื่อง Refund ใน Stripe Connect คือตัวอย่างที่ดีที่สุดว่าทำไมเราต้องอ่าน Documentation (เอกสารคู่มือ) ให้ละเอียด และอย่าเชื่อแค่คำแนะนำในอินเทอร์เน็ตที่บอกให้ใช้คำสั่งสั้นๆ โดยไม่ดูรายละเอียด
ทุกครั้งที่คุณเขียนโค้ดที่เกี่ยวกับเงิน ให้คิดเสมอว่า "เงินมาจากไหน และใครต้องเป็นคนจ่ายคืน" การใส่ Flag อย่าง reverse_transfer: true อาจดูเป็นเรื่องเล็กน้อยในโค้ด แต่มันคือการปกป้องรายได้ของบริษัทไม่ให้รั่วไหลไปกับกระบวนการที่ตรวจสอบได้ยากครับ
สำหรับมือใหม่ที่กำลังฝึกเขียนโปรแกรม ผมแนะนำให้ลองสร้างโปรเจกต์จำลองการซื้อขายเล็กๆ ขึ้นมา แล้วลองทำ Test Case (สถานการณ์จำลอง) เรื่องการคืนเงินดูครับ ลองดูว่าเมื่อกดปุ่มคืนเงินแล้ว เงินในบัญชีจำลองของคุณเปลี่ยนไปอย่างไรบ้าง การเห็นภาพการไหลของเงินด้วยตัวเองจะทำให้คุณเข้าใจตรรกะนี้ได้แม่นยำกว่าการอ่านทฤษฎีแน่นอนครับ
ที่มา: You issued the refund. They kept the money. — DEV Community