สร้างฟังก์ชันเปลี่ยนแพ็กเกจการสมัครใช้บริการด้วย Google Play Billing

1. บทนำ

Codelab นี้สอนวิธีใช้ Google Play Billing Library (PBL) เพื่อจัดการการเปลี่ยนแปลงแพ็กเกจการสมัครใช้บริการ คุณจะได้ทราบว่าโหมดการเปลี่ยนแพ็กเกจต่างๆ ส่งผลต่อราคาและสิทธิ์ของผู้ใช้อย่างไร พร้อมกับเรียนรู้วิธีประมวลผลการแจ้งเตือนแบบเรียลไทม์สำหรับนักพัฒนาแอป (RTDN) ในแบ็กเอนด์

ผู้ชม

Codelab นี้ออกแบบมาสำหรับนักพัฒนาแอป Android โดยมีคำแนะนำในการใช้ฟีเจอร์การจัดการการสมัครใช้บริการที่ซับซ้อน คำแนะนำนี้จะช่วยให้คุณมอบประสบการณ์การอัปเกรด ดาวน์เกรด หรือเปลี่ยนไปใช้แพ็กเกจการสมัครใช้บริการอื่นได้อย่างราบรื่น

สิ่งที่คุณจะได้เรียนรู้...

  • วิธีสร้างการสมัครใช้บริการใน Play Developer Console
  • วิธีเลือก ReplacementMode ที่ถูกต้อง (เช่น WITH_TIME_PRORATION กับ DEFERRED) ให้ตรงกับนโยบายการอัปเกรดและดาวน์เกรดของแอป
  • วิธีกำหนดค่า BillingFlowParams ภายใน launchBillingFlow เพื่อทริกเกอร์ขั้นตอนการซื้อใน Google Play สำหรับการแทนที่แพ็กเกจ
  • วิธีใช้การแจ้งเตือนแบบเรียลไทม์สำหรับนักพัฒนาแอป (RTDN) และ API purchases.subscriptionsv2 เพื่อเพิกถอนสิทธิ์เข้าถึงเก่าและให้สิทธิ์เข้าถึงใหม่ในแบ็กเอนด์อย่างปลอดภัย

สิ่งที่คุณต้องมี

2. สร้างแอปตัวอย่าง

Codelab นี้ใช้แอป Android ตัวอย่างเพื่อแสดงวิธีใช้การแทนที่การสมัครใช้บริการใน PBL แอปตัวอย่างได้รับการออกแบบให้เป็นแอป Android ที่ใช้งานได้อย่างเต็มรูปแบบซึ่งมีซอร์สโค้ดที่สมบูรณ์ซึ่งแสดงลักษณะต่อไปนี้

  • การผสานรวมแอปกับ PBL
  • สร้างฟังก์ชันเปลี่ยนแพ็กเกจการสมัครใช้บริการ

หากคุ้นเคยกับการแทนที่การสมัครใช้บริการและ PBL อยู่แล้ว คุณสามารถดาวน์โหลดแอปตัวอย่างและทดลองใช้ได้

วิดีโอเดโมต่อไปนี้แสดงลักษณะและการทำงานของแอปตัวอย่างหลังจากที่ติดตั้งใช้งานและเรียกใช้

ข้อกำหนดเบื้องต้น

ก่อนที่จะสร้างและติดตั้งใช้งานแอปตัวอย่าง ให้ทำดังนี้

สร้าง

หากต้องการสร้างแอปตัวอย่างตามที่จำเป็นเพื่อทำตาม Codelab ให้ทำดังนี้

  1. ดาวน์โหลดแอปตัวอย่างจาก GitHub
  2. อัปเดต applicationId ภายใน build.gradle ของแอปตัวอย่างให้ตรงกับ Application Id ของแอปใน Play Developer Console
  3. สร้างแอปตัวอย่าง
    หมายเหตุ: การดำเนินการนี้จะสร้างแอปสำหรับการทดสอบในเครื่องได้สำเร็จ อย่างไรก็ตาม การเรียกใช้แอปจะไม่ดึงข้อมูลผลิตภัณฑ์และราคาเนื่องจากยังไม่ได้สร้างการสมัครใช้บริการที่จำเป็นใน Play Developer Console ส่วนถัดไปจะครอบคลุมการสร้างการสมัครใช้บริการใน Developer Console

3. สร้างการสมัครใช้บริการใน Play Console

ระบบการสมัครใช้บริการของ Google Play มอบความยืดหยุ่นในการที่คุณจะสร้าง จัดการ และขายการสมัครใช้บริการ ใน Play Console คุณสามารถกำหนดค่าการสมัครใช้บริการที่มีแพ็กเกจเริ่มต้นหลายแบบ โดยแต่ละแบบมีหลายข้อเสนอได้ ข้อเสนอการสมัครใช้บริการอาจมีรูปแบบการกําหนดราคาและตัวเลือกคุณสมบัติที่หลากหลายได้ สำหรับ Codelab นี้ คุณจะสร้างการสมัครใช้บริการ 3 รายการ ได้แก่ แพ็กเกจพรีเมียม แพ็กเกจพื้นฐาน และแพ็กเกจ Lite ซึ่งจำลองข้อเสนอการสมัครใช้บริการทั่วไปในราคาต่างๆ โดยแต่ละแพ็กเกจจะมีแพ็กเกจเริ่มต้นแบบตามรอบรายเดือนแพ็กเกจเดียว

สร้างการสมัครสมาชิกใหม่

วิธีสร้างการสมัครใช้บริการใหม่

  1. เปิด Play Console แล้วไปที่หน้าการสมัครใช้บริการ (สร้างรายได้ด้วย Play > ผลิตภัณฑ์ > การสมัครใช้บริการ)
  2. คลิกสร้างการสมัครใช้บริการ
  3. ป้อนรายละเอียดการสมัครใช้บริการ
    • ProductID : ป้อนรหัสผลิตภัณฑ์ที่ไม่ซ้ำกัน ป้อน premium_plan
    • ชื่อ : ป้อนชื่อย่อสำหรับการสมัครใช้บริการ เช่น Premium Plan
  4. คลิกสร้าง

สร้าง Base Plan

  1. เปิด Play Console แล้วไปที่หน้าการสมัครใช้บริการ (สร้างรายได้ด้วย Play > ผลิตภัณฑ์ > การสมัครใช้บริการ)
  2. คลิกลูกศรขวาข้างการสมัครใช้บริการที่ต้องการสร้างแพ็กเกจเริ่มต้นในนั้นเพื่อดูรายละเอียดการสมัครใช้บริการดังกล่าว
  3. คลิกเพิ่มแพ็กเกจเริ่มต้น
  4. ป้อนรหัสแพ็กเกจเริ่มต้น ตัวอย่าง monthly-auto-renewing
  5. เลือกประเภทเป็นต่ออายุใหม่อัตโนมัติ
  6. สำหรับแพ็กเกจเริ่มต้นแบบต่ออายุใหม่อัตโนมัติ ให้ตั้งค่าต่อไปนี้
    • ช่วงเวลาที่เรียกเก็บเงิน: รายเดือน
    • ระยะเวลาผ่อนผัน: 7 วัน
    • การเปลี่ยนแปลงแพ็กเกจการเรียกเก็บเงินและข้อเสนอ: เรียกเก็บเงินในวันที่เรียกเก็บเงิน
    • สมัครใช้บริการอีกครั้ง: อนุญาต
  7. ในส่วนราคาและความพร้อมให้บริการ ให้คลิกกำหนดราคาเพื่อกำหนดราคาของแพ็กเกจเริ่มต้น
  8. เลือกประเทศและภูมิภาคทั้งหมด แล้วคลิกกำหนดราคา
  9. ตั้งราคาเป็น $10 สำหรับแพ็กเกจเริ่มต้นนี้ แล้วคลิกอัปเดต
  10. เมื่อตั้งราคาสำหรับแพ็กเกจเริ่มต้นแล้ว ให้คลิกบันทึกที่ด้านขวาล่าง แล้วคลิกเปิดใช้งาน

สร้างการสมัครใช้บริการสำหรับแอปตัวอย่าง

เพื่อวัตถุประสงค์ของ Codelab นี้ ให้สร้างการสมัครใช้บริการเพิ่มเติม 2 รายการที่มีการกำหนดค่าต่อไปนี้

  • แพ็กเกจพื้นฐาน
    • รหัสผลิตภัณฑ์: basic_plan
    • ชื่อ: แพ็กเกจ Basic
    • รหัสแพ็กเกจเริ่มต้น: monthly-auto-renewing
    • ราคา: $5
  • แพ็กเกจ Lite
    • รหัสผลิตภัณฑ์: lite_plan
    • ชื่อ: Lite Plan
    • รหัสแพ็กเกจเริ่มต้น: monthly-auto-renewing
    • ราคา: $3

แอปตัวอย่างได้รับการกำหนดค่าให้ใช้รหัสผลิตภัณฑ์และรหัสแพ็กเกจเริ่มต้นเหล่านี้ คุณสร้างการสมัครใช้บริการที่แตกต่างกันด้วยการกำหนดค่าที่แตกต่างกันได้ ในกรณีนี้ คุณจะต้องแก้ไขแอปตัวอย่างเพื่อใช้รหัสผลิตภัณฑ์ที่คุณสร้างขึ้น

วิดีโอการสร้างการสมัครใช้บริการ

วิดีโอต่อไปนี้แสดงขั้นตอนที่อธิบายไว้ก่อนหน้านี้ในการสร้างการสมัครใช้บริการใน Play Developer Console

4. การเปลี่ยนการสมัครใช้บริการ

นักพัฒนาแอปที่ผสานรวมกับ PBL สามารถมอบตัวเลือกต่างๆ ให้แก่สมาชิกปัจจุบันเพื่อเปลี่ยนแพ็กเกจการสมัครใช้บริการให้ตรงกับความต้องการมากขึ้นได้โดยทำดังนี้

  • หากขายการสมัครใช้บริการหลายระดับ เช่น การสมัครใช้บริการขั้นพื้นฐานและพรีเมียม คุณสามารถอนุญาตให้ผู้ใช้เปลี่ยนระดับได้โดยการซื้อแพ็กเกจเริ่มต้นหรือข้อเสนอของการสมัครใช้บริการอื่น
  • คุณอนุญาตให้ผู้ใช้เปลี่ยนช่วงเวลาที่เรียกเก็บเงินปัจจุบันได้ เช่น เปลี่ยนจากแพ็กเกจรายเดือนเป็นแพ็กเกจรายปี
  • นอกจากนี้ คุณยังอนุญาตให้ผู้ใช้เปลี่ยนแพ็กเกจระหว่างแบบต่ออายุใหม่อัตโนมัติกับแบบชำระล่วงหน้าได้ด้วย

เมื่อผู้ใช้ตัดสินใจอัปเกรด ดาวน์เกรด หรือเปลี่ยนการสมัครใช้บริการ คุณจะต้องระบุโหมดการแทนที่ที่จะกำหนดวิธีใช้มูลค่าตามสัดส่วนของช่วงเวลาที่เรียกเก็บเงินปัจจุบัน และเวลาที่ผู้ใช้จะได้รับการเปลี่ยนแปลงสิทธิ์

Play Billing Library มีตัวเลือก ReplacementMode หลายตัวเลือกในการควบคุมลักษณะการทำงานนี้

โหมดการแทนที่ที่ใช้ได้

  • WITH_TIME_PRORATION: ระบบจะอัปเกรดหรือดาวน์เกรดรายการการสมัครใช้บริการทันที ระบบจะปรับเวลาที่เหลือตามส่วนต่างของราคาและเครดิตสำหรับการสมัครใช้บริการใหม่โดยการอัปเดตวันที่เรียกเก็บเงินครั้งถัดไป นี่คือลักษณะการทำงานเริ่มต้น
  • CHARGE_PRORATED_PRICE: ระบบจะอัปเกรดรายการการสมัครใช้บริการทันที และรอบการเรียกเก็บเงินจะยังคงเหมือนเดิม จากนั้นระบบจะเรียกเก็บเงินส่วนต่างของราคาสำหรับระยะเวลาที่เหลือจากผู้ใช้
  • CHARGE_FULL_PRICE: ระบบจะอัปเกรดหรือดาวน์เกรดรายการการสมัครใช้บริการทันที และจะเรียกเก็บเงินจากผู้ใช้ในราคาเต็มสำหรับสิทธิ์ใหม่นี้ทันที ระบบจะโอนมูลค่าคงเหลือจากการสมัครใช้บริการก่อนหน้าไปยังสิทธิ์เดียวกัน หรือจะคิดตามสัดส่วนเวลาเมื่อเปลี่ยนไปใช้สิทธิ์อื่น
  • WITHOUT_PRORATION: ระบบจะอัปเกรดหรือดาวน์เกรดรายการการสมัครใช้บริการทันที และจะเรียกเก็บเงินในราคาใหม่เมื่อการสมัครใช้บริการต่ออายุ รอบการเรียกเก็บเงินจะยังคงเหมือนเดิม
  • DEFERRED: ระบบจะอัปเกรดหรือดาวน์เกรดรายการการสมัครใช้บริการเมื่อต่ออายุการสมัครใช้บริการเท่านั้น

5. WITH_TIME_PRORATION

ในโหมดการเปลี่ยนทดแทนนี้ ระบบจะอัปเกรดหรือดาวน์เกรดรายการการสมัครใช้บริการทันที ระบบจะปรับเวลาที่เหลืออยู่ตามส่วนต่างของราคา และจะให้เครดิตสำหรับการสมัครใช้บริการใหม่โดยเลื่อนวันที่เรียกเก็บเงินครั้งถัดไป นี่คือลักษณะการทำงานเริ่มต้น

สถานการณ์ตัวอย่าง

ผู้ใช้เปลี่ยนจากแพ็กเกจ Basic ($4.99 ต่อเดือน) เป็นแพ็กเกจ Premium ($9.99 ต่อเดือน) ในวันที่ 15 เมษายน ซึ่งเป็นช่วงครึ่งรอบการเรียกเก็บเงินรายเดือน

ในสถานการณ์นี้จะมีผลดังต่อไปนี้

  • ผู้ใช้จะได้รับสิทธิ์เข้าถึงแพ็กเกจ Premium ทันที
  • Google Play จะคำนวณระยะเวลาตามสัดส่วนโดยอัตโนมัติ สมมติว่า Play คำนวณแล้วว่าแพ็กเกจพื้นฐานที่เหลืออีก 15 วันมีมูลค่าเท่ากับแพ็กเกจพรีเมียม 7 วัน ระบบจะเลื่อนวันที่เรียกเก็บเงินครั้งถัดไปเป็นวันที่ 21 เมษายน
  • ผู้ใช้ไม่จำเป็นต้องชำระเงินทันที

ข้อมูลโค้ด

// ProductDetails for the plan to be switched to
ProductDetails productDetails = ...;
// The specific offer token for the toBeSwitched plan's base plan
String offerToken = "...";
// The purchase token of the user's current subscription
String oldPurchaseToken = "...";
// The productId for the user's current subscription
String oldProductId = "...";
// The replacementMode to replace the user's subscription
int replacementMode = SubscriptionProductReplacementParams.ReplacementMode.WITH_TIME_PRORATION;

SubscriptionProductReplacementParams subscriptionProductReplacementParams =
    SubscriptionProductReplacementParams.newBuilder()
        .setOldProductId(oldProductId)
        .setReplacementMode(replacementMode)
        .build();

ProductDetailsParams productDetailsParams =
    ProductDetailsParams.newBuilder()
        .setProductDetails(productDetails)
        .setSubscriptionProductReplacementParams(subscriptionProductReplacementParams)
        .setOfferToken(offerToken)
        .build();

List<ProductDetailsParams> productDetailsParamsList = ImmutableList.of(productDetailsParams);

BillingFlowParams billingFlowParams =
    BillingFlowParams.newBuilder()
        .setProductDetailsParamsList(productDetailsParamsList)
        .setSubscriptionUpdateParams(
            SubscriptionUpdateParams.newBuilder().setOldPurchaseToken(oldPurchaseToken).build())
        .build();

billingClient.launchBillingFlow(activity, billingFlowParams);

อัปเกรดด้วย WITH_TIME_PRORATION

วิธีจำลองสถานการณ์นี้

  • ใน MainActivity ของแอปตัวอย่าง ให้อัปเดต replacementMode ในข้อมูลโค้ดเป็น SubscriptionProductReplacementParams.ReplacementMode.WITH_TIME_PRORATION
  • สร้างและเปิดใช้แอปพลิเคชันอีกครั้ง
  • ยกเลิกการสมัครใช้บริการที่มีอยู่ (หากมี) จาก Google Play Store และการสมัครใช้บริการจะหมดอายุ
  • ซื้อแพ็กเกจ Basic
  • เปลี่ยนไปใช้แพ็กเกจ Premium

ระบบจะอัปเกรดสิทธิ์ของผู้ใช้เป็นแพ็กเกจ Premium ทันที จำนวนเงินที่ผู้ใช้ต้องชำระทันทีคือ $0.00 ระบบจะนำมูลค่าที่เหลือของแพ็กเกจ Basic มาคำนวณตามสัดส่วนเป็นเวลาสำหรับแพ็กเกจ Premium ซึ่งจะเลื่อนวันที่ต่ออายุครั้งถัดไปให้เร็วขึ้น ระบบจะเรียกเก็บเงินค่าต่ออายุจากผู้ใช้เป็นจำนวน $9.99 ในวันที่เรียกเก็บเงินที่ปรับใหม่

ลดรุ่นด้วย WITH_TIME_PRORATION

วิธีจำลองสถานการณ์นี้

  • ใน MainActivity ของแอปตัวอย่าง ให้อัปเดต replacementMode ในข้อมูลโค้ดเป็น SubscriptionProductReplacementParams.ReplacementMode.WITH_TIME_PRORATION
  • สร้างและเปิดใช้แอปพลิเคชันอีกครั้ง
  • ยกเลิกการสมัครใช้บริการที่มีอยู่ (หากมี) จาก Google Play Store และการสมัครใช้บริการจะหมดอายุ
  • ซื้อแพ็กเกจ Basic
  • เปลี่ยนไปใช้แพ็กเกจ Lite

ระบบจะลดระดับสิทธิ์ของผู้ใช้เป็นแพ็กเกจ Lite ทันที จำนวนเงินที่ต้องชำระทันทีคือ $0.00 ระบบจะนำมูลค่าที่เหลือของแพ็กเกจ Basic มาคำนวณตามสัดส่วนเป็นเวลาสำหรับแพ็กเกจ Lite ซึ่งจะขยายวันที่ต่ออายุครั้งถัดไปอย่างมาก ระบบจะเรียกเก็บเงินค่าต่ออายุจากผู้ใช้เป็นจำนวน $2.99 ในวันที่เรียกเก็บเงินที่ปรับใหม่

บทสรุป

ในส่วนนี้ คุณได้เรียนรู้วิธีที่ WITH_TIME_PRORATION แก้ไขสิทธิ์ของผู้ใช้โดยไม่มีการเรียกเก็บเงินทันทีด้วยการปรับเวลาจนกว่าจะถึงการต่ออายุครั้งถัดไปตามส่วนต่างของราคา ซึ่งเป็นกลยุทธ์เริ่มต้นที่มีประสิทธิภาพในการอัปเกรดหรือดาวน์เกรดผู้ใช้

6. CHARGE_PRORATED_PRICE

ในโหมดการแทนที่นี้ ระบบจะอัปเกรดรายการการสมัครใช้บริการทันที และรอบการเรียกเก็บเงินจะยังคงเหมือนเดิม จากนั้นระบบจะเรียกเก็บเงินส่วนต่างของราคาสำหรับระยะเวลาที่เหลือจากผู้ใช้

หมายเหตุ: ตัวเลือกนี้ใช้ได้กับการอัปเกรดรายการการสมัครใช้บริการเท่านั้น ซึ่งราคาต่อหน่วยเวลาจะเพิ่มขึ้น

สถานการณ์ตัวอย่าง

ผู้ใช้แพ็กเกจ Basic ($4.99 ต่อเดือน) ตัดสินใจอัปเกรดเป็นแพ็กเกจ Premium ($9.99 ต่อเดือน) ในวันที่ 20 เมษายน โดยมีระยะเวลาเหลืออีกประมาณ 10 วันในรอบการเรียกเก็บเงินรายเดือน

ในสถานการณ์นี้จะมีผลดังต่อไปนี้

  • ผู้ใช้จะได้รับสิทธิ์เข้าถึงแพ็กเกจ Premium ทันที
  • ระบบจะเรียกเก็บเงินจากผู้ใช้ทันทีตามส่วนต่างที่คิดตามสัดส่วนสำหรับ 10 วันที่เหลือของรอบการเรียกเก็บเงินปัจจุบัน ซึ่งคิดเป็นเงินประมาณ $2.99 ซึ่งเป็นค่าแพ็กเกจ Premium เป็นเวลา 10 วัน
  • วันที่เรียกเก็บเงินสำหรับผู้ใช้จะไม่เปลี่ยนแปลง

ข้อมูลโค้ด

// ProductDetails for the plan to be switched to
ProductDetails productDetails = ...;
// The specific offer token for the toBeSwitched plan's base plan
String offerToken = "...";
// The purchase token of the user's current subscription
String oldPurchaseToken = "...";
// The productId for the user's current subscription
String oldProductId = "...";
// The replacementMode to replace the user's subscription
int replacementMode = SubscriptionProductReplacementParams.ReplacementMode.CHARGE_PRORATED_PRICE;

SubscriptionProductReplacementParams subscriptionProductReplacementParams =
    SubscriptionProductReplacementParams.newBuilder()
        .setOldProductId(oldProductId)
        .setReplacementMode(replacementMode)
        .build();

ProductDetailsParams productDetailsParams =
    ProductDetailsParams.newBuilder()
        .setProductDetails(productDetails)
        .setSubscriptionProductReplacementParams(subscriptionProductReplacementParams)
        .setOfferToken(offerToken)
        .build();

List<ProductDetailsParams> productDetailsParamsList = ImmutableList.of(productDetailsParams);

BillingFlowParams billingFlowParams =
    BillingFlowParams.newBuilder()
        .setProductDetailsParamsList(productDetailsParamsList)
        .setSubscriptionUpdateParams(
            SubscriptionUpdateParams.newBuilder().setOldPurchaseToken(oldPurchaseToken).build())
        .build();

billingClient.launchBillingFlow(activity, billingFlowParams);

อัปเกรดด้วย CHARGE_PRORATED_PRICE

วิธีจำลองสถานการณ์นี้

  • ใน MainActivity ของแอปตัวอย่าง ให้อัปเดต replacementMode ในข้อมูลโค้ดเป็น SubscriptionProductReplacementParams.ReplacementMode.CHARGE_PRORATED_PRICE
  • สร้างและเปิดใช้แอปพลิเคชันอีกครั้ง
  • ยกเลิกการสมัครใช้บริการที่มีอยู่ (หากมี) จาก Google Play Store และการสมัครใช้บริการจะหมดอายุ
  • ซื้อแพ็กเกจ Basic
  • เปลี่ยนไปใช้แพ็กเกจ Premium

ระบบจะอัปเกรดผู้ใช้เป็นแพ็กเกจ Premium ทันที โดยจะยังคงใช้วันที่ต่ออายุเดิม จำนวนเงินที่ต้องชำระทันทีคือส่วนต่างตามสัดส่วนระหว่างราคาแพ็กเกจ Premium และ Basic สำหรับวันที่เหลือของรอบปัจจุบัน เมื่อถึงวันที่ต่ออายุ ระบบจะเรียกเก็บเงินจากผู้ใช้ในจำนวนเต็มสำหรับการต่ออายุ Premium ซึ่งก็คือ $9.99

ดาวน์เกรดด้วย CHARGE_PRORATED_PRICE

วิธีจำลองสถานการณ์นี้

  • ใน MainActivity ของแอปตัวอย่าง ให้อัปเดต replacementMode ในข้อมูลโค้ดเป็น SubscriptionProductReplacementParams.ReplacementMode.CHARGE_PRORATED_PRICE
  • สร้างและเปิดใช้แอปพลิเคชันอีกครั้ง
  • ยกเลิกการสมัครใช้บริการที่มีอยู่ (หากมี) จาก Google Play Store และการสมัครใช้บริการจะหมดอายุ
  • ซื้อแพ็กเกจ Basic
  • เปลี่ยนไปใช้แพ็กเกจ Lite

โหมดการแทนที่นี้จะทำให้เกิดข้อผิดพลาดระหว่างการดาวน์เกรดเนื่องจากใช้ได้กับการอัปเกรดรายการการสมัครใช้บริการที่ราคาต่อหน่วยเวลาเพิ่มขึ้นเท่านั้น ขั้นตอนการเรียกเก็บเงินจะล้มเหลวและแสดงข้อผิดพลาดแก่ผู้ใช้โดยระบุว่าระบบไม่รองรับโหมดการปันส่วนสำหรับการลดรุ่น

บทสรุป

ส่วนนี้อธิบายวิธีที่ CHARGE_PRORATED_PRICE อนุญาตให้อัปเกรดได้ทันทีโดยการเรียกเก็บเงินจากผู้ใช้ตามส่วนต่างของราคาที่แน่นอนสำหรับช่วงเวลาที่เรียกเก็บเงินที่เหลืออยู่ ในขณะที่รอบการเรียกเก็บเงินยังคงเหมือนเดิม ซึ่งจะมีประโยชน์เมื่อผู้ใช้ต้องการอัปเกรดเป็นแพ็กเกจที่แพงขึ้นโดยไม่เปลี่ยนวันที่เรียกเก็บเงิน

7. CHARGE_FULL_PRICE

ในโหมดการแทนที่นี้ ระบบจะอัปเกรดหรือดาวน์เกรดรายการการสมัครใช้บริการทันที และจะเรียกเก็บเงินจากผู้ใช้ในราคาเต็มสำหรับสิทธิ์ใหม่ทันที ระบบจะโอนมูลค่าคงเหลือจากการสมัครใช้บริการก่อนหน้าไปยังสิทธิ์เดียวกัน หรือจะคิดตามสัดส่วนเวลาเมื่อเปลี่ยนไปใช้สิทธิ์อื่น

สถานการณ์ตัวอย่าง

ผู้ใช้ใช้แพ็กเกจ Basic ($4.99 ต่อเดือน เริ่มตั้งแต่วันที่ 1 เมษายน) ในวันที่ 20 เมษายน ผู้ใช้ต้องการเปลี่ยนไปใช้แพ็กเกจ Premium ($9.99 ต่อเดือน)

ในสถานการณ์นี้จะมีผลดังต่อไปนี้

  • ระบบจะเรียกเก็บเงินจากผู้ใช้ในราคาเต็มของแพ็กเกจ Premium ($9.99) ทันที
  • ระบบจะแปลงมูลค่าที่เหลือจากแพ็กเกจ Basic (เช่น มูลค่า 10 วัน) เป็นเวลาเทียบเท่าสำหรับแพ็กเกจ Premium ในตัวอย่างนี้ Basic 10 วันจะเทียบเท่ากับ Premium 5 วัน
  • ระบบจะปรับวันที่ต่ออายุครั้งถัดไปของผู้ใช้ให้รวมเวลาตามสัดส่วนนี้ ดังนั้น วันที่ต่ออายุจึงเป็นวันที่ 25 พฤษภาคม (20 เมษายน + 1 เดือน + 5 วัน)
  • การต่ออายุหลังจากนี้จะเกิดขึ้นทุกเดือนโดยเริ่มตั้งแต่วันที่ 25 พฤษภาคม

ข้อมูลโค้ด

// ProductDetails for the plan to be switched to
ProductDetails productDetails = ...;
// The specific offer token for the toBeSwitched plan's base plan
String offerToken = "...";
// The purchase token of the user's current subscription
String oldPurchaseToken = "...";
// The productId for the user's current subscription
String oldProductId = "...";
// The replacementMode to replace the user's subscription
int replacementMode = SubscriptionProductReplacementParams.ReplacementMode.CHARGE_FULL_PRICE;

SubscriptionProductReplacementParams subscriptionProductReplacementParams =
    SubscriptionProductReplacementParams.newBuilder()
        .setOldProductId(oldProductId)
        .setReplacementMode(replacementMode)
        .build();

ProductDetailsParams productDetailsParams =
    ProductDetailsParams.newBuilder()
        .setProductDetails(productDetails)
        .setSubscriptionProductReplacementParams(subscriptionProductReplacementParams)
        .setOfferToken(offerToken)
        .build();

List<ProductDetailsParams> productDetailsParamsList = ImmutableList.of(productDetailsParams);

BillingFlowParams billingFlowParams =
    BillingFlowParams.newBuilder()
        .setProductDetailsParamsList(productDetailsParamsList)
        .setSubscriptionUpdateParams(
            SubscriptionUpdateParams.newBuilder().setOldPurchaseToken(oldPurchaseToken).build())
        .build();

billingClient.launchBillingFlow(activity, billingFlowParams);

อัปเกรดด้วย CHARGE_FULL_PRICE

วิธีจำลองสถานการณ์นี้

  • ใน MainActivity ของแอปตัวอย่าง ให้อัปเดต replacementMode ในข้อมูลโค้ดเป็น SubscriptionProductReplacementParams.ReplacementMode.CHARGE_FULL_PRICE
  • สร้างและเปิดใช้แอปพลิเคชันอีกครั้ง
  • ยกเลิกการสมัครใช้บริการที่มีอยู่ (หากมี) จาก Google Play Store และการสมัครใช้บริการจะหมดอายุ
  • ซื้อแพ็กเกจ Basic
  • เปลี่ยนไปใช้แพ็กเกจ Premium

ระบบจะอัปเกรดผู้ใช้เป็นแพ็กเกจ Premium ทันที จำนวนเงินที่ต้องชำระทันทีคือราคาเต็มของแพ็กเกจ Premium ซึ่งอยู่ที่ $9.99 ระบบจะแปลงมูลค่าที่เหลือจากแพ็กเกจพื้นฐานเป็นเวลาในแพ็กเกจ Premium ใหม่ ซึ่งจะขยายวันที่ต่ออายุครั้งแรกออกไปเล็กน้อย หลังจากนั้น ระบบจะเรียกเก็บเงินสำหรับการต่ออายุรอบละ $9.99

ดาวน์เกรดด้วย CHARGE_FULL_PRICE

วิธีจำลองสถานการณ์นี้

  • ใน MainActivity ของแอปตัวอย่าง ให้อัปเดต replacementMode ในข้อมูลโค้ดเป็น SubscriptionProductReplacementParams.ReplacementMode.CHARGE_FULL_PRICE
  • สร้างและเปิดใช้แอปพลิเคชันอีกครั้ง
  • ยกเลิกการสมัครใช้บริการที่มีอยู่ (หากมี) จาก Google Play Store และการสมัครใช้บริการจะหมดอายุ
  • ซื้อแพ็กเกจ Basic
  • เปลี่ยนไปใช้แพ็กเกจ Lite

ระบบจะลดรุ่นผู้ใช้เป็นแพ็กเกจ Lite ทันทีและจะเริ่มรอบการเรียกเก็บเงินใหม่ จำนวนเงินที่ต้องชำระทันทีคือราคาเป้าหมายเต็มจำนวน $2.99 ระบบจะเฉลี่ยสัดส่วนที่ไม่ได้ใช้ของแพ็กเกจ Basic เป็นเวลาสำหรับแพ็กเกจ Lite ใหม่ ซึ่งจะขยายวันที่ต่ออายุครั้งแรก หลังจากนั้น ค่าต่ออายุจะอยู่ที่ $2.99 ต่อรอบ

บทสรุป

ในส่วนนี้ เราได้อธิบายวิธีที่ CHARGE_FULL_PRICE เรียกเก็บเงินจากผู้ใช้เต็มจำนวนในวันที่เปลี่ยนแพ็กเกจ ซึ่งจะเริ่มรอบการเรียกเก็บเงินใหม่ทันที ยอดคงเหลือที่เหลือจากแพ็กเกจก่อนหน้าจะนำไปใช้กับวันที่ต่ออายุถัดไปตามสัดส่วน

8. WITHOUT_PRORATION

ในโหมดการแทนที่นี้ ระบบจะอัปเกรดหรือดาวน์เกรดรายการการสมัครใช้บริการทันที และจะเรียกเก็บเงินในราคาใหม่เมื่อการสมัครใช้บริการต่ออายุ

สถานการณ์ตัวอย่าง

ผู้ใช้ใช้แพ็กเกจ Basic ($4.99 ต่อเดือน เริ่มตั้งแต่วันที่ 1 เมษายน) ในวันที่ 20 เมษายน ผู้ใช้ต้องการเปลี่ยนไปใช้แพ็กเกจ Premium ($9.99 ต่อเดือน)

ในสถานการณ์นี้จะมีผลดังต่อไปนี้

  • ผู้ใช้จะได้รับสิทธิ์เข้าถึงแพ็กเกจ Premium ทันที
  • ผู้ใช้ไม่ต้องจ่ายในราคาที่สูงขึ้นที่ $9.99 จนกว่าจะถึงวันที่ต่ออายุการสมัครใช้บริการครั้งถัดไป (1 พฤษภาคม)

ข้อมูลโค้ด

// ProductDetails for the plan to be switched to
ProductDetails productDetails = ...;
// The specific offer token for the toBeSwitched plan's base plan
String offerToken = "...";
// The purchase token of the user's current subscription
String oldPurchaseToken = "...";
// The productId for the user's current subscription
String oldProductId = "...";
// The replacementMode to replace the user's subscription
int replacementMode = SubscriptionProductReplacementParams.ReplacementMode.WITHOUT_PRORATION;

SubscriptionProductReplacementParams subscriptionProductReplacementParams =
    SubscriptionProductReplacementParams.newBuilder()
        .setOldProductId(oldProductId)
        .setReplacementMode(replacementMode)
        .build();

ProductDetailsParams productDetailsParams =
    ProductDetailsParams.newBuilder()
        .setProductDetails(productDetails)
        .setSubscriptionProductReplacementParams(subscriptionProductReplacementParams)
        .setOfferToken(offerToken)
        .build();

List<ProductDetailsParams> productDetailsParamsList = ImmutableList.of(productDetailsParams);

BillingFlowParams billingFlowParams =
    BillingFlowParams.newBuilder()
        .setProductDetailsParamsList(productDetailsParamsList)
        .setSubscriptionUpdateParams(
            SubscriptionUpdateParams.newBuilder().setOldPurchaseToken(oldPurchaseToken).build())
        .build();

billingClient.launchBillingFlow(activity, billingFlowParams);

อัปเกรดด้วย WITHOUT_PRORATION

วิธีจำลองสถานการณ์นี้

  • ใน MainActivity ของแอปตัวอย่าง ให้อัปเดต replacementMode ในข้อมูลโค้ดเป็น SubscriptionProductReplacementParams.ReplacementMode.WITHOUT_PRORATION
  • สร้างและเปิดใช้แอปพลิเคชันอีกครั้ง
  • ยกเลิกการสมัครใช้บริการที่มีอยู่ (หากมี) จาก Google Play Store และการสมัครใช้บริการจะหมดอายุ
  • ซื้อแพ็กเกจ Basic
  • เปลี่ยนไปใช้แพ็กเกจ Premium

ระบบจะอัปเกรดผู้ใช้เป็นแพ็กเกจ Premium ทันทีพร้อมคงวันที่ต่ออายุเดิมไว้ จำนวนเงินที่ต้องชำระทันทีคือ $0.00 ผู้ใช้จะมีสิทธิ์เข้าถึงแพ็กเกจ Premium ในรอบที่กำหนดเป็นระยะเวลาที่เหลือ ก่อนที่จะเปลี่ยนไปใช้จำนวนเงินต่ออายุใหม่ที่ $9.99 ในวันที่เรียกเก็บเงินครั้งถัดไป

ดาวน์เกรดโดยไม่มีการปันส่วน

วิธีจำลองสถานการณ์นี้

  • ใน MainActivity ของแอปตัวอย่าง ให้อัปเดต replacementMode ในข้อมูลโค้ดเป็น SubscriptionProductReplacementParams.ReplacementMode.WITHOUT_PRORATION
  • สร้างและเปิดใช้แอปพลิเคชันอีกครั้ง
  • ยกเลิกการสมัครใช้บริการที่มีอยู่ (หากมี) จาก Google Play Store และการสมัครใช้บริการจะหมดอายุ
  • ซื้อแพ็กเกจ Basic
  • เปลี่ยนไปใช้แพ็กเกจ Lite

ระบบจะดาวน์เกรดผู้ใช้เป็นแพ็กเกจ Lite ทันที และผู้ใช้จะเสียสิทธิ์เข้าถึงฟีเจอร์ Basic ที่ชำระเงินไปแล้ว จำนวนเงินที่ต้องชำระทันทีคือ $0.00 รอบการเรียกเก็บเงินจะยังคงเหมือนเดิม และผู้ใช้จะชำระเงินในราคาใหม่ที่ต่ำกว่าที่ $2.99 ในการต่ออายุที่กำหนดเวลาไว้ครั้งถัดไป

บทสรุป

ส่วนนี้แสดงให้เห็นว่า WITHOUT_PRORATION จะสลับสิทธิ์ของผู้ใช้ทันทีโดยไม่มีการเรียกเก็บเงินในระหว่างการชำระเงิน ในขณะที่รอบการเรียกเก็บเงินยังคงเหมือนเดิม

9. เลื่อน

ในโหมดการแทนที่นี้ ระบบจะอัปเกรดหรือดาวน์เกรดรายการการสมัครใช้บริการเมื่อต่ออายุการสมัครใช้บริการเท่านั้น แต่จะออกการซื้อใหม่ทันที โดยระบบจะตั้งค่าไอเทมที่มีอยู่ให้ต่ออายุไม่ได้และจะหมดอายุเมื่อสิ้นสุดรอบการเรียกเก็บเงินปัจจุบัน ในขณะที่สิทธิ์ใหม่ที่ขอจะเริ่มใช้งานได้ทันทีหลังจากนั้น

สถานการณ์ตัวอย่าง

ผู้ใช้ใช้แพ็กเกจ Basic ($4.99 ต่อเดือน เริ่มตั้งแต่วันที่ 1 เมษายน) ในวันที่ 20 เมษายน ผู้ใช้ต้องการเปลี่ยนไปใช้แพ็กเกจ Premium ($9.99 ต่อเดือน)

ในสถานการณ์นี้จะมีผลดังต่อไปนี้

  • ระบบจะไม่เรียกเก็บเงินจากผู้ใช้ทันที
  • ผู้ใช้จะยังคงได้รับฟีเจอร์ Basic จนกว่าจะสิ้นสุดรอบการเรียกเก็บเงินปัจจุบัน (30 เมษายน)
  • ระบบจะอัปเกรดแพ็กเกจการสมัครใช้บริการเป็น Premium โดยอัตโนมัติในวันที่ต่ออายุครั้งถัดไป (1 พฤษภาคม)

ข้อมูลโค้ด

// ProductDetails for the plan to be switched to
ProductDetails productDetails = ...;
// The specific offer token for the toBeSwitched plan's base plan
String offerToken = "...";
// The purchase token of the user's current subscription
String oldPurchaseToken = "...";
// The productId for the user's current subscription
String oldProductId = "...";
// The replacementMode to replace the user's subscription
int replacementMode = SubscriptionProductReplacementParams.ReplacementMode.DEFERRED;

SubscriptionProductReplacementParams subscriptionProductReplacementParams =
    SubscriptionProductReplacementParams.newBuilder()
        .setOldProductId(oldProductId)
        .setReplacementMode(replacementMode)
        .build();

ProductDetailsParams productDetailsParams =
    ProductDetailsParams.newBuilder()
        .setProductDetails(productDetails)
        .setSubscriptionProductReplacementParams(subscriptionProductReplacementParams)
        .setOfferToken(offerToken)
        .build();

List<ProductDetailsParams> productDetailsParamsList = ImmutableList.of(productDetailsParams);

BillingFlowParams billingFlowParams =
    BillingFlowParams.newBuilder()
        .setProductDetailsParamsList(productDetailsParamsList)
        .setSubscriptionUpdateParams(
            SubscriptionUpdateParams.newBuilder().setOldPurchaseToken(oldPurchaseToken).build())
        .build();

billingClient.launchBillingFlow(activity, billingFlowParams);

อัปเกรดด้วย DEFERRED

วิธีจำลองสถานการณ์นี้

  • ใน MainActivity ของแอปตัวอย่าง ให้อัปเดต replacementMode ในข้อมูลโค้ดเป็น SubscriptionProductReplacementParams.ReplacementMode.DEFERRED
  • สร้างและเปิดใช้แอปพลิเคชันอีกครั้ง
  • ยกเลิกการสมัครใช้บริการที่มีอยู่ (หากมี) จาก Google Play Store และการสมัครใช้บริการจะหมดอายุ
  • ซื้อแพ็กเกจ Basic
  • เปลี่ยนไปใช้แพ็กเกจ Premium

ผู้ใช้จะยังคงใช้แพ็กเกจ Basic จนกว่าจะสิ้นสุดรอบการเรียกเก็บเงินปัจจุบัน จำนวนเงินที่ต้องชำระทันทีคือ $0.00 เมื่อถึงวันที่ต่ออายุ สิทธิ์ของลูกค้าจะอัปเกรดเป็นแพ็กเกจ Premium และระบบจะเรียกเก็บเงินค่าต่ออายุใหม่จำนวน $9.99

ดาวน์เกรดด้วย DEFERRED

วิธีจำลองสถานการณ์นี้

  • ใน MainActivity ของแอปตัวอย่าง ให้อัปเดต replacementMode ในข้อมูลโค้ดเป็น SubscriptionProductReplacementParams.ReplacementMode.DEFERRED
  • สร้างและเปิดใช้แอปพลิเคชันอีกครั้ง
  • ยกเลิกการสมัครใช้บริการที่มีอยู่ (หากมี) จาก Google Play Store และการสมัครใช้บริการจะหมดอายุ
  • ซื้อแพ็กเกจ Basic
  • เปลี่ยนไปใช้แพ็กเกจ Lite

ผู้ใช้จะยังคงใช้แพ็กเกจ Basic จนกว่าจะสิ้นสุดรอบการเรียกเก็บเงินปัจจุบัน จำนวนเงินที่ต้องชำระทันทีคือ $0.00 เมื่อถึงวันที่ต่ออายุ สิทธิ์ของลูกค้าจะอัปเกรดเป็นแพ็กเกจ Lite และระบบจะเรียกเก็บเงินค่าต่ออายุใหม่จำนวน $2.99

บทสรุป

ส่วนนี้อธิบายว่าโหมดการแทนที่ DEFERRED จะเลื่อนการอัปเกรดหรือดาวน์เกรดจนกว่าจะสิ้นสุดระยะเวลาที่ผู้ใช้ที่ใช้งานอยู่ชำระเงินแล้วได้อย่างไร ซึ่งเหมาะอย่างยิ่งสำหรับการลดรุ่นเพื่อไม่ให้เสียฟีเจอร์ที่ซื้อไปแล้ว

10. การประมวลผลฝั่งแบ็กเอนด์และฝั่งไคลเอ็นต์

หลังจากที่ผู้ใช้ทริกเกอร์การแทนที่การสมัครใช้บริการที่สำเร็จแล้ว ให้ตรวจสอบว่าทั้งแอปและแบ็กเอนด์ของคุณจัดการการเปลี่ยนแปลงอย่างถูกต้องเพื่อหลีกเลี่ยงปัญหาต่างๆ เช่น การหยุดชะงักของบริการหรือการเรียกเก็บเงินซ้ำ

สถานการณ์ตัวอย่าง

  • ผู้ใช้มีแพ็กเกจรายเดือนพื้นฐาน (product_id basic_plan และ purchase_token basic_purchase_token_123)
  • ผู้ใช้เปลี่ยนไปใช้แพ็กเกจ Premium โดยใช้โหมดการเปลี่ยนแทนทันที (WITHOUT_PRORATION, WITH_TIME_PRORATION, CHARGE_PRORATED_PRICE, CHARGE_FULL_PRICE อย่างใดอย่างหนึ่ง)
  • เมื่อเปลี่ยนการสมัครใช้บริการสำเร็จแล้ว Google จะถือว่าเป็นการสมัครใช้บริการใหม่ และสร้างโทเค็นการซื้อใหม่ที่แตกต่างกันสำหรับแพ็กเกจ Premium (product_id premium_plan และ purchase_token premium_purchase_token_123)

การประมวลผลฝั่งไคลเอ็นต์

onPurchasesUpdated

เมื่อการซื้อทดแทนเสร็จสมบูรณ์ ระบบจะทริกเกอร์ PurchasesUpdatedListener แม้ว่าจะเป็นการเปลี่ยน แต่ Google Play จะถือว่าแพ็กเกจ Premium เป็นการซื้อใหม่

แอปจะได้รับออบเจ็กต์ Purchase ที่มี premium_purchase_token_123 purchase token และ product_id premium_plan คุณต้องปฏิบัติต่อผู้ใช้รายนี้เหมือนกับสมาชิกใหม่ทุกประการ นั่นคือ ยืนยันโทเค็นและเตรียมให้สิทธิ์เข้าถึง

@Override
public void onPurchasesUpdated(BillingResult billingResult, List<Purchase> purchases) {
    if (billingResult.getResponseCode() == BillingResponseCode.OK && purchases != null) {
        for (Purchase purchase : purchases) {
            // purchase.getPurchaseToken() = premium_purchase_token_123
            // purchase.getProducts() will contain premium_plan
            // Verify the purchase and grant entitlement
            handleNewPurchase(purchase);
        }
    }
}

queryPurchasesAsync

queryPurchasesAsync จะแสดงเฉพาะการสมัครใช้บริการที่ใช้งานอยู่ซึ่งซื้อจากแอปของคุณ คุณควรใช้วิธีนี้เพื่อพิจารณาสิทธิ์ที่จะแสดงต่อผู้ใช้ สำหรับการเปลี่ยนทดแทนทันที queryPurchasesAsync() จะหยุดส่งคืนโทเค็นการซื้อ BASIC เก่าและจะส่งคืนเฉพาะโทเค็นการซื้อ PREMIUM ใหม่เท่านั้น

เรียกใช้เมธอดนี้ทุกครั้งที่แอปกลับมาทำงานต่อหรือการซื้อเสร็จสมบูรณ์ หากมีโทเค็น Premium ให้มอบสิทธิ์เข้าถึงฟีเจอร์ Premium และนำฟีเจอร์พื้นฐานออกทันที

การประมวลผลแบ็กเอนด์ (RTDN)

เมื่อมีการเปลี่ยนทดแทน Google Play จะส่งการแจ้งเตือนแบบเรียลไทม์สำหรับนักพัฒนาแอป (RTDN) ไปยังหัวข้อ Pub/Sub ที่กำหนดค่าไว้

  • ในกรณีของการเปลี่ยนทดแทนทันที Google จะส่ง SUBSCRIPTION_PURCHASED RTDN พร้อมโทเค็นการซื้อใหม่ตัวอย่างเพย์โหลด RTDN
    {
      "version":"1.0",
      "packageName":"com.google.play.billing.samples.subscriptions",
      "eventTimeMillis":"...",
      "subscriptionNotification":
      {
        "version":"1.0",
        "notificationType":4, // SUBSCRIPTION_PURCHASED
        "purchaseToken":"premium_purchase_token_123" //purchase token for the new subscription
      }
    }
    
  • เมื่อเซิร์ฟเวอร์ได้รับโทเค็นการซื้อใหม่จาก RTDN ให้เรียกใช้ API purchases.subscriptionsV2 ด้วยโทเค็นการซื้อใหม่เพื่อดึงข้อมูลรายละเอียดการซื้อ การตอบกลับของ API มีฟิลด์ linkedPurchaseToken ซึ่งใช้เพื่อพิจารณาว่าโทเค็นการซื้อหมายถึงการสมัครใช้บริการใหม่หรือการแทนที่การสมัครใช้บริการ
  • ในกรณีของการแทนที่การสมัครใช้บริการ linkedPurchaseToken หมายถึงโทเค็นการซื้อของการสมัครใช้บริการเดิม ในสถานการณ์นี้จะเป็น basic_purchase_token_123.Sample GET purchases.subscriptionsV2 response
    curl \
    'https://androidpublisher.googleapis.com/androidpublisher/v3/applications/<application_id>/purchases/subscriptionsv2/tokens/premium_purchase_token_123' \
    --header 'Authorization: Bearer [YOUR_ACCESS_TOKEN]' \
    --header 'Accept: application/json'
    
    {
      "kind": "androidpublisher#subscriptionPurchaseV2",
      "startTime": "...",
      "subscriptionState": "SUBSCRIPTION_STATE_ACTIVE",
      "latestOrderId": "GPA.<order_id>",
      "linkedPurchaseToken": "basic_purchase_token_123", // The purchase token of the subscription that was replaced (Basic Plan in this case)
      "acknowledgementState": "ACKNOWLEDGEMENT_STATE_ACKNOWLEDGED",
      "lineItems": [
        {
          "productId": "premium_plan", // productID of the new subscription (Premium Plan in this case)
          "expiryTime": "....",
          "autoRenewingPlan": {...},
          "offerDetails": {
            "basePlanId": "monthly-auto-renewing" // base plan ID of the new subscription
          },
          "itemReplacement": { // Details about the subscription replacement
            "productId": "subscription_basic", // productID of the old subscription (Basic Plan in this case)
            "replacementMode": "CHARGE_PRORATED_PRICE", // Replacement strategy used for this subscription change
            "basePlanId": "monthly-auto-renewing" // base plan ID of the old subscription 
          },
          "offerPhase": {...}
        }
      ],
      "etag": "<etag_value>"
    }
    
  • คุณต้องรับทราบการซื้อ Premium ใหม่ ซึ่งทำได้ทั้งภายในแอปหรือในแบ็กเอนด์ หากไม่รับทราบการซื้อภายใน 3 วัน ระบบจะคืนเงินและเพิกถอนสิทธิ์ ดูรายละเอียดเพิ่มเติมเกี่ยวกับการประมวลผลและการรับทราบการซื้อได้ในเอกสารประกอบสําหรับนักพัฒนาซอฟต์แวร์

บทสรุป

ส่วนนี้ครอบคลุมขั้นตอนการจัดการการแทนที่การสมัครใช้บริการทันทีทั้งในฝั่งไคลเอ็นต์และแบ็กเอนด์ของคุณ คุณทราบว่า Google Play จะถือว่าแพ็กเกจใหม่เป็นการซื้อใหม่ทั้งหมดและออกโทเค็นการซื้อใหม่ ในไคลเอ็นต์ คุณต้องประมวลผลการซื้อใหม่นี้โดยใช้ PurchasesUpdatedListener และอัปเดตสิทธิ์ตามการตอบกลับจาก queryPurchasesAsync ในแบ็กเอนด์ คุณควรฟัง SUBSCRIPTION_PURCHASED RTDN สำหรับโทเค็นใหม่ ใช้ API purchases.subscriptionsv2 เพื่อระบุ linkedPurchaseToken ของการสมัครใช้บริการเก่า และเพิกถอนสิทธิ์เข้าถึงที่เชื่อมโยงกับโทเค็นเก่าทันทีขณะให้สิทธิ์ใหม่ โปรดอย่าลืมรับทราบการซื้อใหม่เสมอ

11. ประมวลผลการเปลี่ยนทดแทนที่เลื่อนออกไป

ReplacementMode.DEFERRED จะเลื่อนการเปลี่ยนแปลงการสมัครใช้บริการและการอัปเดตสิทธิ์จนกว่าจะสิ้นสุดรอบการเรียกเก็บเงินปัจจุบัน ซึ่งแตกต่างจากโหมดการแทนที่ทันที การจัดการการเปลี่ยนทดแทนที่เลื่อนออกไปต้องใช้ตรรกะที่เฉพาะเจาะจงเพื่อให้มั่นใจว่าผู้ใช้จะได้รับสิทธิ์ที่ถูกต้องในเวลาที่เหมาะสม

สถานการณ์ตัวอย่าง

  • ผู้ใช้มีแพ็กเกจรายเดือนพื้นฐาน (product_id basic_plan และ purchase_token basic_purchase_token_123) ซึ่งจะต่ออายุในวันที่ 15 เมษายน
  • ในวันที่ 1 เมษายน ผู้ใช้ตัดสินใจเปลี่ยนไปใช้แพ็กเกจ Premium โดยใช้ ReplacementMode.DEFERRED
  • Google จะสร้างโทเค็นการซื้อใหม่สำหรับแพ็กเกจ Premium (product_id premium_plan และ purchase_token premium_purchase_123) ทันที แต่จะกำหนดเวลาเรียกเก็บเงินจากผู้ใช้และสิทธิ์เป็นวันที่ 15 เมษายน

ดำเนินการเปลี่ยนทดแทนที่เลื่อนออกไป

1. ทันทีหลังจากขั้นตอนการซื้อสำเร็จ (แอป)

  • PurchasesUpdatedListener จะเรียกใช้หลังจากขั้นตอนการซื้อเสร็จสมบูรณ์ แอปจะได้รับออบเจ็กต์ Purchase ที่มีโทเค็นการซื้อใหม่ premium_purchase_token_123 แต่ product_id จะยังคงอ้างอิงถึง basic_plan เดิมเนื่องจากผู้ใช้มีสิทธิ์ใช้แพ็กเกจ Basic เท่านั้น คุณต้องถือว่าการดำเนินการนี้เป็นการซื้อใหม่และรับทราบโทเค็น
    @Override
    public void onPurchasesUpdated(BillingResult billingResult, List<Purchase> purchases) {
        if (billingResult.getResponseCode() == BillingResponseCode.OK && purchases != null) {
            for (Purchase purchase : purchases) {
                // purchase.getPurchaseToken() = premium_purchase_token_123
                // purchase.getProducts() will contain basic_plan
                // Verify and acknowledge the purchase
                handleNewPurchase(purchase);
            }
        }
    }
    
  • queryPurchasesAsync จะแสดงผลการซื้อพร้อมโทเค็นการซื้อใหม่ (premium_purchase_token_123) ทันที รวมถึงสิทธิ์เดิม (basic_plan) ที่เชื่อมโยงกับการซื้อนั้น คุณสามารถใช้การตั้งค่านี้เพื่อมอบสิทธิ์ในแพ็กเกจพื้นฐานให้แก่ผู้ใช้ต่อไปได้

2. ทันทีหลังจากขั้นตอนการซื้อสำเร็จ (แบ็กเอนด์)

  • ระบบจะส่ง RTDN SUBSCRIPTION_PURCHASED ทันทีหลังจากขั้นตอนการซื้อสำหรับโทเค็นการซื้อใหม่ (premium_purchase_token_123) ตัวอย่างเพย์โหลด RTDN
    {
      "version":"1.0",
      "packageName":"com.google.play.billing.samples.subscriptions",
      "eventTimeMillis":"...",
      "subscriptionNotification":
      {
        "version":"1.0",
        "notificationType":4, // SUBSCRIPTION_PURCHASED
        "purchaseToken":"premium_purchase_token_123" //purchase token for the new subscription
      }
    }
    
  • เรียกใช้ GET purchases.subscriptionsv2 ด้วยโทเค็นการซื้อใหม่เพื่อดึงรายละเอียดการซื้อ การตอบกลับมีรายการโฆษณา 2 รายการ
    • รายการหนึ่งแสดงถึงการสมัครใช้บริการเดิม (แพ็กเกจพื้นฐาน) และมีexpiryTimeในอนาคต ระบบจะไม่ต่ออายุการสมัครใช้บริการเดิมและมีdeferredItemReplacementที่มีการสมัครใช้บริการใหม่ (แพ็กเกจ Premium) ซึ่งแสดงถึงการแทนที่สิทธิ์เดิมที่รอดำเนินการเมื่อสิทธิ์หมดอายุ
    • เส้นหนึ่งแสดงถึงการสมัครใช้บริการที่เพิ่งซื้อใหม่ ไม่ได้ตั้งค่าสำหรับ "expiryTime"
    ตัวอย่างการตอบกลับจาก API
    {
      "kind": "androidpublisher#subscriptionPurchaseV2",
      "startTime": "2026-05-07T15:50:11.383Z",
      "subscriptionState": "SUBSCRIPTION_STATE_ACTIVE",
      "latestOrderId": "GPA.<order_id>",
      "linkedPurchaseToken": "basic_purchase_token_123",
      "acknowledgementState": "ACKNOWLEDGEMENT_STATE_ACKNOWLEDGED",
      "lineItems": [
        {
          "productId": "premium_plan", // Premium Plan has no expiry time
          "autoRenewingPlan": {...},
          "offerDetails": {...},
          "itemReplacement": {. // Subscription replacement details
            "productId": "basic_plan",
            "replacementMode": "DEFERRED",
            "basePlanId": "monthly-auto-renewing"
          },
          "offerPhase": {}
        },
        {
          "productId": "basic_plan", // Subscription to be replaced
          "expiryTime": "2026-05-07T15:54:34.768Z", // Expiry time in the future
          "autoRenewingPlan": {},
          "offerDetails": {...},
          "deferredItemReplacement": { // identifier indicating this subscription will be replaced upon renewal
            "productId": "subscription_premium"
          },
          "latestSuccessfulOrderId": "GPA.<order_id>",
          "itemReplacement": {...},
        }
      ],
      "etag": "<etag>"
    }
    
  • คุณต้องรับทราบโทเค็นการซื้อใหม่ ซึ่งทำได้ทั้งภายในแอปหรือในแบ็กเอนด์ ดูรายละเอียดเพิ่มเติมเกี่ยวกับการประมวลผลและการรับทราบการซื้อได้ในเอกสารประกอบสําหรับนักพัฒนาซอฟต์แวร์
  • SUBSCRIPTION_EXPIRED ระบบจะส่ง RTDN สำหรับโทเค็นการซื้อเก่า (basic_purchase_token_123) ตัวอย่างเพย์โหลด RTDN
    {
      "version":"1.0",
      "packageName":"com.google.play.billing.samples.subscriptions",
      "eventTimeMillis":"...",
      "subscriptionNotification":
      {
        "version":"1.0",
        "notificationType":13, // SUBSCRIPTION_EXPIRED
        "purchaseToken":"basic_purchase_token_123" //purchase token for the old subscription
      }
    }
    
  • เมื่อเรียกใช้ API GET purchases.subscriptionsv2 ด้วยโทเค็นการซื้อเก่า ระบบจะแสดงว่าโทเค็นหมดอายุแล้ว (SUBSCRIPTION_STATE_EXPIRED) สิทธิ์ของแพ็กเกจเก่าจะโอนไปยังการซื้อใหม่สำหรับเวลาที่เหลือ

3. ในวันที่เปลี่ยนแทน - การต่ออายุครั้งแรกหลังจากขั้นตอนการซื้อ (แอป)

  • queryPurchasesAsync จะคืนค่าการซื้อพร้อมโทเค็นการซื้อใหม่ (premium_purchase_token_123) และการสมัครใช้บริการใหม่ที่เชื่อมโยงกับโทเค็นดังกล่าว (premium_plan)
  • ระบบควรประมวลผลการซื้อใหม่แล้วเมื่อขั้นตอนการซื้อสำเร็จ คุณไม่จำเป็นต้องดำเนินการใดๆ เป็นพิเศษนอกเหนือจากการตรวจสอบว่าผู้ใช้ได้รับสิทธิ์เข้าถึงการสมัครใช้บริการที่ถูกต้อง

4. ในวันที่เปลี่ยนแทน - การต่ออายุครั้งแรกหลังจากขั้นตอนการซื้อ (แบ็กเอนด์)

  • เมื่อใช้ ReplacementMode.DEFERRED การต่ออายุครั้งแรกจะเป็นไปตามลักษณะการทำงานมาตรฐานของการต่ออายุอื่นๆ ที่ประมวลผล SUBSCRIPTION_RENEWED RTDN คุณไม่จำเป็นต้องมีตรรกะพิเศษสำหรับการแทนที่เมื่อเกิดกรณีนี้
  • เรียกใช้ GET purchases.subscriptionsv2 ด้วยโทเค็นการซื้อใหม่เพื่อดึงรายละเอียดการซื้อ การตอบกลับมีรายการโฆษณา 2 รายการ
    • รายการหนึ่งแสดงถึงการสมัครใช้บริการเดิม (แพ็กเกจพื้นฐาน) และมีexpiryTimeในอดีต การสมัครใช้บริการเดิมจะไม่มีค่าที่ตั้งไว้สำหรับช่อง deferredItemReplacement อีกต่อไป
    • รายการหนึ่งแสดงการสมัครใช้บริการใหม่ที่มี expiryTime ในอนาคต และตั้งค่าช่อง autoRenewEnabled เป็น true
    ตัวอย่างการตอบกลับจาก API
    {
      "kind": "androidpublisher#subscriptionPurchaseV2",
      "subscriptionState": "SUBSCRIPTION_STATE_ACTIVE",
      "latestOrderId": "GPA.<order_id>..0",
      "linkedPurchaseToken": "basic_purchase_token_123", // purchase token of the old subscription
      "acknowledgementState": "ACKNOWLEDGEMENT_STATE_ACKNOWLEDGED",
      "lineItems": [
        {
          "productId": "premium_plan", // New subscription
          "expiryTime": "2026-05-07T16:00:09.437Z", // Expiry time set in the future
          "autoRenewingPlan": {
            "autoRenewEnabled": true, // Auto Renewing Flag set to True
            "recurringPrice": {...}
          },
          "offerDetails": {...},
          "latestSuccessfulOrderId": "GPA.<order_id>..0",
          "itemReplacement": {. // Details of the subscription replacement
            "productId": "basic_plan",
            "replacementMode": "DEFERRED",
            "basePlanId": "monthly-auto-renewing"
          },
          "offerPhase": {...}
        },
        {
          "productId": "basic_plan", // Old subscription, Does not contains the deferredItemReplacement field
          "expiryTime": "2026-05-07T15:54:34.768Z", // Expiry time set in the past
          "autoRenewingPlan": {},
          "offerDetails": {...},
          "latestSuccessfulOrderId": "GPA.<order_id>..0",
          "itemReplacement": {...},
        }
      ],
      "etag": "<etag>"
    }
    

บทสรุป

ส่วนนี้อธิบายการจัดการที่ไม่ซ้ำกันซึ่งจำเป็นสำหรับ ReplacementMode.DEFERRED คุณทราบว่าการเปลี่ยนแปลงสิทธิ์จะเกิดขึ้นเมื่อสิ้นสุดรอบการเรียกเก็บเงินปัจจุบันเท่านั้น ซึ่งต่างจากโหมดทันที ส่วนนี้ครอบคลุมขั้นตอนที่จำเป็นทั้งสำหรับแอปและแบ็กเอนด์ในการประมวลผลการซื้อครั้งแรกอย่างถูกต้อง รับทราบโทเค็นใหม่ และจัดการการเปลี่ยนสิทธิ์เมื่อการสมัครใช้บริการเก่าหมดอายุและการสมัครใช้บริการใหม่เริ่มใช้งาน

12. สนามเด็กเล่นสำหรับการเปลี่ยนการสมัครใช้บริการ

ฟีเจอร์สนามเด็กเล่นทดแทนในแอปตัวอย่างช่วยให้คุณทดสอบการอัปเกรดและดาวน์เกรดการสมัครใช้บริการสำหรับผลิตภัณฑ์ที่ต้องสมัครใช้บริการซึ่งกำหนดค่าไว้ในบัญชี Google Play Console ได้ ส่วนนี้จะอธิบายวิธีใช้ฟีเจอร์สนามเด็กเล่นการแทนที่

ตั้งค่า

หากต้องการใช้ฟีเจอร์ Playground ทดแทน โปรดตรวจสอบสิ่งต่อไปนี้

สนามเด็กเล่นสำหรับการเปลี่ยนการสมัครใช้บริการ

แอปตัวอย่างมีแท็บสนามเด็กเล่นการแทนที่ ซึ่งช่วยให้คุณจำลองการเปลี่ยนแปลงการสมัครใช้บริการได้ คุณสามารถค้นหาข้อมูลการสมัครใช้บริการที่กำหนดไว้ใน Play Console และทดสอบการเปลี่ยนการสมัครใช้บริการโดยใช้โหมดการแทนที่ต่างๆ สนามเด็กเล่นนี้จะช่วยให้คุณเข้าใจว่าโหมดต่างๆ ส่งผลต่อรอบการเรียกเก็บเงินและสิทธิ์ในการสมัครใช้บริการอย่างไร เพื่อให้คุณพิจารณาได้ว่าตัวเลือกใดเหมาะกับความต้องการทางธุรกิจมากที่สุด

หากต้องการจำลองการแทนที่โดยใช้ Playground ให้ทำตามขั้นตอนต่อไปนี้

  1. ไปที่แท็บสนามเด็กเล่น
  2. หากมีการสมัครใช้บริการที่ใช้งานอยู่ ระบบจะไฮไลต์การสมัครใช้บริการนั้น นี่คือการสมัครใช้บริการที่จะถูกแทนที่

หน้าแรกของ Playground

  1. หากไม่มีการสมัครใช้บริการที่ใช้งานอยู่ คุณต้องซื้อการสมัครใช้บริการก่อน
    • Playground จะแสดงแพ็กเกจ Basic, Premium และ Lite ที่สร้างขึ้นสำหรับ Codelab นี้โดยค่าเริ่มต้น
    • หากต้องการทดสอบกับแพ็กเกจอื่นๆ ที่กำหนดค่าไว้ใน Play Developer Console ให้คลิกเพิ่มแพ็กเกจที่กำหนดเอง แล้วค้นหาตาม productId และ basePlanId
    • ซื้อการสมัครใช้บริการที่เลือก
    • ตอนนี้การสมัครใช้บริการที่ใช้งานอยู่ซึ่งผู้ใช้เพิ่งซื้อควรจะแสดงแล้ว เพิ่มการสมัครใช้บริการที่กำหนดเอง
  2. เลือกการสมัครใช้บริการเป้าหมายที่ผู้ใช้ต้องการเปลี่ยนไปใช้
  3. เลือกโหมดการแทนที่สำหรับการเปลี่ยน

เลือกโหมดการแทนที่

  1. คลิกปุ่มทดสอบการแทนที่เพื่อจำลองการแทนที่การสมัครใช้บริการ
  2. คุณควรเห็น Bottom Sheet ของ Google Play Billing พร้อมรายละเอียดที่คำนวณแล้วของการเปลี่ยนแพ็กเกจการสมัครใช้บริการ (เช่น การเรียกเก็บเงินทันทีและการปรับรอบการเรียกเก็บเงิน)

รถเข็นสำหรับการเรียกเก็บเงินในการเปลี่ยนการสมัครใช้บริการ

  1. ทำธุรกรรมให้เสร็จสมบูรณ์
  2. ไปที่หน้าจัดการการสมัครใช้บริการในแอป Play Store เพื่อดูการเปลี่ยนแปลงการสมัครใช้บริการที่ใช้งานอยู่พร้อมรายละเอียดเกี่ยวกับวันที่ต่ออายุและราคาที่อัปเดตแล้ว

การจัดการการสมัครใช้บริการใน Play Store

13. ขั้นตอนถัดไป

เอกสารอ้างอิง

14. ขอแสดงความยินดี

ยินดีด้วย คุณได้ติดตั้งใช้งานการเปลี่ยนแทนการสมัครรับข้อมูลเรียบร้อยแล้วด้วยโหมดการปรับสัดส่วนต่างๆ และกำหนดค่าการจัดการแบ็กเอนด์สำหรับการเปลี่ยนแพ็กเกจ

สิ่งที่คุณได้เรียนรู้

  • วิธีกำหนดค่า SubscriptionProductReplacementParams ด้วยโหมดการแทนที่ที่เฉพาะเจาะจง
  • ความแตกต่างระหว่างการอัปเกรดทันทีกับการดาวน์เกรดที่เลื่อนออกไป
  • วิธีเลิกใช้งานโทเค็นการสมัครใช้บริการเก่าโดยใช้ linkedPurchaseToken โดยใช้ RTDN

แบบสำรวจ

ความคิดเห็นของคุณเกี่ยวกับ Codelab นี้มีค่าอย่างยิ่ง โปรดสละเวลาสักครู่เพื่อตอบแบบสำรวจของเรา