problem generating signature for subscription offers - error code 12

Hello, I'm new to apple ios developement and I'm running into a problem generating signature for subscription offers. I took the sample code from this documentation. I changed it a bit to adapt it to serverless

const express = require('express');
const crypto = require('crypto');
const ECKey = require('ec-key');
const secp256k1 = require('secp256k1');
const uuidv4 = require('uuid/v4');
const KeyEncoder = require('key-encoder').KeyEncoder;

exports.handler = (event, context, callback) => {

    const appBundleID = event.appBundleId;
    const productIdentifier = event.productIdentifier;
    const subscriptionOfferID = event.offerId;
    const applicationUsername = event.applicationUsername;
    const keyID = event.keyId;
    const keyString = event.keyString;

    /*
        The nonce is a lowercase random UUID string that ensures the payload is unique.
        The App Store checks the nonce when your app starts a transaction with SKPaymentQueue,
        to prevent replay attacks.
    */
    const nonce = uuidv4();

    /*
        Get the current time and create a UNIX epoch timestamp in milliseconds.
        The timestamp ensures the signature was generated recently. The App Store also uses this
        information help prevent replay attacks.
    */
    const currentDate = new Date();
    const timestamp = currentDate.getTime();

    /*
        Combine the parameters into the payload string to be signed. These are the same parameters you provide
        in SKPaymentDiscount.
    */
    const payload = appBundleID + '\u2063' +
        keyID + '\u2063' +
        productIdentifier + '\u2063' +
        subscriptionOfferID + '\u2063' +
        applicationUsername + '\u2063' +
        nonce + '\u2063' +
        timestamp;

    // Create an Elliptic Curve Digital Signature Algorithm (ECDSA) object using the private key.
    const key = new ECKey(keyString, 'pem');

    // Set up the cryptographic format used to sign the key with the SHA-256 hashing algorithm.
    const cryptoSign = key.createSign('SHA256');

    // Add the payload string to sign.
    cryptoSign.update(payload);

    /*
        The Node.js crypto library creates a DER-formatted binary value signature,
        and then base-64 encodes it to create the string that you will use in StoreKit.
    */
    const signature = cryptoSign.sign('base64');

    /*
        Check that the signature passes verification by using the ec-key library.
        The verification process is similar to creating the signature, except it uses 'createVerify'
        instead of 'createSign', and after updating it with the payload, it uses `verify` to pass in
        the signature and encoding, instead of `sign` to get the signature.

        This step is not required, but it's useful to check when implementing your signature code.
        This helps debug issues with signing before sending transactions to Apple.
        If verification succeeds, the next recommended testing step is attempting a purchase
        in the Sandbox environment.
    */
    const verificationResult = key.createVerify('SHA256').update(payload).verify(signature, 'base64');
    console.log("Verification result: " + verificationResult)

    // Send the response.

    callback(null, { 'key_identifier': keyID, 'nonce': nonce, 'ts': timestamp, 'base_64_signature': signature });
};

I call it from another aws lambda and i pass all the values needed including the key. For testing purposes I have also tried adding the key as a multine string directly into the code const keyString = `my multiline key string`; in line 16 and still it didn't work. To add more context around error 12


Error Domain=SKErrorDomain Code=12 "Cannot connect to iTunes Store"
 UserInfo={NSLocalizedDescription=Cannot connect to iTunes Store}


we have created new keys already to rule out the possibility of the the key file being corrupted, the check on line 70 returns true all the time.

So I'm trying to figure out if we are missing something here that I've gotten too tunnel vision to see in the code above.


we have been testing with offers created for our debug app (not production app from the app store)


I'm the backend engineer, the ios engineer has provided me the following code snippets on how they are sending the request to the storekit



// Fetch the signature from server to be applied to the offer.
    // At this point we should know the user, the product the offer is for, and which offer you want to display.
    func prepareDiscountOffer(forProduct product: SKProduct, withDiscount discountId: String, completion: @escaping (SKPaymentDiscount?) -> Void) {        // Get signature from backend, what if the user isn't signed in?
        self.fetchDiscountSignatureFromBackend(usernameHash: AWSCognitoManager.shared.userId ?? "", productIdentifier: product.productIdentifier, offerIdentifier: discountId) { result in
            if let discountOfferingSignature = result {
                // Create an SKPaymentDiscount to be used later when the user initiates the purchase
                // FIXME: convert timestamp: String to NSNumber
                guard let uuid = UUID.init(uuidString: discountOfferingSignature.nonce) else { return }
                let paymentDiscountOffer = SKPaymentDiscount(identifier: discountId,
                                                             keyIdentifier: discountOfferingSignature.keyIdentifier,
                                                             nonce: uuid,
                                                             signature: discountOfferingSignature.base64Signature,
                                                             timestamp: NSNumber(value: discountOfferingSignature.ts))
                //                                   generatedPaymentDiscounts.append(paymentDiscountOffer)
                completion(paymentDiscountOffer)
            } else {
                Analytics.track(event: .failedToGetOfferSignature, properties: ["Product Id": product.productIdentifier,
                                                                                "Offer Id": discountId])
            }
        }






// Makes a buy request with a subscription offer attached.
    public func buyDiscountedProduct(_ product: SKProduct, forUser usernameHash: String, withOffer discountOffer: SKPaymentDiscount) {        // The original product being purchased.
        let payment = SKMutablePayment(product: product)        // You must set applicationUsername to be the same as the one used to generate the signature.
        payment.applicationUsername = usernameHash        // Add the offer to the payment.
        payment.paymentDiscount = discountOffer        // Add the payment to the queue for purchase.
        SKPaymentQueue.default().add(payment)        // what happens when we fail? what's the fallback? fallback endpoint, send cisco what things need to be regenerated
        // free month, for this user
    }





Thanks in advance for your time, we are trying to figure out if the problem is the way we are generating the signature or the way we are sending it to the store kit.



Many thanks !

Replies

We are having same issue.

are you resolved? We are having same issue.