Subscription Offer generate signature with PHP

is I am trying to do this https://developer.apple.com/documentation/storekit/in-app_purchase/generating_a_signature_for_subscription_offers in PHP.


I am wondering if I did the signature generation properly. It's really specific to PHP but maybe someone can help:


class Key_offer {

  var $appBundleId;
  var $keyIdentifier;
  var $productIdentifier;
  var $offerIdentifier;
  var $applicationUsername;
  var $nonce;
  var $timestamp;

  function __construct() {
  // Setting Data
  $this->appBundleId = 'bundle_id';
  $this->keyIdentifier = 'XXXXXXXXXX';
  $this->productIdentifier = $_POST["productIdentifier"];
  $this->offerIdentifier = $_POST["offerIdentifier"];
  $this->applicationUsername = $_POST["usernameHash"];
  $this->nonce = strtolower( $this->gen_uuid() );
  $this->timestamp = time() * 1000;
  }

  function rsa_sign($policy, $private_key_filename) {
  $signature = "";
  // load the private key
  $fp = fopen($private_key_filename, "r");
  $priv_key = fread($fp, 8192);
  fclose($fp);
  $pkeyid = openssl_get_privatekey($priv_key);
  // compute signature
  openssl_sign($policy, $signature, $pkeyid);
  // free the key from memory
  openssl_free_key($pkeyid);
  return $signature;
  }

  function gen_uuid() {
  return sprintf('%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
  // 32 bits for "time_low"
  mt_rand( 0, 0xffff ), mt_rand( 0, 0xffff ),
  // 16 bits for "time_mid"
  mt_rand( 0, 0xffff ),
  // 16 bits for "time_hi_and_version",
  // four most significant bits holds version number 4
  mt_rand( 0, 0x0fff ) | 0x4000,
  // 16 bits, 8 bits for "clk_seq_hi_res",
  // 8 bits for "clk_seq_low",
  // two most significant bits holds zero and one for variant DCE1.1
  mt_rand( 0, 0x3fff ) | 0x8000,
  // 48 bits for "node"
  mt_rand( 0, 0xffff ), mt_rand( 0, 0xffff ), mt_rand( 0, 0xffff )
  );
  }

  function get() {
  $text = utf8_encode($this->appBundleId.'\u2063'.$this->keyIdentifier.'\u2063'.$this->productIdentifier.'\u2063'.$this->offerIdentifier.'\u2063'.$this->applicationUsername.'\u2063'.$this->nonce.'\u2063'.$this->timestamp);

  $signature0 = $this->rsa_sign($text, "key.pem");
  $signature = hash('sha256', $signature0);
  $array = array(
  'lowUUid' => $this->nonce,
  'timeStump' => $this->timestamp,
  'identifier' => $this->offerIdentifier,
  'keyid' => $this->keyIdentifier,
  'signature' => base64_encode($signature)
  );

  return json_encode($array);
  }


}

$obj = new Key_offer();
echo $obj->get();

?>




And in the app (Swift) preparing Offer =>


public func prepareOffer(usernameHash: String, productIdentifier: String, offerIdentifier: String, completion: @escaping (SKPaymentDiscount?) -> Void) {
        let config = URLSessionConfiguration.default


        let session = URLSession.init(configuration: config) // configuration session
        let url = URL(string: "http://192.168.2.100/Key_Offer/genera_class.php")!
        var request = URLRequest(url: url)
        request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
        request.httpMethod = "POST"
        let postString = "usernameHash=\(usernameHash)&productIdentifier=\(productIdentifier)&offerIdentifier=\(offerIdentifier)"
        request.httpBody = postString.data(using: .utf8)
        let task = session.dataTask(with: request) { (data, response, error) in
            guard let data = data, error == nil else {                                                 // check for fundamental networking error
                print("Send POST error= ", error ?? 1)
                DispatchQueue.main.async {
                    completion(nil)
                }
                return
            }
            if let httpStatus = response as? HTTPURLResponse, httpStatus.statusCode != 200 {           // check for http errors
                print("statusCode should be 200, but is \(httpStatus.statusCode)")
                print("Response = \(String(describing: response))")
                DispatchQueue.main.async {
                    completion(nil)
                }
            }
            // DA DATA TO JSON CONVERSION
            let decoder = JSONDecoder.init()
            do {
                let post_response = try decoder.decode(SkProd.self, from: data)
                print(post_response)
                DispatchQueue.main.async {
                    let lowUUid = UUID.init(uuidString: post_response.lowUUid)!
                    let timeStump = NSNumber.init(value: post_response.timeStump)
                    let identifier = post_response.identifier
                    let keyID = post_response.keyid
                    let signature = post_response.signature
                    let paymentDiscount = SKPaymentDiscount(identifier: identifier, keyIdentifier: keyID, nonce: lowUUid, signature: signature, timestamp: timeStump) // genera la firma per poter creare l'offerta
                    // completed with Success, return a SKPaymentDiscount
                    completion(paymentDiscount)
                }


            } catch let error {
                print("Errore nella conversione del json API Login", error)
                DispatchQueue.main.async {
                    completion(nil)
                }
            }
        }
        task.resume()
    }


and this is the Purchase function to purchase the offer =>


publicfunc buyProduct(product: SKProduct, usernameHash: String, discountOffer: SKProductDiscount) {
let payment = SKMutablePayment.init(product: product)
        payment.applicationUsername = usernameHash 
// Add the offer to the payment.
        print(usernameHash)
        print(product.productIdentifier)
        print(discountOffer.identifier!)

// REQUEST signature from Server
self.prepareOffer(usernameHash:usernameHash, productIdentifier: product.productIdentifier, offerIdentifier: discountOffer.identifier!) { (prodottoDiscount) in
if(prodottoDiscount != nil) {
               // set PaymentDiscount Product
                payment.paymentDiscount = prodottoDiscount!
// Add the payment to the queue for purchase.
                SKPaymentQueue.default().add(payment)
            }
        }

}


And this is the mistake I get, I hope someone can help me find the error.

https://www.tuunes.org/screen.png

Replies

Were you able to figure that one out?

where can I find offer identifier in my setup ?