I'm implementing receipt validation server side for In-App Purchases, as described by the Receipt Validation Programming guide.
Whenever I try to encode to Base64 the receipt and send it to my server, I get an error from Apple. But if I manually copy-paste the non-encoded base64 on the server, encode it there and trigger a REST call to Apple, their iTunes server responds correctly.
I'm not sure what am I doing wrong client side. The code is very straightforward:
-(NSString*) retrieveReceiptFromCompletedTransaction:(SKPaymentTransaction*)transaction
{
NSData *receiptData;
NSString *receiptString;
NSBundle *bundle = [NSBundle mainBundle];
if ([bundle respondsToSelector:@selector(appStoreReceiptURL)]) {
NSURL *receiptURL = [bundle performSelector:@selector(appStoreReceiptURL)];
receiptData= [NSData dataWithContentsOfURL:receiptURL];
}
else {
receiptData = transaction.transactionReceipt;
}
receiptString = [receiptData base64EncodedStringWithOptions:0];
return receiptString;
}
Any ideas?
I am writing down complete approach to help the beginner too.
Generate shared key from your iTunes account.
-(void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions
SKPaymentTransactionStatePurchased
{
.....
case SKPaymentTransactionStatePurchased:
if ([transaction.payment.productIdentifier
isEqualToString:PRICEPLAN1]) {
//[server PurchasedClinicalToDoList];
[self completeTransaction: transaction];
}
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
break;
}
5: VALIDATE RECEIPT
- (void) completeTransaction: (SKPaymentTransaction *)transaction
{
// NSString* receiptString = [[NSString alloc] initWithString:transaction.payment.productIdentifier];
NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL];
NSData *receipt = [NSData dataWithContentsOfURL:receiptURL];
NSString *jsonObjectString = [receipt base64EncodedStringWithOptions:0];
[self validateViaOwnServer:jsonObjectString];
[self validateDirectly: receipt];
}
6.ValidateViaServerSideCodes:
-(void)validateViaOwnServer :(NSString*)receipt
{
NSString* BASEURl = @"XXXXXXXXX/XXX/XXXX";
NSString *TestReceipt= [BASEURl stringByAppendingString:@"/InApp/iOS/updatePurchase"];
NSDictionary *params = @ {@"receipt" :receipt]};
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
manager.responseSerializer.acceptableContentTypes = [NSSet setWithObject:@"text/html"];
manager.responseSerializer = [AFHTTPResponseSerializer serializer];
[manager TestReceipt
parameters:params
success:^(AFHTTPRequestOperation *operation, id responseData) {
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
}];
}
7.VALIDATE DIRECTLY:
-(void) validateDirectly :(NSData*)receipt
{
// Sent to the server by the device
// Create the JSON object that describes the request
NSError *error;
// NSData *requestData = [NSJSONSerialization dataWithJSONObject:requestContents options:0 error:&error];
NSData *requestData = [NSJSONSerialization dataWithJSONObject:[NSDictionary dictionaryWithObjectsAndKeys:
[receipt base64EncodedStringWithOptions:0],@"receipt-data",
SHARED_SECRET,@"password",
nil]
options:NSJSONWritingPrettyPrinted
error:&error
];
if(!requestData) {
/* ... Handle error ... */ }
// Create a POST request with the receipt data.
NSURL *storeURL = [NSURL URLWithString:@"https://buy.itunes.apple.com/verifyReceipt"];
//@"https://sandbox.itunes.apple.com/verifyReceipt"
NSMutableURLRequest *storeRequest = [NSMutableURLRequest requestWithURL:storeURL]; [storeRequest setHTTPMethod:@"POST"];
[storeRequest setHTTPBody:requestData];
// Make a connection to the iTunes Store on a background queue.
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[NSURLConnection sendAsynchronousRequest:storeRequest queue:queue
completionHandler:^(NSURLResponse *response, NSData *data, NSError
*connectionError) {
if (connectionError) {
/* ... Handle error ... */
// NSLog(@"CONNECTION ERROR");
} else {
NSError *error;
NSDictionary *jsonResponse = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error];
if (!jsonResponse) { // NSLog(@"NOT JSON");
/* ... Handle error ...*/ }
else{
/* ... YES VALIDATED ...*/
}/* ... Send a response back to the device ... */
}
}];
}
8: SERVER SIDE CODES (JAVA)
String URL = "https://buy.itunes.apple.com/verifyReceipt";
try {
URL url = new URL(URL);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setDoOutput(true);
conn.setRequestMethod("POST");
//conn.setRequestProperty("Content-Type", "text/plain");
conn.setRequestProperty("Accept", "application/json");
conn.setRequestProperty("Content-Type", "application/json");
String input = "{\"receipt-data\": \""+receiptData+"\", \"password\": \"69744THESECRETKEYXXX\"}";
OutputStream os = conn.getOutputStream();
os.write(input.getBytes());
os.flush();
if (conn.getResponseCode() != 200) {
throw new RuntimeException("Failed : HTTP error code : "
+ conn.getResponseCode());
}
BufferedReader br = new BufferedReader(new InputStreamReader(
(conn.getInputStream())));
String output;
System.out.println("Output from Server .... \n");
while ((output = br.readLine()) != null) {
appleJson += output;
// System.out.println(output);
}
conn.disconnect();
appleReceipt = gson.fromJson(appleJson, AppleReceipt.class);
purchaseStartDate = appleReceipt.getReceipt().getIn_app().get(1).getOriginal_purchase_date_ms();
expiryDate = appleReceipt.getReceipt().getIn_app().get(1).getExpires_date_ms();
// System.out.println("Apple receipt expiry date="+appleReceipt.getReceipt().getIn_app().get(1).getExpires_date_ms());
// System.out.println("Apple receipt expiry date="+appleReceipt.getReceipt().getIn_app().get(1).getExpires_date_ms());