Entitlements

रिफंड के बाद एक्सेस वापस लेना ऐसा काम है जो न तो Apple करता है, न Google

पैसे का ट्रांसफर होना और एक्सेस खत्म होना, दोनों स्टोर्स पर अलग-अलग घटनाएं हैं, और इनमें से सिर्फ एक ही ऑटोमैटिक रूप से होती है। Google में entitlement हटाना एक ऐसे ऑप्शन के पीछे है जो डिफ़ॉल्ट रूप से ऑफ रहता है; Apple उस consumable को छू भी नहीं सकता जो पहले ही खर्च हो चुका है। आपका सर्वर जो भी मिस करता है, वह उस रेवेन्यू पर चलता रहता है जो अब आपके पास नहीं है।

5 min read
रिफंड के बाद एक्सेस वापस लेना ऐसा काम है जो न तो Apple करता है, न Google

पैसे का ट्रांसफर होना और एक्सेस खत्म होना, दोनों प्लेटफॉर्म्स पर अलग-अलग घटनाएं हैं, और इनमें से सिर्फ एक ही ऑटोमैटिक रूप से होती है। Google में entitlement हटाना एक ऐसे ऑप्शन के पीछे छिपा है जो डिफ़ॉल्ट रूप से ऑफ रहता है। Apple लाइसेंस को हैंडल करता है लेकिन किसी ऐसे consumable को नहीं छू सकता जिसे कोई पहले ही खर्च कर चुका है। आपका सर्वर जो भी साफ करने में चूक जाता है, वह उस रेवेन्यू पर चलता रहता है जो अब आपके पास नहीं है।

मुख्य बिंदु

  • Play ऑर्डर को रिफंड करने से कस्टमर का एक्सेस तब तक प्रभावित नहीं होता जब तक आप खुद कुछ न कहें। या तो Play Console के अंदर "Remove entitlement" को टिक किया जाए, या आपके API कॉल में revoke=true भेजा जाए। इनमें से कोई भी अपने आप नहीं होता।

  • वह revoke ऑप्शन इस्तेमाल किए बिना जारी किए गए रिफंड, Voided Purchases API में पूरी तरह से गायब रहते हैं। सिर्फ इस फीड पर बनाई गई कोई भी revocation जॉब इन सभी को चुपचाप नजरअंदाज कर देगी।

  • non-consumables और subscriptions के लिए लाइसेंस ऐसी चीज है जिसे Apple आपके लिए वापस ले सकता है। Consumables को खर्च होते ही डिलीवर माना जाता है, जिससे StoreKit के पास वापस लेने के लिए कुछ नहीं बचता, इसलिए बैलेंस घटाना आपके अपने सर्वर की जिम्मेदारी बन जाता है।

  • Apple कभी-कभी अपने ही रिफंड को रिवर्स कर देता है। एक REFUND_REVERSED नोटिफिकेशन बताता है कि दिया गया रिफंड वापस ले लिया गया है, revocationDate फिर से null हो जाती है, और जो भी आपने हटाया था, उसे वापस लगाना जरूरी है।

  • Google के voidedReason में नौ अलग-अलग वैल्यूज़ होती हैं, जिनमें remorse और accidental_purchase से लेकर fraud, friendly_fraud, और unacknowledged_purchase तक शामिल हैं, यानी एक ही फीड आपको एक्सेस काटने का निर्देश भी देती है और यह भी बताती है कि कौन से मामले abuse जैसे लगते हैं।

  • जब तक रिफंड की गई खरीदारी का एक्सेस बना रहता है, तब तक आप उसके compute, storage, और API calls को उस रेवेन्यू से फंड कर रहे होते हैं जो पहले ही खरीदार को वापस जा चुका है।

डिफ़ॉल्ट रूप से रिफंड के बाद भी एक्सेस क्यों बना रहता है

दोनों कंपनियां पेमेंट और entitlement को अलग-अलग ऑपरेशन के रूप में देखती हैं। पेमेंट स्टोर की पॉलिसी के मुताबिक चलता है, और अप्रूव होने के बाद रिफंड सेटल हो जाता है और लेजर में यह दिखने लगता है। entitlement एक अलग मामला है, कभी ऑप्शनल, कभी आपको खुद हैंडल करना पड़ता है, और अक्सर प्लेटफॉर्म और प्रोडक्ट टाइप के हिसाब से दोनों ही बातें लागू होती हैं। कोई भी स्टोर यह नहीं मानता कि पैसे वापस करने का मतलब एक्सेस खत्म करना है, क्योंकि बहुत सी सही-सही स्थितियां, जैसे पार्शियल रिफंड, गुडविल क्रेडिट, या सपोर्ट की तरफ से कोई सद्भावना वाला कदम, का मकसद यही होता है कि कस्टमर के पास जो उसने खरीदा है, वह बना रहे।

Google Play: वह टिक बॉक्स जिस पर किसी का ध्यान नहीं जाता

Play Console रिफंड को दो फैसलों में बांटता है। पहला, अमाउंट; और फिर, पूरी तरह अलग से, यह कि "Remove entitlement" को टिक किया जाए या नहीं। ज्यादातर सपोर्ट एजेंट इसे ऐसे ही छोड़ देते हैं, जो समझ में आता है क्योंकि ज्यादातर रिफंड रिक्वेस्ट वास्तव में गुडविल के तौर पर होती हैं और कस्टमर को सामान अपने पास रखना होता है। कन्फर्मेशन स्क्रीन जो कभी नहीं बताती, वह यह है कि यही बिना छुआ हुआ बॉक्स इस बात की वजह बनता है कि कोई व्यक्ति उस रिफंड के बाद भी सब्सक्रिप्शन या इन-ऐप आइटम अपने पास रखे रहता है, जिस रिफंड को आप पूरी तरह खत्म करने वाला मानते थे।

API में भी वही जाल मौजूद है

कोड में भी बिल्कुल वैसा ही फोर्क मौजूद है। orders.refund एंडपॉइंट में एक ऑप्शनल revoke क्वेरी पैरामीटर होता है, और इसे true सेट करने से आइटम का एक्सेस तुरंत खत्म हो जाता है, और साथ ही अगर कोई रिकरिंग सब्सक्रिप्शन शामिल है तो आगे की सभी पेमेंट्स भी कैंसल हो जाती हैं। पैरामीटर को छोड़ देने पर आपको false मिलता है: रिफंड प्रोसेस हो जाता है, खरीदारी का रिकॉर्ड बदल जाता है, और कस्टमर पहले जैसा ही बना रहता है।

  • Refund in Play Console with "Remove entitlement" left unticked, और एक्सेस बना रहता है। ऑर्डर भी कभी Voided Purchases API तक नहीं पहुंचता।

  • Refund in Play Console with "Remove entitlement" ticked, और एक्सेस तुरंत खत्म हो जाता है। यह ऑर्डर Voided Purchases API में दिखाई भी देता है।

  • Call orders.refund and leave the revoke parameter off, जिससे यह डिफ़ॉल्ट रूप से false हो जाता है, और एक्सेस बना रहता है। फिर यहां भी, कुछ भी Voided Purchases API तक नहीं पहुंचता।

  • Call orders.refund with revoke=true, और एक्सेस तुरंत खत्म हो जाता है, और यह ऑर्डर Voided Purchases API में भी दिखाई देता है।

सिर्फ Voided Purchases API पढ़ने वाली कोई revocation जॉब, बिना revoke फ्लैग के जारी किए गए रिफंड से कभी नहीं टकराएगी। यह रिकॉर्ड देर से नहीं आता। यह बस कभी नहीं आता, और वह ऑर्डर उस फीड के बाहर ही रहता है, जैसे रिफंड हुआ ही न हो।

वह आखिरी वाली पंक्ति ही इंटीग्रेशन को तोड़ती है। बिना Play Console को मैनुअली देखे एक revocation पाइपलाइन बनाना ठीक वही है जिसके लिए Voided Purchases API बना है, लेकिन इसका दायरा सिर्फ उन ऑर्डरों तक है जो वास्तव में revocation के साथ voided हुए हैं। टिक बॉक्स के बिना, या revoke=true के बिना प्रोसेस किए गए रिफंड, किसी खराबी की वजह से नहीं बल्कि डिज़ाइन की वजह से वहां नहीं दिखते।

voidedReason को समझना

Voided purchases के साथ एक voidedReason आता है, जो नौ संभावित वैल्यूज़ में से कारण बताता है: other, remorse, not_received, defective, accidental_purchase, fraud, friendly_fraud, chargeback, और unacknowledged_purchase। इसे सिर्फ रिकॉर्ड-कीपिंग मानकर इसका इस्तेमाल न करना इसे बेकार कर देता है। remorse और accidental_purchase आम, बिना किसी खास बात के रिफंड बताते हैं। fraud, friendly_fraud, और chargeback को ऑटोमेटिक revoke-and-close के बजाय एक फ्रॉड रिव्यू क्यू में भेजा जाना चाहिए, क्योंकि इन वैल्यूज़ को जनरेट करने वाले अकाउंट्स सांख्यिकीय रूप से ज्यादा बार दोहराते हैं।

जो भी इस फीड के ऊपर सीधे बिल्ड कर रहा है, उसे एक ऑपरेशनल नंबर जरूर पता होना चाहिए: purchases.voidedpurchases.list रोजाना 6,000 क्वेरी और किसी भी 30-सेकंड अवधि में 30 क्वेरी की अनुमति देता है। टाइट पोलिंग लूप्स इस सीमा को जल्दी ही खत्म कर देते हैं। pagination tokens का इस्तेमाल करते हुए शेड्यूल्ड पुल्स ही इसका मूल डिज़ाइन है।

Apple: लाइसेंस वापस लिए जा सकते हैं, consumables नहीं

App Store Server Notifications V2, रिफंड सेटल होते ही तुरंत एक REFUND नोटिफिकेशन भेजता है, जो consumables, non-consumables, auto-renewable subscriptions, और non-renewing subscriptions को बिना किसी फर्क के कवर करता है। इसके बाद जो होना चाहिए, वह प्रोडक्ट टाइप के हिसाब से पूरी तरह अलग होता है, क्योंकि इन चार में से सिर्फ दो ही StoreKit के अपने revocation मैकेनिज्म के दायरे में आते हैं।

Consumables इससे बाहर क्यों रहते हैं

स्टैंडिंग entitlements non-consumables और subscriptions से जुड़ी होती हैं, और Apple के सिस्टम इन्हें बंद कर सकते हैं। Consumables के साथ ऐसा कुछ नहीं होता। किसी एक को खर्च करना उसे डिलीवर मार्क कर देता है, जिससे StoreKit के पास वापस लेने के लिए कुछ नहीं बचता। जिस कस्टमर ने 500 coins खरीदे, उन्हें खर्च किया, और फिर रिफंड हासिल किया, उसे Apple की नजर में पहले ही सब कुछ मिल चुका है। आपका लेजर ही एकमात्र ऐसी जगह है जहां वे coins अभी भी मौजूद हैं। वह REFUND नोटिफिकेशन आपके लिए एकमात्र संकेत है कि आप मैचिंग ओरिजिनल ट्रांजैक्शन ID ढूंढें और बैलेंस खुद घटाएं, क्योंकि Apple की तरफ से ऐसा कुछ नहीं होने वाला।

  • REFUND का मतलब है कि कोई consumable, non-consumable, auto-renewable, या non-renewing खरीदारी रिफंड हो गई है। non-consumable और subscription का एक्सेस तुरंत वापस लें, और किसी भी consumable बैलेंस को खुद घटाएं।

  • REFUND_DECLINED का मतलब है कि Apple ने कस्टमर की रिफंड रिक्वेस्ट ठुकरा दी है। करने के लिए कुछ नहीं है, क्योंकि एक्सेस कभी खतरे में था ही नहीं।

  • REFUND_REVERSED का मतलब है कि Apple द्वारा पहले दिया गया रिफंड वापस ले लिया गया है। ओरिजिनल REFUND नोटिफिकेशन की वजह से आपने जो भी हटाया था, उसे फिर से बहाल करें।

  • CONSUMPTION_REQUEST का मतलब है कि फैसला लेने से पहले Apple आपसे सबूत चाहता है। 12 घंटे के भीतर जवाब दें, और ध्यान दें कि यह REFUND के बाद नहीं बल्कि उससे पहले आता है।

वह नोटिफिकेशन जिसे लगभग सभी नजरअंदाज करते हैं

REFUND_REVERSED वह है जिसे इंटीग्रेशन अक्सर छोड़ देते हैं, क्योंकि यह Apple द्वारा अपने ही फैसले को पलटने को दर्शाता है, जिसके लिए शुरुआत में शायद ही कोई डिज़ाइन करता है। इसके आने पर revocationDate फिर से null हो जाती है, यानी Apple अब दोबारा उस खरीदारी को पूरी तरह वैलिड मानता है। ओरिजिनल REFUND पर आपके सर्वर ने जो भी किया था, जैसे कोई entitlement डिलीट करना, सब्सक्रिप्शन रिकॉर्ड कैंसल करना, या consumable बैलेंस को ज़ीरो करना, उसे पलटना जरूरी है।

यह मत सोचिए कि रिवर्स किया गया रिफंड एक नई खरीदारी जैसा दिखेगा, क्योंकि ऐसा नहीं होगा। लगाए गए बैन, वापस लिया गया एक्सेस, खर्च किए गए मार्क किए गए बैलेंस: REFUND_REVERSED ठीक इन्हीं चीजों को पलटने का निर्देश है, और बाद में आपको याद दिलाने के लिए कोई फॉलो-अप नोटिफिकेशन नहीं आएगा।

इस गैप को चरण दर चरण बंद करना

  • हर Play रिफंड रूट में, Console और API दोनों पर, revoke=true को डिफ़ॉल्ट बनाएं, और दूसरे विकल्प को सिर्फ उन सपोर्ट केसों के लिए रखें जिनमें कस्टमर का एक्सेस बनाए रखना वाजिब हो।

  • purchases.voidedpurchases.list को टाइट लूप में चलाने के बजाय pagination tokens का इस्तेमाल करते हुए शेड्यूल पर पुल करें, और रोजाना 6,000 रिक्वेस्ट और 30 सेकंड में 30 रिक्वेस्ट से नीचे ही रहें।

  • fraud, friendly_fraud, और chargeback वाली voidedReason वैल्यूज़ को ऑटोमेटिक revoke-and-close हैंडलिंग के बजाय एक रिव्यू क्यू में भेजें।

  • Apple पर App Store Server Notifications V2 को सब्सक्राइब करें, और जैसे ही कोई REFUND नोटिफिकेशन इन्हें पहचानता है, non-consumable और subscription एक्सेस तुरंत वापस लें।

  • हर consumable खरीदारी के साथ ओरिजिनल ट्रांजैक्शन ID को सेव करके रखें, ताकि REFUND नोटिफिकेशन सही बैलेंस को ढूंढ सके जिसे घटाना है, और किसी को इसे मैनुअली ढूंढना न पड़े।

  • REFUND_REVERSED को जान-बूझकर इम्प्लीमेंट करें, और हर revocation को उसके ट्रांजैक्शन या ऑर्डर ID के साथ रिकॉर्ड करें, ताकि कोई रिवर्सल ठीक वही बहाल करे जो हटाया गया था।

यह गैप असल में क्या कीमत चुकाता है

यहां मुद्दा कस्टमर के साथ न्याय का नहीं है। मुद्दा यह है कि रेवेन्यू जाने के बाद भी क्या चलता रहता है। कोई रिफंड हो चुका सब्सक्रिप्शन जो अभी भी आपके API को हिट कर रहा है, डेटा होल्ड कर रहा है, compute खर्च कर रहा है, उसमें कोई नैतिक दुविधा नहीं है; इसमें एक इनवॉइस है। इंफ्रास्ट्रक्चर का पैसा उस खरीदारी से चुकाया गया था जिसका रेवेन्यू आप पहले ही वापस कर चुके हैं। कोई भी स्टोर आपकी तरफ से इस खर्च को ट्रैक नहीं करता, क्योंकि उनके नजरिए से रिफंड उस पल खत्म हो गया जब पैसा मूव हुआ। एक्सेस को कंट्रोल करना पूरी तरह से आपका ही खर्च है, जिसे आपको मैनेज करना है।

इसलिए Refund Sensor revocation को रिफंड पाइपलाइन के एक हिस्से के रूप में ट्रीट करता है, न कि बाद में जोड़ी गई किसी चीज के रूप में। एक REFUND इवेंट और उसका संबंधित एक्सेस चेंज एक ही एक्शन के रूप में लिखे जाते हैं, और एक REFUND_REVERSED इवेंट अपने आप पहले की स्थिति को बहाल कर देता है, बजाय इसके कि कोई यह नोटिस करने का इंतजार करे कि किसी कस्टमर को उस चीज से लॉक आउट किया गया है जो Apple ने दिनों पहले ही वापस कर दी थी।

यह नियम कहां डॉक्यूमेंटेड हैं

अक्सर पूछे जाने वाले प्रश्न

नहीं, ऐसा नहीं होता। एक्सेस सिर्फ तभी खत्म होता है जब Play Console में "Remove entitlement" को टिक किया गया हो, या orders.refund कॉल में revoke=true भेजा गया हो। चूंकि इनमें से कोई भी डिफ़ॉल्ट नहीं है, रिफंड अक्सर प्रोसेस हो जाते हैं जबकि कस्टमर प्रोडक्ट का इस्तेमाल करते रहते हैं।

एक REFUND नोटिफिकेशन आता है, लेकिन StoreKit किसी consumable को रिवोक नहीं कर सकता क्योंकि उसे खर्च करना ही डिलीवरी माना जाता है। ओरिजिनल ट्रांजैक्शन को ढूंढना और बैलेंस घटाना आपके अपने सर्वर का काम है।

इसका मतलब है कि Apple द्वारा पहले दिया गया रिफंड अब वापस ले लिया गया है। revocationDate फिर से null हो जाती है और खरीदारी एक बार फिर वैलिड मानी जाती है। ओरिजिनल REFUND की वजह से आपने जो भी एक्सेस या बैलेंस हटाया था, उसे वापस बहाल करना जरूरी है, और बहुत सारे इंटीग्रेशन इस नोटिफिकेशन को सब्सक्राइब ही नहीं करते।

क्योंकि वह एंडपॉइंट सिर्फ वही ऑर्डर लौटाता है जो revocation के साथ voided हुए हों। बिना revoke ऑप्शन के प्रोसेस किए गए रिफंड इसमें शामिल नहीं होते, इसलिए सिर्फ इसी फीड पर निर्भर कोई भी पाइपलाइन उन्हें पूरी तरह नजरअंदाज कर देगी।

रोजाना छह हज़ार क्वेरी, और किसी भी 30-सेकंड विंडो में 30 से ज्यादा नहीं। यह एंडपॉइंट लगातार पोलिंग के बजाय pagination tokens के साथ शेड्यूल्ड पुल के लिए डिज़ाइन किया गया है।

#refunds#entitlements#voided purchases#refund_reversed#revoke access#play billing
Refund SensorRefund Sensor TeamRefund defense for App Store and Google Play developers