Generating a Signature for Subscription Offers - Xcode - Swift

谁说我不能喝 提交于 2019-12-07 14:44:05

问题


I wanted to ask if someone has already implemented the new Offers for the inapp-subscription (auto renewal), the difficulty in creating server-side the system to create this signature using the p8 key with php if possible. I found this on the Apple documentation, I'm not sure understanding it: https://developer.apple.com/documentation/storekit/in-app_purchase/generating_a_signature_for_subscription_offers


回答1:


Here's a walkthrough from RevenueCat: iOS Subscription Offers

The post contains much more detail, but the signature generation is:

import json
import uuid
import time
import hashlib
import base64

from ecdsa import SigningKey
from ecdsa.util import sigencode_der

bundle_id = 'com.myapp'
key_id = 'XWSXTGQVX2'
product = 'com.myapp.product.a'
offer = 'REFERENCE_CODE' # This is the code set in ASC
application_username = 'user_name' # Should be the same you use when
                                   # making purchases
nonce = uuid.uuid4()
timestamp = int(round(time.time() * 1000))

payload = '\u2063'.join([bundle_id, 
                         key_id, 
                         product, 
                         offer, 
                         application_username, 
                         str(nonce), # Should be lower case
                         str(timestamp)])

# Read the key file
with open('cert.der', 'rb') as myfile:
  der = myfile.read()

signing_key = SigningKey.from_der(der)

signature = signing_key.sign(payload.encode('utf-8'), 
                             hashfunc=hashlib.sha256, 
                             sigencode=sigencode_der)
encoded_signature = base64.b64encode(signature)

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, if the requested offer is appropriate.

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

Disclaimer: I work at RevenueCat. We support Subscription Offers out of the box with our SDK, no code-signing required: https://www.revenuecat.com/2019/04/25/signing-ios-subscription-offers




回答2:


I used to have problems with subscription offers, but this issue on GitHub helped me to make it work. I installed Sop CryptoBridge library (composer require sop/crypto-bridge) and it finally worked for my iOS app client. Here is my working PHP code:

use Sop\CryptoBridge\Crypto;
use Sop\CryptoEncoding\PEM;
use Sop\CryptoTypes\AlgorithmIdentifier\Hash\SHA256AlgorithmIdentifier;
use Sop\CryptoTypes\AlgorithmIdentifier\Signature\SignatureAlgorithmIdentifierFactory;
use Sop\CryptoTypes\Asymmetric\PrivateKeyInfo;

// you can copy your p8 file contents here or just use file_get_contents()
$privateKeyPem = <<<'PEM'
-----BEGIN PRIVATE KEY-----
...
-----END PRIVATE KEY-----
PEM;

// load private key
$privateKeyInfo = PrivateKeyInfo::fromPEM(PEM::fromString($privateKeyPem));
// you can also load p8 file like this
// PrivateKeyInfo::fromPEM(PEM::fromFile($pathToP8));

// combine the parameters
$appBundleId = 'com.github.yaronius'; // iOS app bundle ID
$keyIdentifier = 'A1B2C3D4E5'; // Key ID from AppStore Connect
$productIdentifier = 'product_identifier';
$offerIdentifier = 'offer_identifier';
$applicationUsername = 'username'; // same as in the iOS app
$nonce = 'db6ba7a6-9ec2-4504-bcb9-c0dfbdc3d051'; // some UUID in lowercase
$timestamp = time() * 1000; // milliseconds! as stated in the docs

$dataArray = [
    $appBundleId,
    $keyIdentifier,
    $productIdentifier,
    $offerIdentifier,
    $applicationUsername,
    $nonce,
    $timestamp
];
$data = implode("\u{2063}", $dataArray);

// signature algorithm
$algo = SignatureAlgorithmIdentifierFactory::algoForAsymmetricCrypto(
    $privateKeyInfo->algorithmIdentifier(),
    new SHA256AlgorithmIdentifier()
);
// generate signature
$signature = Crypto::getDefault()->sign($data, $privateKeyInfo, $algo);

// encode as base64 encoded DER
$encodedSignature = base64_encode($signature->toDER());
// send signature to your app
echo $encodedSignature;

Keep in mind a few things:

  • as a delimiter you have to use PHP Unicode codepoint, i.e. "\u{2063}". Using '\u2063' did not work for me.
  • $nonce is lowercase UUID
  • $timestamp is in milliseconds (i.e. time() * 1000).

And it should work like a charm.




回答3:


I can confirm that this is working:

<?php
use Ramsey\Uuid\Uuid;

class ItunesSignatureGenerator {
    private $appBundleID = 'your.bundle.id';

    private $keyIdentifier = 'ZZZZZZZ';

    private $itunesPrivateKeyPath = '/path/to/the/file.p8;

    /**
     * @see https://developer.apple.com/documentation/storekit/in-app_purchase/generating_a_signature_for_subscription_offers
     *
     * @param $productIdentifier
     * @param $offerIdentifier
     *
     * @return Signature
     */
    public function generateSubscriptionOfferSignature($productIdentifier, $offerIdentifier)
    {
        $nonce = strtolower(Uuid::uuid1()->toString());
        $timestamp = time() * 1000;
        $applicationUsername = 'username';

        $message = implode(
            "\u{2063}",
            [
                $this->appBundleID,
                $this->keyIdentifier,
                $productIdentifier,
                $offerIdentifier,
                $applicationUsername,
                $nonce,
                $timestamp
            ]
        );

        $message = $this->sign($message);

        return new Signature(
            base64_encode($message),
            $nonce,
            $timestamp,
            $this->keyIdentifier
        );
    }

    private function sign($data)
    {
        $signature = '';

        openssl_sign(
            $data,
            $signature,
            openssl_get_privatekey('file://' . $this->itunesPrivateKeyPath),
            OPENSSL_ALGO_SHA256
        );

        return $signature;
    }
}

We had a issue on the client side because the device was connected on 2 different itunes account, one regular and one sandbox. It was creating a invalid signature error that didn't make sens. We disconnect the regular account and just use the sandbox account and everything is working.




回答4:


I am trying to create something in php, but it seems impractical, maybe spotting something in the encoding?

<?php

class Key_offer {

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

    function __construct() {
        // Setting Data
        $this->appBundleId = 'bundle-app-id';
        $this->keyIdentifier = '0123456789';
        $this->productIdentifier = $_POST["productIdentifier"] ?? "";
        $this->offerIdentifier = $_POST["offerIdentifier"] ?? "";
        $this->applicationUsername = $_POST["usernameHash"] ?? "";  // usare lo stesso anche nella chiamata che si effettua da Xcode
        $this->nonce = strtolower( $this->gen_uuid() ); // genera UUID formato 4;
        $this->timestamp = time(); // get timeStump
    }

    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"); // SubscriptionKey_43PF4FTV2X.p8
        $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();

?>



回答5:


I found this example online but unfortunately the result is not positive. Tutorial Example



来源:https://stackoverflow.com/questions/55414749/generating-a-signature-for-subscription-offers-xcode-swift

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!