At SpeakingPal we offer our apps for free, and provide customers ways to extend content availability via in-app purchase. In our case, in-app purchase is build from two parts: the first, is the client's integration with the device's apps services (AppStore in iOS, Market in Android), the second is the server-side verification of the in-app receipt, avoiding trickery of the client's app.
Client-side code samples are in abundance, and usually are very well documented (well, of course, the OS vendor wants to get paid, no?). But the other side, the server-side, is not well documented and lack samples, although considered a best-practice.
Android
Android dev-site suggests making the in-app verification at the server-side as safer, but does not provide any code sample.
Google method of in-app verification is via digital signatures verification: You provide a nonce, they give you a receipt with that nonce prefixed and a RSA-SHA1 signature on that text. The key used for signing is defined in your Market developer console. With that public key, the signed receipt and the signature, you can verify that the receipt is real, that is, produced by the Market service.
iOS
Apple went with something else; verifying the AppStore receipt is done by asking iTunes' servers whether a receipt is real: after in-app is done, device's AppStore service gives you a base64 encoded receipt, which you'll send to iTunes' servers, and get a json response about the validity of the receipt. Here's a snippet:
Client-side code samples are in abundance, and usually are very well documented (well, of course, the OS vendor wants to get paid, no?). But the other side, the server-side, is not well documented and lack samples, although considered a best-practice.
Android
Android dev-site suggests making the in-app verification at the server-side as safer, but does not provide any code sample.
Google method of in-app verification is via digital signatures verification: You provide a nonce, they give you a receipt with that nonce prefixed and a RSA-SHA1 signature on that text. The key used for signing is defined in your Market developer console. With that public key, the signed receipt and the signature, you can verify that the receipt is real, that is, produced by the Market service.
Verifying an RSA-SHA1 signature is simple in PHP, but tricky. Here is a working code:
function verify_market_in_app($signed_data, $signature, $public_key_base64)
{
$key = "-----BEGIN PUBLIC KEY-----\n".
chunk_split($public_key_base64, 64,"\n").
'-----END PUBLIC KEY-----';
//using PHP to create an RSA key
$key = openssl_get_publickey($key);
//$signature should be in binary format, but it comes as BASE64.
//So, I'll convert it.
$signature = base64_decode($signature);
//using PHP's native support to verify the signature
$result = openssl_verify(
$signed_data,
$signature,
$key);
if (0 === $result)
{
return false;
}
else if (1 !== $result)
{
return false;
}
else
{
return true;
}
}
iOS
Apple went with something else; verifying the AppStore receipt is done by asking iTunes' servers whether a receipt is real: after in-app is done, device's AppStore service gives you a base64 encoded receipt, which you'll send to iTunes' servers, and get a json response about the validity of the receipt. Here's a snippet:
function verify_app_store_in_app($receipt, $is_sandbox)
{
//$sandbox should be TRUE if you want to test against itunes sandbox servers
if ($is_sandbox)
$verify_host = "ssl://sandbox.itunes.apple.com";
else
$verify_host = "ssl://buy.itunes.apple.com";
$json='{"receipt-data" : "'.$receipt.' }';
//opening socket to itunes
$fp = fsockopen ($verify_host, 443, $errno, $errstr, 30);
if (!$fp)
{
// HTTP ERROR
return false;
}
else
{
//iTune's request url is /verifyReceipt
$header = "POST /verifyReceipt HTTP/1.0\r\n";
$header .= "Content-Type: application/x-www-form-urlencoded\r\n";
$header .= "Content-Length: " . strlen($json) . "\r\n\r\n";
fputs ($fp, $header . $json);
$res = '';
while (!feof($fp))
{
$step_res = fgets ($fp, 1024);
$res = $res . $step_res;
}
fclose ($fp);
//taking the JSON response
$json_source = substr($res, stripos($res, "\r\n\r\n{") + 4);
//decoding
$app_store_response_map = json_decode($json_source);
$app_store_response_status = $app_store_response_map->{'status'};
if ($app_store_response_status == 0)//eithr OK or expired and needs to synch
{
//here are some fields from the json, btw.
$json_receipt = $app_store_response_map->{'receipt'};
$transaction_id = $json_receipt->{'transaction_id'};
$original_transaction_id = $json_receipt->{'original_transaction_id'};
$json_latest_receipt = $app_store_response_map->{'latest_receipt_info'};
return true;
}
else
{
return false;
}
}
}