Je voulais demander si quelqu'un a déjà implémenté les nouvelles offres pour l'abonnement inapp (renouvellement automatique), la difficulté de créer côté serveur le système pour créer cette signature en utilisant la clé p8 avec php si possible. J'ai trouvé cela sur la documentation Apple, je ne suis pas sûr de le comprendre: https://developer.apple.com/documentation/storekit/in-app_purchase/generating_a_signature_for_subscription_offers
5 Réponses :
Voici une procédure pas à pas de RevenueCat: Offres d'abonnement iOS
Le message contient beaucoup plus de détails, mais la génération de signature est:
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)
Ceci est juste une preuve de concept. Vous souhaiterez cela sur votre serveur et aurez peut-être une logique pour déterminer, pour un utilisateur donné, si l'offre demandée est appropriée.
Une fois que vous avez généré la signature, le nonce et l'horodatage, renvoyez-les avec le key_id
à votre application où vous pouvez créer un SKPaymentDiscount
.
Avertissement: Je travaille chez RevenueCat. Nous prenons en charge les offres d'abonnement prêtes à l'emploi avec notre SDK, aucune signature de code requise: https://www.revenuecat.com/2019/04/25/signing-ios-subscription-offers
Merci d'avoir signalé cet exemple, je l'ai déjà suivi, mais malheureusement le résultat n'est pas positif
Connaissez-vous quelque chose d'exemple avec PHP?
J'ai trouvé cet exemple en ligne mais malheureusement le résultat n'est pas positif. Exemple de didacticiel
J'essaie de créer quelque chose en php, mais cela semble peu pratique, peut-être repérer quelque chose dans l'encodage?
<?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(); ?>
Vous feriez mieux de ne pas réinventer la roue et d'utiliser les bibliothèques appropriées pour les opérations UUID et cryptographiques. Il existe une excellente bibliothèque PHP pour UUID github.com/ramsey/uuid , installée via composer car le composer require ramsey/uuid
. De plus, vous avez besoin de l'algorithme ECDSA pour la signature, et c'est mieux fait avec une bibliothèque, telle que PHPECC ou tout autre (j'ai utilisé celui-ci packagist.org/packages/sop/crypto-bridge ).
Je peux confirmer que cela fonctionne:
<?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; } }
Nous avons eu un problème côté client car l'appareil était connecté sur 2 comptes iTunes différents, un normal et un sandbox. Cela créait une erreur de signature non valide qui n'a pas fait sens. Nous déconnectons le compte régulier et utilisons simplement le compte sandbox et tout fonctionne.
Il manque la définition de la classe Signature
La classe de signature est juste un DTO sans logique.
J'avais des problèmes avec les offres d'abonnement, mais ce problème sur GitHub m'a aidé à le faire fonctionner. J'ai installé la bibliothèque Sop CryptoBridge (le composer require sop/crypto-bridge
) et cela a finalement fonctionné pour mon client d'application iOS. Voici mon code PHP de travail:
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;
Gardez à l'esprit quelques points:
"\u{2063}"
. L'utilisation de '\u2063'
ne fonctionnait pas pour moi.$nonce
est un UUID en minuscule$timestamp
est en millisecondes (ie time() * 1000
).Et cela devrait fonctionner comme un charme.
J'essaie d'installer la bibliothèque que vous avez signalée, mais j'obtiens toujours un message d'erreur / crypto-types 0.2.1 nécessite ext-gmp * -> l'extension PHP demandée gmp est absente de votre système.
Oui, la plupart des bibliothèques cryptographiques nécessitent l'extension PHP GMP (GNU Multiple Precision). Vous pouvez en installer un en suivant les étapes d'ici: stackoverflow.com/questions/40010197/...