phpwindows-8in-app-purchasesignedxmlxmlseclibs

Verifying windows 8 purchases (receipts) using PHP


I need to verify in-app purchases made in Windows 8 applications server-side using PHP. MSDN's documentation page has an example only in C#. Right now I've spent a whole day by searching for a way to do it in PHP. No success. All over the internet there are only .NET examples on this topic.

I've found some partial information about signed XML and x509, some libraries (xmlseclibs - useless, uses openssl_* functions which do not support sha256; phpseclib - looks promising but their documentation and examples are poor with no help).

Is it possible to do it somehow without learning everything about signed XML, RSA and x509? Right now I've read almost everything but everywhere is info only about encoding. Nothing about verifying.


Solution

  • I managed to verify WP8 IAP receipt using xmlseclibs library.

    Also, you need php curl enabled.

    do {
        $doc = new DOMDocument();
    
        $xml = $_POST['receipt_data']; // your receipt xml here!
    
        // strip unwanted chars - IMPORTANT!!!
        $xml = str_replace(array("\n","\t", "\r"), "", $xml);
        //some (probably mostly WP8) receipts have unnecessary spaces instead of tabs
        $xml = preg_replace('/\s+/', " ", $xml);
        $xml = str_replace("> <", "><", $xml);
    
        $doc->loadXML($xml);
        $receipt = $doc->getElementsByTagName('Receipt')->item(0);
        $certificateId = $receipt->getAttribute('CertificateId');
    
        $ch = curl_init("https://lic.apps.microsoft.com/licensing/certificateserver/?cid=$certificateId");
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
    
        $publicKey = curl_exec($ch);
        $errno = curl_errno($ch);
        $errmsg = curl_error($ch);
        curl_close($ch);
    
        if ($errno != 0) {
            $verifyFailed = true;
            break;
        }
    
        // Verify xml signature
        require('./xmlseclibs.php');
        $objXMLSecDSig = new XMLSecurityDSig();
        $objDSig = $objXMLSecDSig->locateSignature($doc);
        if (!$objDSig) {
            $verifyFailed = true;
            break;
        }
        try {
            $objXMLSecDSig->canonicalizeSignedInfo();
            $retVal = $objXMLSecDSig->validateReference();
            if (!$retVal) {
                throw new Exception("Error Processing Request", 1);
            }
            $objKey = $objXMLSecDSig->locateKey();
            if (!$objKey) {
                throw new Exception("Error Processing Request", 1);
            }
            $key = NULL;
            $objKeyInfo = XMLSecEnc::staticLocateKeyInfo($objKey, $objDSig);
            if (! $objKeyInfo->key && empty($key)) {
                $objKey->loadKey($publicKey);
            }
            if (!$objXMLSecDSig->verify($objKey)) {
                throw new Exception("Error Processing Request", 1);
            }
        } catch (Exception $e) {
            $verifyFailed = true;
            break;
        }
    
        $productReceipt = $doc->getElementsByTagName('ProductReceipt')->item(0);
        $prodictId = $productReceipt->getAttribute('ProductId');
        $purchaseDate = $productReceipt->getAttribute('PurchaseDate');
    } while(0);
    
    if ($verifyFailed) {
        // invalid receipt
    } else {
        // valid receipt
    }