ระหว่างทดสอบ IAP ก่อนปล่อยขึ้นสโตร์ เราเจอปัญหาคลาสสิกที่ทำให้งงมาก:
- ผู้ใช้กด “ดำเนินการซื้อต่อ”
- ปุ่มเปลี่ยนเป็น “กำลังประมวลผล”
- ผ่านไปไม่กี่วินาที ปุ่มกลับมาเป็น “ดำเนินการซื้อต่อ”
- ไม่มีหน้าต่างยืนยันการซื้อจาก App Store เด้งขึ้นมา
บทความนี้สรุปเส้นทางการตรวจสอบทั้งหมด, ล็อกสำคัญ, ต้นตอของปัญหา และแนวทางแก้ที่ใช้ได้จริงก่อนขึ้นโปรดักชัน
1. อาการที่พบ
หน้ารายการสินค้าซื้อในแอปโหลดได้ปกติ แต่พอกดซื้อ:
- มีการเรียก
purchase() - ไม่มี system dialog
- UI กลับไปสถานะยังไม่ซื้อในที่สุด
มองเผิน ๆ เหมือน “การซื้อไม่ได้เริ่มเลย” แต่ล็อกบอกอีกอย่าง
2. ตรวจรอบแรก: flow การซื้อเดินจริงหรือไม่
เราเพิ่มล็อกในจุดสำคัญทั้งหมดของเส้นทางการซื้อ:
- พารามิเตอร์ตอนกดปุ่ม
- จุดเข้า
purchaseSelectedProduct()และสาขาguard - ก่อน/หลัง
product.purchase() Transaction.updatesrefreshEntitlements()Transaction.currentEntitlementsTransaction.latest(for:)
ผลที่ได้:
purchase()คืนค่า.success- ตรวจสอบธุรกรรมผ่าน และมีการเรียก
transaction.finish() - แต่
entitledIDsยังว่าง
สรุปคือ: ปัญหาไม่ใช่ “ไม่ได้เริ่มซื้อ” แต่เป็น “ซื้อแล้วระบบไม่รับรู้ entitlement”
3. สิ่งที่เจอหลังขุดลึก
เมื่อเพิ่มล็อกละเอียดขึ้น พบสัญญาณชี้ชัด:
- จำนวนรอบของ
Transaction.currentEntitlementsเป็น0 Transaction.latest(for: monthly)มีเรคอร์ดถูกต้อง แต่หมดอายุแล้ว- จุดสำคัญ: ก่อนซื้อมีธุรกรรมเก่าอยู่ใน
Transaction.unfinished purchase()รอบนี้คืนธุรกรรมจาก chain เก่า ไม่ใช่จาก flow ซื้อใหม่แบบโต้ตอบ
จึงอธิบายได้ว่าทำไม “ไม่เด้งหน้าต่าง”:
- StoreKit ส่งธุรกรรมเก่าที่ยังประมวลผลได้กลับมาก่อน (unfinished / historical chain)
- โค้ดเราไป
finish()ธุรกรรมนั้น - คำขอดูเหมือน “สำเร็จ” แต่ไม่ได้เข้าทางยืนยันการซื้อใหม่ที่ผู้ใช้คาดหวัง
4. สรุปสาเหตุหลัก
นี่ไม่ใช่ปัญหาแบบจุดเดียว แต่เกิดจากสองปัจจัยซ้อนกัน:
- unfinished transaction รบกวน flow การซื้อ
- subscription chain ของ sandbox อยู่ในสถานะหมดอายุ (แม้มี
latest transactionก็อาจไม่ใช่ active entitlement)
ผลลัพธ์สุดท้ายคือ: ไม่ขึ้นป๊อปอัป, เหมือนสำเร็จ, แต่ entitlement ว่าง
5. แนวทางแก้ (พิสูจน์แล้วว่าใช้ได้จริง)
5.1 ล้าง unfinished transactions เชิงรุกก่อนซื้อ
ตอนแอปเริ่มทำงานและก่อนกดซื้อ ให้สแกน Transaction.unfinished แล้วเรียก finish() กับธุรกรรมที่ verified เพื่อกันธุรกรรมเก่ามาแทรก flow ใหม่
5.2 เก็บและเสริมล็อกวินิจฉัย (แนะนำให้เปิดก่อนปล่อยจริง)
ล็อกที่ควรเก็บไว้จนกว่าจะปล่อย:
- snapshot ของ
unfinished transaction subscription status(state, renewal info)- จำนวนรอบของ
currentEntitlements - รายละเอียด transaction/expiration จาก
latest(for:)
5.3 ตรวจ entitlement แบบสองทาง (เสริมได้)
ถ้าบางสภาพแวดล้อม currentEntitlements ว่างชั่วคราว ให้ใช้ latest(for:) เป็น fallback ได้ (แต่ยังต้องเช็กว่า active จริง)
6. เช็กลิสต์ IAP ก่อนขึ้นสโตร์
- ก่อนทุกการทดสอบ ตรวจสถานะสมาชิกของบัญชี Sandbox (หมดอายุหรือไม่)
- พิมพ์จำนวน
unfinishedในเส้นทางสำคัญ - ทดสอบ 3 สถานการณ์: ซื้อครั้งแรก / ต่ออายุหลังหมดอายุ / กู้คืนการซื้อ
- ตรวจว่า
currentEntitlementsกับlatestสอดคล้องกัน - ถ้าเจอ “ไม่เด้งหน้าต่าง” ให้เช็กก่อนว่าโดนเส้นทางธุรกรรมเก่าหรือไม่
7. บทสรุป
“กดซื้อแล้วไม่เด้งหน้าต่าง” ไม่ได้แปลว่าเป็นปัญหา UI หรือจังหวะเรียกอย่างเดียวเสมอไป
ใน StoreKit 2, สถานะธุรกรรม, คิว unfinished และวงจรชีวิต subscription ส่งผลโดยตรงต่อพฤติกรรมสุดท้าย
การเติมล็อกและขั้นตอนล้างธุรกรรมก่อนปล่อย จะช่วยลดปัญหา IAP ที่รีโปรดักซ์ยากในโปรดักชันได้มาก