มี endpoint หนึ่งเดียวที่คืนประวัติการคืนเงิน App Store ทั้งหมดของลูกค้า และนี่คือสิ่งที่มันส่งกลับมาให้
endpoint Get Refund History ของ Apple คืนประวัติการคืนเงิน App Store ทั้งหมดของลูกค้าในรูปแบบ signed transactions นี่คือทุกฟิลด์ วิธีที่ revision token แบ่งหน้า เหตุใดมันจึงเป็นรายลูกค้าไม่ใช่รายแอป และการคืนเงินที่คุณพลาดไปทำให้คุณเสียเท่าไหร่

ประเด็นสำคัญ
- Get Refund History เป็น endpoint ของ App Store Server API ที่คืนการซื้อ in-app ที่ถูกคืนเงินของลูกค้าในแอปของคุณเป็นรายการ signed transactions เพื่อให้คุณกระทบยอดการคืนเงินและเพิกถอนสิทธิ์การเข้าถึงได้ แม้ว่าจะไม่มี notification ใดมาถึงคุณเลยก็ตาม
- คุณเรียก GET /inApps/v2/refund/lookup/{transactionId} ด้วย transaction id ใดก็ได้ของลูกค้ารายนั้น แล้ว Apple จะคืนการคืนเงินของพวกเขาในทุกประเภทการซื้อในแอปของคุณ ไม่ใช่แค่รายการที่คุณถามถึง
- การตอบกลับมีสามฟิลด์: signedTransactions มากถึง 20 JWS transactions ต่อหน้าที่เรียงจากการคืนเงินเก่าสุดก่อน พร้อมด้วย revision token และค่า hasMore แบบ boolean สำหรับการแบ่งหน้า
- จัดเก็บ revision token ตัวสุดท้ายไว้ ส่งมันกลับไปในครั้งถัดไป แล้ว Apple จะคืนเฉพาะการคืนเงินที่ใหม่กว่าจุดนั้น ซึ่งเปลี่ยนการดึงประวัติทั้งหมดให้กลายเป็นรายการสั้นๆ ของแถวใหม่ในทุกครั้งที่รัน
- แต่ละ transaction ที่ถอดรหัสแล้วจะมี revocationDate และ revocationReason ค่า revocationReason เท่ากับ 1 หมายความว่าลูกค้าคืนเงินเพราะปัญหาจริงหรือที่รับรู้ในแอปของคุณ และ 0 หมายถึงเหตุผลอื่นเช่นการซื้อโดยบังเอิญ
- endpoint นี้เป็นรายลูกค้า ไม่ใช่รายแอป ไม่มีการเรียกเดียวที่แสดงรายการการคืนเงินทุกรายการทั่วทั้งแอปของคุณ ดังนั้นคุณจึงกระทบยอดเป็นรายบัญชีจาก transaction id หรืออ่านฟีด notification แบบ REFUND ของคุณเพื่อดูภาพรวมทั้งแอป
- เหตุผลที่ต้องเชื่อมต่อมันคือเรื่องเงิน การคืนเงินที่คุณไม่เคยจับได้จะทำให้บัญชียังคงใช้งานอยู่ และคุณยังคงจ่ายค่า compute, model API calls, storage และ payouts ให้ลูกค้าที่ App Store ได้ชดใช้ให้เขาไปแล้ว
Apple เก็บบันทึกที่ค้นหาได้ของทุกการคืนเงินที่ได้อนุมัติบนบัญชีของลูกค้าสำหรับแอปของคุณ และการเรียกเพียงครั้งเดียวจะคืนมันกลับมา endpoint คือ Get Refund History ซึ่งเป็นส่วนหนึ่งของ App Store Server API และมันจะส่งประวัติการคืนเงิน App Store ทั้งหมดของลูกค้ารายนั้นให้คุณเป็นรายการ signed transactions คุณส่ง transaction id เข้าไป คุณได้สิ่งที่ Apple คืนเงินกลับมา แล้วคุณกระทบยอดมันกับสิ่งที่คุณยังเปิดใช้งานอยู่
นี่คือเหตุผลว่าทำไมคุณจึงควรใส่ใจ การคืนเงินที่คุณไม่เคยเห็นคือการคืนเงินที่คุณยังคงจ่ายอยู่ เงินหายไปแล้ว แต่บัญชียังคงใช้งานอยู่ และทุกชั่วโมงที่มันยังอยู่ คุณยังคงใช้จ่ายกับ compute, model API calls, storage และ payout ใดๆ ที่ผูกกับลูกค้ารายนั้น การแจ้งเตือนการคืนเงินของคุณมีไว้เพื่อจับสิ่งนี้ในทันทีที่มันเกิดขึ้น Get Refund History คือแนวรับสุดท้ายสำหรับตอนที่มันจับไม่ได้ หลังเกิดเหตุขัดข้อง หลัง deploy ที่ทำ webhook หลุด หรือในเคสฝ่ายสนับสนุนที่คุณต้องการภาพทั้งหมดในการเรียกครั้งเดียว
endpoint ประวัติการคืนเงิน App Store คืนอะไรกลับมา
คุณเรียก GET /inApps/v2/refund/lookup/{transactionId} ไปยัง App Store Server API โดยลงนามด้วย JWT ตัวเดียวกับที่คุณใช้ในทุกการเรียกอื่นๆ ของมัน transaction id ในเส้นทางสามารถเป็น transaction ใดก็ได้ของลูกค้ารายนั้น Apple อ่านมันเป็นตัวระบุตัวตน ไม่ใช่ตัวกรอง และคืนการซื้อที่ถูกคืนเงินของลูกค้ารายนั้นทั่วทั้งแอปของคุณ ทั้ง consumables, non-consumables, auto-renewable และ non-renewing subscriptions เหมือนกันหมด V1 รุ่นเก่าของ endpoint นี้เคยคืนการคืนเงินได้มากถึง 50 รายการในการตอบกลับครั้งเดียวและตอนนี้เลิกใช้แล้ว รุ่นปัจจุบันแบ่งหน้า ดังนั้นคุณจึงจัดการลูกค้าที่มีประวัติยาวได้โดยไม่ต้องมี payload ขนาดใหญ่
การตอบกลับมีสามฟิลด์
| ฟิลด์ | สิ่งที่มันเก็บไว้ |
|---|---|
| signedTransactions | transactions ที่ถูกคืนเงินมากถึง 20 รายการสำหรับลูกค้ารายนี้ แต่ละรายการเป็น JWS ที่ลงนามซึ่งคุณตรวจสอบและถอดรหัส เรียงจากการคืนเงินเก่าสุดก่อนตาม revocationDate array ว่างหมายความว่าลูกค้าไม่มีการคืนเงินในแอปของคุณ |
| revision | โทเคนสำหรับแบ่งหน้า ส่งมันกลับไปเพื่อรับหน้าถัดไป และเก็บตัวสุดท้ายไว้เพื่อดึงเฉพาะการคืนเงินใหม่ในครั้งถัดไป |
| hasMore | True เมื่อ Apple มี transactions ที่ถูกคืนเงินมากกว่าที่หน้านี้คืนกลับมา ดังนั้นคุณจึงเรียกอีกครั้งด้วย revision |
transaction ที่ถูกคืนเงินหนึ่งรายการบอกอะไรคุณ
แต่ละรายการใน signedTransactions เป็น JWS ตรวจสอบมันกับสายใบรับรองของ Apple ถอดรหัสมัน แล้วคุณจะได้ payload ของ transaction ตามปกติที่มีฟิลด์การคืนเงินใส่ค่าไว้ นี่คือฟิลด์ที่สำคัญที่นี่
| ฟิลด์ | สิ่งที่มันบอกคุณ |
|---|---|
| transactionId | id ของ transaction ที่ถูกคืนเงิน คีย์เชื่อมของคุณกลับไปยังการซื้อที่คุณบันทึกไว้ |
| originalTransactionId | id ของการซื้อครั้งแรกในสายนั้น วิธีที่คุณผูกการต่ออายุของ subscription เข้าด้วยกัน |
| productId | ผลิตภัณฑ์ที่ถูกคืนเงิน เพื่อให้คุณเพิกถอน entitlement ที่ถูกต้องและไม่ใช่อย่างอื่น |
| revocationDate | เวลา UNIX เป็นมิลลิวินาที ที่ Apple คืนเงิน transaction นั้น |
| revocationReason | เหตุผลที่ Apple คืนเงินมัน 1 หมายถึงปัญหาจริงหรือที่รับรู้ในแอปของคุณ 0 หมายถึงเหตุผลอื่นเช่นการซื้อโดยบังเอิญ |
| price, currency | จำนวนเงินเป็นหน่วย milliunits และรหัสสกุลเงิน ISO 4217 ของมัน เพื่อให้คุณรวมยอดเงินที่คืนได้ |
| appAccountToken | UUID ที่คุณแนบมาตอนซื้อ วิธีที่สะอาดที่สุดในการจับคู่การคืนเงินกลับไปยังผู้ใช้ของคุณเอง |
revision token คือวิธีที่คุณหยุดอ่านรายการทั้งหมดซ้ำ
วิธีที่ไร้เดียงสาในการใช้ endpoint นี้คือค้นหาลูกค้าและเดินผ่านทุกหน้าทุกครั้ง มันได้ผล และบนลูกค้าที่มีการคืนเงินห้าสิบรายการ มันคือห้าสิบแถวที่คุณรู้อยู่แล้วบวกกับแถวใหม่หนึ่งแถว revision token มีอยู่เพื่อกำจัดความสูญเปล่านั้น การตอบกลับแต่ละครั้งมี revision เมื่อ hasMore เป็น true คุณส่งมันกลับไปเพื่อรับหน้าถัดไป เมื่อคุณไปถึงจุดสิ้นสุด คุณเก็บ revision ตัวสุดท้ายที่คุณเห็นไว้
สิ่งที่ endpoint นี้จะไม่ทำ
มีความคาดหวังหนึ่งอย่างที่ต้องละทิ้งก่อนที่คุณจะสร้างบนมัน Get Refund History เป็นรายลูกค้า ไม่ใช่รายแอป คุณไม่สามารถขอมันให้แสดงทุกการคืนเงินที่แอปของคุณได้รับเมื่อสัปดาห์ที่แล้ว มันตอบคำถามเดียว คือบัญชีนี้มีการคืนเงินอะไรบ้าง และคุณต้องมาพร้อม transaction id ของบัญชีนั้นเพื่อจะถามมัน นักพัฒนาชนกำแพงนี้อยู่ตลอดและออกไปมองหา endpoint การคืนเงินทั้งแอปที่ไม่มีอยู่จริง
ภาพรวมทั้งแอปอยู่ที่อื่น ฟีด App Store Server Notifications ของคุณส่ง notification แบบ REFUND ในทันทีที่ Apple อนุมัติแต่ละรายการ และ Get Notification History ให้คุณเล่นฟีดนั้นซ้ำโดยกรองเป็นประเภทการคืนเงินในช่วงวันที่ที่กำหนด ดังนั้นการแบ่งจึงชัดเจน Notifications และประวัติของมันให้กระแสข้อมูลทั้งแอปแก่คุณ Get Refund History ให้รายการที่เชื่อถือได้ของหนึ่งบัญชีแก่คุณตามต้องการ ซึ่งเป็นสิ่งที่คุณต้องการที่โต๊ะฝ่ายสนับสนุนหรือหลังเกิดเหตุขัดข้อง

การคืนเงินที่พลาดไปทำให้คุณเสียเงินเท่าไหร่
endpoint คือระบบท่อ ใบเรียกเก็บเงินคือเหตุผลที่คุณวางท่อ ทุกการคืนเงินในรายการนั้นคือเงินที่คืนไปแล้ว และตัวแปรเดียวที่เหลือในการควบคุมของคุณคือคุณจะใช้จ่ายกับบัญชีที่ไม่จ่ายอีกต่อไปนานแค่ไหน
คุณยังคงจ่ายเพื่อให้บริการบัญชีที่ถูกคืนเงิน
ราคาซื้อหายไปในทันทีที่ Apple อนุมัติการคืนเงิน สิ่งที่ยังคงทำงานอยู่คือต้นทุนของการส่งมอบ สำหรับแอปที่ทำงานจริงต่อผู้ใช้แต่ละคน นั่นคือ compute, model API calls, storage และ payout ใดๆ ของครีเอเตอร์หรือพาร์ตเนอร์ที่ผูกกับการใช้งานของพวกเขา ลูกค้าที่ถูกคืนเงินซึ่งคุณไม่เคยตัดการเข้าถึงคือ subscription ที่คุณออกเงินจ่ายจากกระเป๋าตัวเอง การกระทบยอดกับ Get Refund History และเพิกถอนตามสิ่งที่คุณพบคือวิธีที่คุณปิดมิเตอร์นั้นเมื่อ notification หลุดรอดไป
เหตุผลการคืนเงินที่เป็น 1 คือรายงานข้อบกพร่องที่แฝงตัวอยู่
revocationReason ทำให้คุณเสียสองเท่าหากคุณเพิกเฉยต่อมัน ต้นทุนแรกคือการคืนเงินนั้นเอง ต้นทุนที่สองคือทุกการคืนเงินในอนาคตจากสาเหตุเดียวกัน เมื่อผลิตภัณฑ์หนึ่งกลับมาเรื่อยๆ ด้วย revocationReason เท่ากับ 1 คือปัญหาจริงหรือที่รับรู้ในแอปของคุณ Apple กำลังส่งตัวอย่างที่ติดป้ายกำกับให้คุณว่าอะไรทำให้ลูกค้าขอเงินคืน จับแนวโน้มของมันตามผลิตภัณฑ์แล้วคุณจะแก้รอยรั่วได้แทนที่จะจ่ายมันออกไปทีละการคืนเงิน
จับได้ช้ายังดีกว่าจับไม่ได้
chargeback เป็นที่สิ้นสุดกับธนาคาร และบนอีกสโตร์หนึ่ง ตอนนี้มีค่าธรรมเนียมที่นักพัฒนาต้องแบกรับ การคืนเงินของ App Store ไม่ใช่แบบนั้น มันยุติแล้ว แต่ entitlement เป็นของคุณที่จะเพิกถอนในทันทีที่คุณรู้ ดังนั้นแม้แต่การคืนเงินที่คุณพบช้าไปหลายวันผ่าน endpoint นี้ก็ยังคุ้มค่าที่จะพบ คุณไม่สามารถทวงเงินคืนได้ แต่คุณสามารถหยุดการใช้จ่ายที่ยังคงวิ่งอยู่เบื้องหลังมันได้
สิ่งนี้เข้ากับ notifications และกับ Google อย่างไร
คิดถึงชิ้นส่วนต่างๆ เป็นระบบเดียว notification แบบ REFUND คือสัญญาณสด ถูกส่งไปยังเซิร์ฟเวอร์ของคุณเมื่อ Apple ตัดสินใจ Get Refund History คือแหล่งข้อมูลความจริงแบบ pull สำหรับลูกค้าคนเดียว การเรียกที่คุณทำเมื่อการ push ล้มเหลวหรือเมื่อมีคนต้องการดูบัญชีทั้งหมดต่อหน้า ในฝั่ง Google Play รูปแบบก็เป็นแนวคิดเดียวกันด้วยชื่อที่ต่างกัน: VoidedPurchaseNotification push แบบเรียลไทม์ และ Voided Purchases API คือรายการที่คุณดึง ทั้งสองสโตร์ให้กระแสข้อมูลและสมุดบัญชีแก่คุณ ความผิดพลาดคือการเชื่อเฉพาะกระแสข้อมูลอย่างเดียว เพราะกระแสข้อมูลนั้นตกหล่นได้
การเชื่อมต่อมันในแบบของ RefundHalt
ลูปนั้นเล็กเมื่อทุกชิ้นส่วนเข้าที่แล้ว นำ notification แบบ REFUND มาเป็นตัวกระตุ้น กระทบยอดกับ Get Refund History เพื่อให้ webhook ที่หลุดไม่เคยทิ้งบัญชีที่ถูกคืนเงินให้ยังคงใช้งานอยู่ ถอดรหัสแต่ละ transaction จับคู่มันด้วย appAccountToken หรือ transactionId กลับไปยังผู้ใช้ของคุณ อ่าน revocationReason เพื่อให้การคืนเงินจากข้อบกพร่องถูกทำเครื่องหมายไม่ใช่แค่เก็บเข้าแฟ้ม และเพิกถอน entitlement ที่แม่นยำแทนที่จะเป็นทั้งบัญชี แบ่งหน้าด้วย revision token เพื่อให้คุณอ่านการคืนเงินใหม่ ไม่ใช่ของเก่า
นี่คือส่วนที่ RefundHalt รันให้คุณ มันคอยฟังการแจ้งเตือนการคืนเงิน ถอยกลับไปใช้ Get Refund History เมื่อมันต้องการรายการที่เชื่อถือได้ ตรวจสอบทุก signed transaction เพิกถอนการซื้อที่แม่นยำ และเก็บ revision ไว้เพื่อให้แต่ละรอบอ่านเฉพาะสิ่งที่เปลี่ยนแปลง คุณได้การตัดการเข้าถึงภายในไม่กี่วินาทีและบันทึกที่สะอาดว่าใครถูกคืนเงิน สำหรับอะไร และเพราะอะไร โดยไม่ต้องตั้งระบบ polling และการตรวจสอบ JWS ขึ้นมาเอง
คำถามที่พบบ่อย
- ฉันจะดูทุกการคืนเงินทั่วทั้งแอปของฉัน ไม่ใช่แค่ลูกค้าคนเดียวได้อย่างไร
- คุณทำไม่ได้ด้วย Get Refund History เพราะมันเป็นรายลูกค้าและต้องการ transaction id สำหรับบัญชีที่คุณกำลังถามถึง สำหรับภาพรวมทั้งแอป ให้ใช้ฟีด App Store Server Notifications ของคุณ ซึ่งส่ง notification แบบ REFUND สำหรับการคืนเงินแต่ละรายการเมื่อ Apple อนุมัติ และ Get Notification History เพื่อเล่นฟีดนั้นซ้ำโดยกรองเป็นประเภทการคืนเงินในช่วงวันที่ที่กำหนด
- endpoint Get Refund History คืนการคืนเงินกี่รายการ
- รุ่นปัจจุบันคืน transactions ที่ถูกคืนเงินมากถึง 20 รายการต่อหน้า เรียงจากการคืนเงินเก่าสุดก่อน และแบ่งหน้าส่วนที่เหลือด้วย revision token เมื่อ hasMore เป็น true endpoint V1 ที่เลิกใช้แล้วเคยคืนมากถึง 50 รายการในการตอบกลับครั้งเดียว ไม่มีเพดานสำหรับยอดรวม ดังนั้นลูกค้าที่มีประวัติยาวก็เพียงกระจายไปหลายหน้ามากขึ้น
- revision token มีไว้ทำอะไร
- มันคือวิธีที่คุณแบ่งหน้าและวิธีที่คุณหลีกเลี่ยงการอ่านประวัติทั้งหมดของลูกค้าซ้ำทุกครั้ง การตอบกลับแต่ละครั้งมี revision คุณส่งมันกลับไปเพื่อดึงหน้าถัดไป และคุณจัดเก็บตัวสุดท้ายไว้เพื่อให้การค้นหาครั้งถัดไปคืนเฉพาะการคืนเงินที่ใหม่กว่าจุดนั้น นั่นทำให้การกระทบยอดตามกำหนดเหลือเพียงรายการสั้นๆ ของแถวใหม่
- revocationReason ใน transaction ที่ถูกคืนเงินหมายความว่าอย่างไร
- มันคือเหตุผลที่ Apple คืนเงิน transaction นั้น ค่า 1 หมายความว่าลูกค้าคืนเงินเพราะปัญหาจริงหรือที่รับรู้ภายในแอปของคุณ และ 0 หมายถึงเหตุผลอื่น เช่นการซื้อโดยบังเอิญ revocationDate บอกคุณว่าการคืนเงินเกิดขึ้นเมื่อใด เป็นหน่วย UNIX มิลลิวินาที การอ่าน revocationReason ช่วยให้คุณแยกข้อบกพร่องของผลิตภัณฑ์ออกจากการคืนเงินเพราะเสียใจครั้งเดียวได้
- ฉันยังต้องใช้สิ่งนี้อยู่ไหมถ้าฉันจัดการ notification แบบ REFUND อยู่แล้ว
- ใช่ ในฐานะแนวรับสุดท้าย Notifications คือสัญญาณสด แต่การ push อาจส่งมาถึงไม่สำเร็จระหว่างเกิดเหตุขัดข้อง deploy ที่ผิดพลาด หรือการเปลี่ยน webhook และการคืนเงินที่พลาดไปจะทิ้งบัญชีที่ถูกคืนเงินให้ยังคงใช้งานอยู่และทำให้คุณเสียเงิน Get Refund History คือแหล่งข้อมูลความจริงแบบ pull ที่คุณกระทบยอดด้วย เพื่อไม่ให้มีสิ่งใดยังเปิดใช้งานอยู่ที่ Apple ได้คืนเงินไปแล้ว
แหล่งข้อมูลและเนื้อหาเพิ่มเติม
- Apple Developer: Get Refund History (App Store Server API)
- Apple Developer: RefundHistoryResponse
- Apple Developer: JWSTransactionDecodedPayload (revocationDate, revocationReason)
- Apple Developer: Get Refund History V1 (deprecated)
- Apple Developer: App Store Server Notifications V2 notificationType (REFUND)
- Apple Developer: Support customers and handle refunds (WWDC21)
RefundHalt
ระบบอัตโนมัติสำหรับการคืนเงินบน App Store และ Google Play
อ่านต่อ
แอปของคุณสามารถแสดงชีตคำขอคืนเงินภายในแอปได้ และนี่คือสิ่งที่ Apple ทำหลังจากลูกค้าแตะส่ง
คำขอคืนเงินภายในแอปของ Apple ให้ลูกค้าขอคืนเงินได้โดยไม่ต้องออกจากแอปของคุณ บนชีตที่ Apple สร้างและตรวจสอบเอง นี่คือสิ่งที่ beginRefundRequest ส่งคืน นาฬิกา CONSUMPTION_REQUEST และ 48 ชั่วโมงที่มันเริ่มจับเวลาบนเซิร์ฟเวอร์ของคุณ และปุ่มนี้คุ้มค่าที่จะปล่อยใช้งานหรือไม่
เมื่อการซื้อบน Google Play ถูกคืนเงินหรือถูก chargeback, Voided Purchases API คือวิธีที่ทำให้คุณรู้
Google Play จะเพิกถอนการซื้อแบบเงียบ ๆ เมื่อมันถูกคืนเงินหรือถูก chargeback. Voided Purchases API คือรายการของออร์เดอร์เหล่านั้น เพื่อให้คุณเพิกถอนสิทธิ์การเข้าถึงได้. นี่คือทุกฟิลด์ หน้าต่าง 30 วัน ตัวเลือก revoke ที่ซ่อนออร์เดอร์ และค่าใช้จ่ายของมัน