Search Unity

Implementing iOS Promotional Offers with IOS Native

Discussion in 'iOS and tvOS' started by stanislav-osipov, Mar 2, 2021.

  1. stanislav-osipov

    stanislav-osipov

    Joined:
    May 30, 2012
    Posts:
    1,790
    The "promotional offers" is a relatively new cool iOS feature available from iOS 12.2. Recently I finished a feature request from one of the IOS Native plugin users and would like to share some good articles about it and also a few code snippets on how to implement it.
    First of all, here is a couple of awesome articles I think you should read before jumping to implementing this feature in your game.

    But for now, let's just go to the short description of how to do it.

    1. Pull offer info.
    Pull the product data but it's id if you haven't got all the product info yet (normally you pull all the product info as a store initialization process.)

    Code (CSharp):
    1.  var productsRequest = new ISN_SKProductsRequest(new List<string>{"my.product.id"});
    Once a request is sent, we can print available discounts for our product.
    Code (CSharp):
    1.  
    2. productsRequest.Start(response =>
    3. {
    4.     if (response.IsSucceeded)
    5.     {
    6.         // We only interested in 1st product
    7.         // since we requesting only 1 is in this sample:
    8.         var product = response.Products[0];
    9.         foreach (var discount in product.Discounts)
    10.         {
    11.             Debug.Log($"Product has available Discount: " +
    12.                 $"{discount.LocalizedPrice}");
    13.         }
    14.     }
    15. });
    16.  
    The available discounts are presented by ISN_SKProductDiscount model.

    2. Determine Eligibility
    In order for an offer to be applied, a user either needs to have an active or lapsed subscription. + any additional eligibility check logic you would like to add based on your app business logic.

    3. Sign
    This is probably the trickiest part. We need to convert our ISN_SKProductDiscount into an ISN_SKPaymentDiscount. I will post here part of Jacob Eiting guide from Configuring iOS Subscription Offers article.

    The constructor for SKPaymentDiscount provides some clue to what we’ll need to achieve that:

    • identifier — The identifier of the subscription offer
    • keyIdentifier — The identifier of the subscription key used to sign the offer
    • nonce — A throwaway value generated along with the signature
    • signature — The signature itself
    • timestamp — The timestamp when the signature was generated.
    First, generate your subscription key, you can do this from the “Users and Access” section of App Store Connect.

    Configuring your key will trigger a one-time download of a p8 file of your private key. Don’t lose it! You will need this key every time you want any user to redeem an offer. Apple provided a signing guide for doing so but it’s light on specifics. But don’t worry! We don’t have to learn the Elliptic Curve Digital Signature Algorithm, we can use Python.

    First, install the ecdsa package for Python:

    Code (CSharp):
    1. pip install ecdsa
    Next we need to convert your p8 key file into a DER file readable by the ecdsa library. Luckily the OpenSSL command line tool can do this.

    Code (CSharp):
    1. openssl pkcs8 -nocrypt -in SubscriptionKey_XWSXTGQVX2.p8 -out cert.der -outform der
    With the converted key, the following will generate the needed signature:
    Code (CSharp):
    1. import json
    2. import uuid
    3. import time
    4. import hashlib
    5. import base64
    6.  
    7. from ecdsa import SigningKey
    8. from ecdsa.util import sigencode_der
    9.  
    10. bundle_id = 'com.myapp'
    11. key_id = 'XWSXTGQVX2'
    12. product = 'com.myapp.product.a'
    13. offer = 'REFERENCE_CODE' # This is the code set in ASC
    14. application_username = 'user_name' # Should be the same you use when
    15.                                   # making purchases
    16. nonce = uuid.uuid4()
    17. timestamp = int(round(time.time() * 1000))
    18.  
    19. payload = '\u2063'.join([bundle_id,
    20.                         key_id,
    21.                         product,
    22.                         offer,
    23.                         application_username,
    24.                         str(nonce), # Should be lower case
    25.                         str(timestamp)])
    26.  
    27. # Read the key file
    28. with open('cert.der', 'rb') as myfile:
    29.   der = myfile.read()
    30.  
    31. signing_key = SigningKey.from_der(der)
    32.  
    33. signature = signing_key.sign(payload.encode('utf-8'),
    34.                             hashfunc=hashlib.sha256,
    35.                             sigencode=sigencode_der)
    36. encoded_signature = base64.b64encode(signature)
    37.  
    38. print(str(encoded_signature, 'utf-8'), str(nonce), str(timestamp), key_id)
    This is just a proof of concept. You will want this on your server and perhaps have some logic to determine, for a given user, the requested offer is appropriate. This forces the developer to use off device control for granting offers.

    Once you’ve generated the signature, nonce and timestamp send these along with the key_id back to your app where we can create an ISN_SKPaymentDiscount.

    4. Display an offer
    The following code snippet shows how to display an offer using our ISN_SKPaymentDiscount object.
    Code (CSharp):
    1. var paymentDiscount = new ISN_SKPaymentDiscount(identifier, keyIdentifier, nonce, signature, timestamp);
    2.  
    3. var payment = new ISN_SKMutablePayment(product);
    4. payment.ApplicationUsername = applicationUsername;
    5. payment.PaymentDiscount = paymentDiscount;
    6.  
    7. ISN_SKPaymentQueue.AddPayment(payment);
    And that's it, this is how you do it :)