omz:forum

    • Register
    • Login
    • Search
    • Recent
    • Popular

    Welcome!

    This is the community forum for my apps Pythonista and Editorial.

    For individual support questions, you can also send an email. If you have a very short question or just want to say hello — I'm @olemoritz on Twitter.


    In App Purchases using Pythonista App Template - Objc_util issues

    Pythonista
    2
    6
    3405
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • mapmangame
      mapmangame last edited by ccc

      Hi

      I'm developing an app using pythonista and the pythonista app template. I've hit a snag trying to implement in app purchases using the code below (which is adapted from objective code here https://www.tutorialspoint.com/ios/ios_in_app_purchase.htm).

      The issue is that the receive_products method, which does get called-back, is returned an empty list of products. I have checked the itunes connect set up and it seems fine.

      To investigate the issue further I added some objectives code to the pythonista app template and managed to successfully return the correct list of products. This is not useful in practice as I want the user to be able to buy the products after the main.py python script has been started.

      I therefore think the issue relates to threads i.e. I that that store kit probably needs the code to run on the main thread, but I'm not sure. I've tried adding the @on_main_thread decorator, but it's not working.

      Does anyone have any suggestions? Any help really appreciated (as always)

      from objc_util import *
      import ctypes
      import time
      
      def fetchAvailableProducts(_self, _cmd):
      
          obj = ObjCInstance(_self)
          
          sk_class = ObjCClass("SKProductsRequest")
          
          InApp.Instance.products_request = sk_class.alloc().init(productIdentifiers=ns(InApp.PRODUCTS))
          InApp.Instance.products_request.delegate = obj
          InApp.Instance.products_request.start()
      
      
      def productsRequest_didReceiveResponse_(_self, _cmd, request, response):
          #defined here: https://developer.apple.com/documentation/storekit/skproductsrequestdelegate/1506070-productsrequest
          InApp.Instance.receive_products(response)
      
      
      def paymentQueue_updatedTransactions_(_self, _cmd, queue, transactions):
          InApp.Instance.update_transactions(queue, transactions)
      
      class Product:
      	
          def __init__(self, identifier, title, description, price):
              self.identifier = identifier
              self.title = title
              self.description = description
              self.price = price
      
      class InApp:
          
          PRODUCTS = ['com.YYY.ZZZ']
          
          Instance = None
          
          @classmethod
          def initialize(cls):
              cls.Instance = InApp()
              cls.Instance.fetch()
      
          @classmethod
          def initialize_dummy(cls):
              cls.Instance = InAppDummy()
          
          def log(self, message):
              self.log.append(message)
          
          def update_successfull_purchase(self, product_identifier):
              for observer in self.observers:
                  observer.purchase_successful(product_identifier)
      
          def update_failed_purchase(self, product_identifier):
              for observer in self.observers:
                  observer.purchase_failed(product_identifier)
      
          def update_restored_purchase(self, product_identifier):
              for observer in self.observers:
                  observer.purchase_restored(product_identifier)
          
          def get_valid_product(self, product_identifier):
              
              for product in self.valid_products:
                  if product.product_identifier == product_identifier:
                      return product
      
              raise Exception('Product is not valid: {0}'.format(product_identifier))
      
          def is_valid_product(self, product_identifier):
          
              for product in self.valid_products:
                  if product.product_identifier == product_identifier:
                      return True
              
              return False
          
          @on_main_thread
          def purchase(self, product_identifier):
      
              if not self.can_make_purchases:
                  raise Exception('Purchases are disabled')
              else:
                  product = self.get_valid_product(product_identifier)
                  sk_payment_class = ObjCClass("SKPayment")
                  payment = sk_payment_class.alloc().init(product=product)
                  default_queue = ObjCClass("SKPaymentQueue").defaultQueue
                  default_queue.addTransactionObserver(self.purchase_controller)
                  default_queue.addPayment(sk_payment_queue_class)
          
          @on_main_thread
          def update_transactions(self, queue, transactions):
              
              for transaction in ObjCInstance(transactions):
      
                  transaction_state = transaction.transactionState
                  
                  if transaction_state == "SKPaymentTransactionStatePurchasing":
              
                      self.log('Purchasing')
                  
                  elif transaction_state == "SKPaymentTransactionStatePurchased":
      
                      if transaction.payment.productIdentifier in InApp.PRODUCTS:
                          self.log('Purchased')
                          self.update_successfull_purchase(transaction.payment.productIdentifier)
                          default_queue = ObjCClass("SKPaymentQueue").defaultQueue
                          default_queue.finishTransaction(transaction)
                              
                  elif transaction_state == "SKPaymentTransactionStateRestored":
      
                      self.log('Restored')
                      self.update_restored_purchase(transaction.payment.productIdentifier)
                      default_queue = ObjCClass("SKPaymentQueue").defaultQueue
                      default_queue.finishTransaction(transaction)
                  
                  elif transaction_state == "SKPaymentTransactionStateFailed":
                      self.update_failed_purchase(transaction.payment.productIdentifier)
                      self.log('Failed')
          
          @on_main_thread
          def receive_products(self, response):
      
              self.products_validated = True
              self.valid_products = []
              self.invalid_products = []
              
              obj_response = ObjCInstance(response)
              valid_products = ObjCInstance(obj_response.products())
              
              self.valid_count = len(valid_products)
              
              for valid_product in valid_products:
                  print valid_product.productIdentifier
                  if (valid_product.productIdentifier in InApp.PRODUCTS):
                      
                      product = Product(valid_product.productIdentifier,
                                        valid_product.localizedTitle,
                                        valid_product.localizedDescription,
                                        valid_product.price)
                                    
                      self.valid_products.append(product)
          
              for invalid in obj_response.invalidProductIdentifiers():
                  self.invalid_products.append(invalid)
          
          def __init__(self):
              
              self.products_request = None
              
              self.observers = []
              self.log = []
              
              self.products = []
              self.products_validated = False
              
              self.valid_count = 0
              self.invalid_products = []
              
      		
          def add_observer(self, observer):
              self.observers.append(observer)
      			
          @on_main_thread
          def fetch(self):
              
              self.check_purchases_enabled()
              
              ObjCClass('NSBundle').bundleWithPath_('/System/Library/Frameworks/StoreKit.framework').load()
              
              superclass = ObjCClass("NSObject")
              
              methods = [fetchAvailableProducts,
                         productsRequest_didReceiveResponse_,
                         paymentQueue_updatedTransactions_]
                  
              protocols = ['SKProductsRequestDelegate', 'SKPaymentTransactionObserver']
              
              purchase_controller_class = create_objc_class('PurchaseController', superclass, methods=methods, protocols=protocols)
                
              self.purchase_controller = purchase_controller_class.alloc().init()
      
              self.purchase_controller.fetchAvailableProducts()
          
          @on_main_thread
          def check_purchases_enabled(self):
              sk_payment_queue_class = ObjCClass("SKPaymentQueue")
              self.can_make_purchases = sk_payment_queue_class.canMakePayments()
      1 Reply Last reply Reply Quote 0
      • mapmangame
        mapmangame last edited by

        quick update: I've been having some success shifting the purchase code into the Xcode App Template and then using objc_util to call the purchase code added to the template from within pythonista. I've managed to get the list of products back and I'm moving on to implementing purchases now.

        1 Reply Last reply Reply Quote 0
        • JonB
          JonB last edited by JonB

          was len(valid_products) retrning 0? or just nothing came out of the loop?

          1 Reply Last reply Reply Quote 0
          • JonB
            JonB last edited by

            This post is deleted!
            1 Reply Last reply Reply Quote 0
            • mapmangame
              mapmangame last edited by

              well both.

              obj_response.products() is an empty array.

              there are a number of articles out there that explain that an empty array denotes something is wrong.

              1 Reply Last reply Reply Quote 0
              • mapmangame
                mapmangame last edited by

                I have got this working by moving the code that uses storekit into the pythonista template. objc_util calls from within pythonista are used to control the code in the template.

                I think the security around storekit might be conflicting with running the store kit calls from the python interpreter.

                1 Reply Last reply Reply Quote 0
                • First post
                  Last post
                Powered by NodeBB Forums | Contributors