Sending In-App Purchase receipt data to my server

dornad picture dornad · Oct 21, 2013 · Viewed 10.5k times · Source

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?

Answer

Yogesh Lolusare picture Yogesh Lolusare · Nov 18, 2014
  • THE approach implemented is for type RENEWABLE, not tried the approach for other types (May or May not be helpful for other types).
  • THE point number 5 (sending string directly to server side(JAVA) is the way i implemened to resolve this issues (similar issue faced by me)
  • I am writing down complete approach to help the beginner too.

  • Generate shared key from your iTunes account.

  • Than in your codes (ios side) use shared_secret #define SHARED_SECRET @"XXXXXX6666555XXXXXXx"
  • In

-(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());