دليل لتطبيق اشتراكات التجديد التلقائي في تطبيقات iOS

صورة


مرحبا بالجميع! اسمي دينيس ، أقوم بتطوير Apphud ، وهي خدمة لتحليل اشتراكات التجديد التلقائي في تطبيقات iOS.


في هذه المقالة ، سأخبرك عن كيفية تكوين الاشتراكات القابلة للتجديد التلقائي وتطبيقها والتحقق من صحتها في iOS 12 و iOS 13. على سبيل المكافأة ، سوف أخبركم بالنقاط الدقيقة والمخاطر التي لا يراعيها جميع المطورين.


قم بإعداد اشتراكات على متجر التطبيقات App Store


إذا كان لديك بالفعل Bundle ID وتطبيق تم إنشاؤه ، فيمكنك تخطي هذه الخطوات. إذا كنت تقوم بإنشاء تطبيق لأول مرة ، فقم بما يلي:


يجب عليك إنشاء معرف حزمة واضح (معرف التطبيق) على Apple Developer Portal . مع فتح صفحة تسمى الشهادات والمعرفات والتوصيفات ، انتقل إلى علامة تبويب المعرفات . في يونيو 2019 ، قامت Apple أخيرًا بتحديث تصميم البوابة وفقًا لـ ASC (اختصار لـ App Store Connect).


تصميم جديد لـ Apple Developer Portal في 2019
تصميم جديد لـ Apple Developer Portal في 2019



عادةً ما يتم تحديد معرف حزمة صريح في نمط المجال ( com.apphud.subscriptionstest ). في قسم القدرات ، ستلاحظ أن علامة الاختيار الموجودة بجوار عمليات الشراء في التطبيق محددة بالفعل. بعد إنشاء معرف Bundle ( معرف التطبيق ) ، انتقل إلى App Store Connect.


اختبار المستخدمين (مستخدمي Sandbox)


لاختبار عمليات الشراء المستقبلية ، ستحتاج إلى إنشاء مستخدم اختبار. للقيام بذلك ، انتقل إلى ASC في علامة التبويب المستخدمون والوصول ، ثم إلى اختبار Sandbox.


نموذج إنشاء صندوق حماية المستخدم
نموذج إنشاء صندوق حماية المستخدم


عند إنشاء اختبار ، يمكنك تحديد أي بيانات غير موجودة ، والأهم من ذلك ، لا تنسَ إذن البريد الإلكتروني وكلمة المرور!

سأتحدث عن كيفية اختبار عمليات الشراء باستخدام بيانات اعتماد الاختبار بالقرب من نهاية المقالة.


هناك خطوة أخرى مهمة وهي إعداد العقود والبيانات المصرفية في قسم " الاتفاقيات والضرائب والعمليات المصرفية ". إذا لم يكن لديك اتفاق على الطلبات المدفوعة ، فلن تكون قادرًا على اختبار اشتراكات التجديد التلقائي!


بعد ذلك ، يمكنك إنشاء تطبيق جديد في App Store Connect. حدد اسمًا فريدًا وحدد Bundle ID كمعرف الحزمة.


معرّف الحزمة هو معرف الحزمة الخاص بك
معرّف الحزمة هو معرف الحزمة الخاص بك


مباشرة بعد إنشاء التطبيق ، انتقل إلى علامة التبويب الميزات.


إذا كنت قد أنشأت التطبيق بالفعل ، فيمكنك متابعة القراءة من هنا.

تتكون عملية إنشاء اشتراك قابل للتجديد التلقائي من عدة مراحل:


1. قم بإنشاء معرف اشتراك وإنشاء مجموعة اشتراك . مجموعة الاشتراك عبارة عن مجموعة من الاشتراكات بفترات وأسعار مختلفة ، ولكنها تفتح نفس الوظيفة في التطبيق. أيضًا ، في مجموعة الاشتراك ، يمكنك فقط تنشيط الفترة التجريبية المجانية مرة واحدة ، ويمكن أن يكون واحد فقط من الاشتراكات نشطة. إذا كنت تريد أن يكون لتطبيقك اشتراكان مختلفان في نفس الوقت ، فستحتاج إلى إنشاء مجموعتين من الاشتراكات.


2. ملء بيانات الاشتراك: المدة واسم العرض في متجر التطبيقات (لا ينبغي الخلط بينه وبين الاسم فقط) والوصف. إذا قمت بإضافة الاشتراك الأول إلى المجموعة ، فستحتاج إلى الإشارة إلى اسم العرض الخاص بمجموعة الاشتراك. تذكر أن تحفظ تغييراتك في كثير من الأحيان ، فقد تجمد ASC في أي وقت وتوقف عن الاستجابة.


صفحة الاشتراك
شاشة الاشتراك


3. ملء سعر الاشتراك. هناك مرحلتان: إنشاء الأسعار والعروض الخاصة. حدد السعر الحقيقي بأي عملة ، ويتم إعادة حسابها تلقائيًا لجميع البلدان الأخرى. العروض التمهيدية: هنا يمكنك أن تقدم للمستخدمين فترة تجريبية مجانية أو خصومات مسبقة الدفع. ظهرت العروض الترويجية في متجر التطبيقات مؤخرًا في عام 2019: فهي تتيح لك تقديم خصومات خاصة للمستخدمين الذين ألغوا اشتراكهم والذين تريد العودة.


سر مشترك مفتاح الجيل


في الصفحة التي تحتوي على قائمة بجميع اشتراكاتك التي تم إنشاؤها ، سترى المفتاح المشترك لزر التطبيق . هذا خط خاص مطلوب للتحقق من صحة التحقق في تطبيق iOS. سنحتاج إلى التحقق من صحة الاختيار لتحديد حالة الاشتراك.


يمكن أن يكون المفتاح المشترك من نوعين: مفتاح فريد لتطبيقك أو مفتاح واحد لحسابك. هام: لا تقم أبدًا بإعادة إنشاء المفتاح إذا كان لديك بالفعل التطبيق في متجر التطبيقات ، وإلا فلن يتمكن المستخدمون من التحقق من صحة الاختيار وسوف يتوقف تطبيقك عن العمل كما هو متوقع.


في هذا المثال ، يتم إنشاء ثلاث مجموعات اشتراك و 3 اشتراكات سنوية.
في هذا المثال ، يتم إنشاء ثلاث مجموعات اشتراك و 3 اشتراكات سنوية.


انسخ معرّف جميع اشتراكاتك والمفتاح المشترك ، وسيصبح ذلك مفيدًا في وقت لاحق في الرمز.


جزء البرمجيات


هيا بنا إلى الجزء العملي. ما الذي يتطلبه الأمر لإنشاء مدير تسوق كامل؟ كحد أدنى ، يجب تنفيذ ما يلي:


  1. الشراء


  2. التحقق من حالة الاشتراك


  3. تحقق التحديث


  4. استرداد المعاملة (يجب عدم الخلط بينه وبين تحديث شيك!)



الشراء


يمكن تقسيم عملية الشراء بأكملها إلى مرحلتين: استلام المنتجات (فئة SKProduct ) وتهيئة عملية الشراء (فئة SKPayment ). بادئ ذي بدء ، يجب علينا تحديد مفوض بروتوكول SKPaymentTransactionObserver .


 // Starts products loading and sets transaction observer delegate @objc func startWith(arrayOfIds : Set<String>!, sharedSecret : String){ SKPaymentQueue.default().add(self) self.sharedSecret = sharedSecret self.productIds = arrayOfIds loadProducts() } private func loadProducts(){ let request = SKProductsRequest.init(productIdentifiers: productIds) request.delegate = self request.start() } public func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) { products = response.products DispatchQueue.main.async { NotificationCenter.default.post(name: IAP_PRODUCTS_DID_LOAD_NOTIFICATION, object: nil) } } func request(_ request: SKRequest, didFailWithError error: Error){ print("error: \(error.localizedDescription)") } 

IAP_PRODUCTS_DID_LOAD_NOTIFICATION إشعار IAP_PRODUCTS_DID_LOAD_NOTIFICATION لتحديث واجهة المستخدم في تطبيق ما.


بعد ذلك ، نكتب طريقة لتهيئة عملية الشراء:


 func purchaseProduct(product : SKProduct, success: @escaping SuccessBlock, failure: @escaping FailureBlock){ guard SKPaymentQueue.canMakePayments() else { return } guard SKPaymentQueue.default().transactions.last?.transactionState != .purchasing else { return } self.successBlock = success self.failureBlock = failure let payment = SKPayment(product: product) SKPaymentQueue.default().add(payment) } 

يبدو مفوض SKPaymentTransactionObserver كما يلي:


 extension IAPManager: SKPaymentTransactionObserver { public func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) { for transaction in transactions { switch (transaction.transactionState) { case .purchased: SKPaymentQueue.default().finishTransaction(transaction) notifyIsPurchased(transaction: transaction) break case .failed: SKPaymentQueue.default().finishTransaction(transaction) print("purchase error : \(transaction.error?.localizedDescription ?? "")") self.failureBlock?(transaction.error) cleanUp() break case .restored: SKPaymentQueue.default().finishTransaction(transaction) notifyIsPurchased(transaction: transaction) break case .deferred, .purchasing: break default: break } } } private func notifyIsPurchased(transaction: SKPaymentTransaction) { refreshSubscriptionsStatus(callback: { self.successBlock?() self.cleanUp() }) { (error) in // couldn't verify receipt self.failureBlock?(error) self.cleanUp() } } func cleanUp(){ self.successBlock = nil self.failureBlock = nil } } 

عند الاشتراك الناجح ، يتم استدعاء طريقة التفويض التي يتم فيها purchased .


ولكن كيف تحدد تاريخ انتهاء الاشتراك؟ للقيام بذلك ، قم بتقديم طلب منفصل إلى Apple.


التحقق من حالة الاشتراك


يتم التحقق من صحة التحقق باستخدام طلب التحقق من POST إلى Apple ، نرسل الشيك المشفر كسلسلة مشفرة base64 كمعلمة ، وفي الاستجابة نتلقى نفس الشيك بتنسيق JSON. في الصفيف ، latest_receipt_info المفتاح latest_receipt_info جميع المعاملات من كل فترة من كل اشتراك ، بما في ذلك الفترات التجريبية. يمكننا فقط تحليل الإجابة والحصول على تاريخ انتهاء الصلاحية الحالي لكل منتج.


في WWDC 2017 ، أضافوا القدرة على تلقي الشيكات الحالية فقط لكل اشتراك باستخدام مفتاح exclude-old-transactions verifyReceipt طلب verifyReceipt .

 func refreshSubscriptionsStatus(callback : @escaping SuccessBlock, failure : @escaping FailureBlock){ // save blocks for further use self.refreshSubscriptionSuccessBlock = callback self.refreshSubscriptionFailureBlock = failure guard let receiptUrl = Bundle.main.appStoreReceiptURL else { refreshReceipt() // do not call block yet return } #if DEBUG let urlString = "https://sandbox.itunes.apple.com/verifyReceipt" #else let urlString = "https://buy.itunes.apple.com/verifyReceipt" #endif let receiptData = try? Data(contentsOf: receiptUrl).base64EncodedString() let requestData = ["receipt-data" : receiptData ?? "", "password" : self.sharedSecret, "exclude-old-transactions" : true] as [String : Any] var request = URLRequest(url: URL(string: urlString)!) request.httpMethod = "POST" request.setValue("Application/json", forHTTPHeaderField: "Content-Type") let httpBody = try? JSONSerialization.data(withJSONObject: requestData, options: []) request.httpBody = httpBody URLSession.shared.dataTask(with: request) { (data, response, error) in DispatchQueue.main.async { if data != nil { if let json = try? JSONSerialization.jsonObject(with: data!, options: .allowFragments){ self.parseReceipt(json as! Dictionary<String, Any>) return } } else { print("error validating receipt: \(error?.localizedDescription ?? "")") } self.refreshSubscriptionFailureBlock?(error) self.cleanUpRefeshReceiptBlocks() } }.resume() } 

في بداية الطريقة ، يمكنك أن ترى أن هناك التحقق من وجود نسخة محلية من الشيك. قد لا يتوفر التحقق المحلي ، على سبيل المثال ، إذا تم تثبيت التطبيق عبر iTunes. إذا لم يكن هناك فحص ، فلن نتمكن من تنفيذ طلب verifyReceipt . نحتاج أولاً إلى الحصول على التحقق المحلي الحالي ، ثم محاولة التحقق من الصحة مرة أخرى. يتم إجراء عملية التحقق باستخدام فئة SKReceiptRefreshRequest :


 private func refreshReceipt(){ let request = SKReceiptRefreshRequest(receiptProperties: nil) request.delegate = self request.start() } func requestDidFinish(_ request: SKRequest) { // call refresh subscriptions method again with same blocks if request is SKReceiptRefreshRequest { refreshSubscriptionsStatus(callback: self.successBlock ?? {}, failure: self.failureBlock ?? {_ in}) } } func request(_ request: SKRequest, didFailWithError error: Error){ if request is SKReceiptRefreshRequest { self.refreshSubscriptionFailureBlock?(error) self.cleanUpRefeshReceiptBlocks() } print("error: \(error.localizedDescription)") } 

يتم تطبيق التحقق من التحديث في وظيفة refreshReceipt() . إذا تم تحديث التحقق بنجاح ، requestDidFinish(_ request : SKRequest) الأسلوب المفوض requestDidFinish(_ request : SKRequest) ، والذي يستدعي طريقة refreshSubscriptionsStatus .


كيف يتم تطبيق تحليل معلومات الشراء؟ لقد تم إرجاع كائن JSON حيث توجد مجموعة متداخلة من المعاملات (بواسطة مفتاح latest_receipt_info ). نتصفح الصفيف ، ونحصل على تاريخ انتهاء الصلاحية باستخدام مفتاح expires_date إذا لم يكن هذا التاريخ قد وصل بعد.


 private func parseReceipt(_ json : Dictionary<String, Any>) { // It's the most simple way to get latest expiration date. Consider this code as for learning purposes. Do not use current code in production apps. guard let receipts_array = json["latest_receipt_info"] as? [Dictionary<String, Any>] else { self.refreshSubscriptionFailureBlock?(nil) self.cleanUpRefeshReceiptBlocks() return } for receipt in receipts_array { let productID = receipt["product_id"] as! String let formatter = DateFormatter() formatter.dateFormat = "yyyy-MM-dd HH:mm:ss VV" if let date = formatter.date(from: receipt["expires_date"] as! String) { if date > Date() { // do not save expired date to user defaults to avoid overwriting with expired date UserDefaults.standard.set(date, forKey: productID) } } } self.refreshSubscriptionSuccessBlock?() self.cleanUpRefeshReceiptBlocks() } 

قدمت مثالًا بسيطًا عن كيفية استخراج تاريخ انتهاء الصلاحية الحالي للاشتراك. لا يوجد أي معالجة للخطأ ، على سبيل المثال ، لا يوجد التحقق من إعادة الشراء ( تتم إضافة تاريخ الإلغاء ).


لتحديد ما إذا كان الاشتراك نشطًا أم لا ، ما عليك سوى مقارنة التاريخ الحالي بالتاريخ من افتراضيات المستخدم حسب مفتاح المنتج. في حالة غيابه أو أقل من التاريخ الحالي ، يعتبر الاشتراك غير نشط.


 func expirationDateFor(_ identifier : String) -> Date?{ return UserDefaults.standard.object(forKey: identifier) as? Date } let subscriptionDate = IAPManager.shared.expirationDateFor("YOUR_PRODUCT_ID") ?? Date() let isActive = subscriptionDate > Date() 

يتم تنفيذ استرداد المعاملة في سطر واحد SKPaymentQueue.default().restoreCompletedTransactions() . تقوم هذه الوظيفة باستعادة جميع المعاملات المكتملة عن طريق استدعاء طريقة المفوض func paymentQueue(**_** queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) .


ما الفرق بين تحديث شيك من استرداد المعاملة؟


كلتا الطريقتين تساعد في استعادة بيانات الشراء الخاصة بك. ولكن ما هي خلافاتهم؟ هناك طاولة رائعة مع wwdc الفيديو :


جدول الفرق بين طريقتين لاستعادة المشتريات من WWDC
جدول الفرق بين طريقتين لاستعادة المشتريات من WWDC


في معظم الحالات ، تحتاج فقط إلى استخدام SKReceiptRefreshRequest() ، لأننا مهتمون فقط باستلام شيك للحساب اللاحق لتاريخ انتهاء الصلاحية.


في حالة الاشتراكات القابلة للتجديد التلقائي ، لا تهمنا المعاملات ذاتها ، لذلك يكفي استخدام تحديث الشيكات فقط. ومع ذلك ، هناك حالات تحتاج فيها إلى استخدام طريقة استرداد المعاملة: إذا قام تطبيقك بتنزيل المحتوى عند الشراء (محتوى مستضاف من Apple) أو إذا كنت لا تزال تدعم الإصدارات أدناه iOS 7.


اختبار التسوق (اختبار رمل)


في السابق ، لاختبار عمليات الشراء ، كان عليك تسجيل الدخول من App Store في إعدادات جهاز iPhone الخاص بك. كان هذا غير مريح للغاية (على سبيل المثال ، تم محو مكتبة Apple Music بالكامل). ومع ذلك ، لا يلزم القيام بذلك الآن: حساب صندوق الحماية موجود الآن بشكل منفصل عن الحساب الرئيسي.



تتشابه عملية الشراء مع عمليات الشراء الحقيقية في App Store ، ولكن هناك بعض النقاط:


  • ستحتاج دائمًا إلى إدخال كلمة مرور تسجيل الدخول من خلال نافذة النظام. لا تزال المشتريات التي تستخدم Touch ID / Face ID غير مدعومة.


  • إذا ، عند إدخال تسجيل الدخول وكلمة المرور بشكل صحيح ، طلب النظام مرارًا وتكرارًا كلمة مرور تسجيل الدخول ، انقر فوق "إلغاء" ، وقم بتقليل التطبيق ، ثم حاول مرة أخرى. يبدو وكأنه هراء ، لكنه يعمل بالنسبة للكثيرين. لكن في بعض الأحيان بعد إدخال كلمة المرور الثانية ، لا تزال العملية مستمرة.


  • لن تتمكن من اختبار عملية إلغاء الاشتراك بأي طريقة.


  • مدة فترات الاشتراك أقل بكثير من الحقيقي. ولا يتم تحديثها أكثر من 6 مرات في اليوم.



المدة الفعليةمدة الاختبار
أسبوع واحد3 دقائق
شهر واحد5 دقائق
شهرين10 دقائق
3 شهور15 دقيقة
6 شهور30 دقيقة
1 سنة1 ساعة

ما الجديد في StoreKit في iOS 13؟


من الفئة الجديدة - فئة SKStorefront فقط ، والتي تقدم معلومات حول البلد الذي تم تسجيل دخول المستخدم إليه في متجر التطبيقات. قد يكون ذلك مفيدًا لهؤلاء المطورين الذين يستخدمون اشتراكات مختلفة لبلدان مختلفة. في السابق ، قام الجميع بالتحقق من الموقع الجغرافي أو حسب منطقة الجهاز ، ولكن هذا لم يعط نتيجة دقيقة. الآن أصبح من السهل جدًا اكتشاف البلد في متجر التطبيقات: SKPaymentQueue.default().storefront?.countryCode . تمت إضافة مفوض الطريقة أيضًا إذا تغير البلد في متجر التطبيقات أثناء عملية الشراء. في هذه الحالة ، يمكنك متابعة أو إلغاء عملية الشراء بنفسك.


مطبات عند العمل مع الاشتراكات


  • لا توصي شركة Apple بالتحقق من التحقق مباشرةً من جهاز ما. تحدثوا عن هذا عدة مرات في WWDC (من 5:50) وهذا موضح في الوثائق . هذا غير آمن لأن أي مهاجم يمكنه اعتراض البيانات باستخدام هجوم رجل في المنتصف. الطريقة الصحيحة للتحقق من الشيكات هي التحقق من الصحة المحلي باستخدام خادمك.
  • هناك مشكلة في التحقق من تاريخ انتهاء الصلاحية. إذا كنت لا تستخدم الخادم الخاص بك ، فيمكن تغيير وقت النظام على الجهاز إلى إصدار أقدم ، ثم سيعطي رمزنا النتيجة الخاطئة - سيتم اعتبار الاشتراك نشطًا. إذا كان هذا لا يناسبك ، فيمكنك استخدام أي خدمة تصدر وقتًا عالميًا دقيقًا.
  • قد لا يكون لدى جميع المستخدمين نسخة تجريبية مجانية. يمكن للمستخدم إعادة تثبيت التطبيق بعد بعض الوقت ، وسيظهر التطبيق أن النسخة التجريبية متوفرة كالمعتاد. سيكون من الصحيح تحديث الشيك والتحقق منه والتحقق من JSON من توفر الإصدار التجريبي لهذا المستخدم. كثير لا.
  • إذا طلب المستخدم استرداد المبلغ ، فسيتم إضافة cancellation_date إلى JSON للاشتراك ، ولكن ستنتهي مدة expires_date دون تغيير. لذلك ، من المهم التحقق دائمًا من وجود حقل cancellation_date ، وهو المفضل على expires_date .
  • لا يستحق تحديث الشيك في كل مرة يتم فيها تشغيل التطبيق ، لأنه أولاً لا معنى له ، وثانيًا ، على الأرجح سيشاهد المستخدم نافذة إدخال كلمة مرور معرف Apple. يجدر تحديث الشيك ، على سبيل المثال ، عندما ينقر المستخدم نفسه على زر استعادة التسوق.
  • كيفية تحديد ما هي النقاط التي تستحق التحقق من صحة التحقق للحصول على تاريخ انتهاء الصلاحية الحالي للاشتراك؟ يمكنك التحقق من صحة التحقق في كل بداية ، أو فقط في نهاية الاشتراك. ومع ذلك ، إذا قمت بفحص الشيك فقط في نهاية الاشتراك ، فسيكون المستخدم الذي أصدر المبلغ المسترد قادراً على استخدام طلبك مجانًا حتى نهاية الفترة.

استنتاج


آمل أن تكون هذه المقالة مفيدة لك. حاولت إضافة ليس فقط الكود ، ولكن أيضًا لشرح النقاط الدقيقة في التطوير. يمكن تنزيل رمز الفصل الكامل هنا . ستكون هذه الفئة مفيدة للغاية للتعرف على المطورين المبتدئين وأولئك الذين يرغبون في معرفة المزيد حول كيفية عمل كل شيء. للتطبيقات المباشرة ، يوصى باستخدام حلول أكثر خطورة ، على سبيل المثال ، SwiftyStoreKit .


هل تريد تنفيذ اشتراكات في تطبيق iOS الخاص بك في 10 دقائق؟ دمج Apphud و:
  • إجراء عمليات شراء باستخدام طريقة واحدة فقط ؛
  • تتبع تلقائيا حالة اشتراك كل مستخدم ؛
  • دمج عروض الاشتراك بسهولة
  • إرسال أحداث الاشتراك إلى Amplitude و Mixpanel و Slack و Telegram مع مراعاة العملة المحلية للمستخدم ؛
  • خفض معدل Churn في التطبيقات وإرجاع المستخدمين غير المشتركين.

Source: https://habr.com/ru/post/ar456602/


All Articles