nlutterman
nlutterman

Reputation: 85

Status 21002 While Verifying Transaction Receipt

I tried looking through all of the other questions and nothing seemed to fix this issue. I have narrowed it down so the response from https://sandbox.itunes.apple.com/verifyReceipt is simply {"status":21002}. Any help would be greatly appreciated, I've copied the related code below.

NSString *completeString = @"http://www.mysite.com/verify.php";

NSURL *urlForValidation = [NSURL URLWithString:completeString];

NSMutableURLRequest *validationRequest = [[NSMutableURLRequest alloc] initWithURL:urlForValidation];

[validationRequest setHTTPMethod:@"POST"];

NSString *strTest = [NSString stringWithFormat:@"receipt=%@", [self createEncodedString:transaction.transactionReceipt]];

[validationRequest setHTTPBody:[NSData dataFromBase64String:strTest]];

NSData *responseData = [NSURLConnection sendSynchronousRequest:validationRequest returningResponse:nil error:nil];

NSString *response = [[NSString alloc] initWithData: responseData encoding: NSUTF8StringEncoding];

NSLog(@"%@", response);

- (NSString*) createEncodedString:(NSData*)data {
    static char table[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";

    const int size = ((data.length + 2)/3)*4;
    uint8_t output[size];

    const uint8_t* input = (const uint8_t*)[data bytes];
    for (int i = 0; i < data.length; i += 3)
    {
        int value = 0;
        for (int j = i; j < (i + 3); j++)
        {
            value <<= 8;
            if (j < data.length)
                value |= (0xFF & input[j]);
        }

        const int index = (i / 3) * 4;
        output[index + 0] =  table[(value >> 18) & 0x3F];
        output[index + 1] =  table[(value >> 12) & 0x3F];
        output[index + 2] = (i + 1) < data.length ? table[(value >> 6)  & 0x3F] : '=';
        output[index + 3] = (i + 2) < data.length ? table[(value >> 0)  & 0x3F] : '=';
    }

    return  [[NSString alloc] initWithBytes:output length:size encoding:NSASCIIStringEncoding];
}

Finally this is the PHP code used:

$url = 'https://sandbox.itunes.apple.com/verifyReceipt';

$receipt = "{%s}" % $_POST["receipt"];

$purchase_encoded = base64_encode( $receipt );

$encodedData = json_encode( Array( 
    'receipt-data' => $purchase_encoded 
) );

//Open a Connection using POST method, as it is required to use POST method.
$ch = curl_init();
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $encodedData);
$encodedResponse = curl_exec($ch);
curl_close($ch);


//Decode response data using json_decode method to get an object.

echo $encodedResponse;

$response = json_decode( $encodedResponse );

echo $response;

Upvotes: 2

Views: 7336

Answers (2)

Offek
Offek

Reputation: 371

nlutterman's answer is correct! It helped me so much. In case anyone is trying to get his solution to work, it would only work if you use the following method to encode the receipt data to base64 (in-app).

- (NSString*)base64forData:(NSData*)theData {
const uint8_t* input = (const uint8_t*)[theData bytes];
NSInteger length = [theData length];

static char table[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";

NSMutableData* data = [NSMutableData dataWithLength:((length + 2) / 3) * 4];
uint8_t* output = (uint8_t*)data.mutableBytes;

NSInteger i;
for (i=0; i < length; i += 3) {
    NSInteger value = 0;
    NSInteger j;
    for (j = i; j < (i + 3); j++) {
        value <<= 8;

        if (j < length) {
            value |= (0xFF & input[j]);
        }
    }

    NSInteger theIndex = (i / 3) * 4;
    output[theIndex + 0] =                    table[(value >> 18) & 0x3F];
    output[theIndex + 1] =                    table[(value >> 12) & 0x3F];
    output[theIndex + 2] = (i + 1) < length ? table[(value >> 6)  & 0x3F] : '=';
    output[theIndex + 3] = (i + 2) < length ? table[(value >> 0)  & 0x3F] : '=';
}

return [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding]; }

The normal base64EncodedStringWithOptions: wouldn't work.

Upvotes: 0

nlutterman
nlutterman

Reputation: 85

After a lot of fiddling I was able to figure it out. I believe the biggest error was with how I was setting the HTTP body. Finalized code is below, hopefully this can help someone else out!

Objective-C:

NSString *completeString = @"http://www.mysite.com/verify.php";

NSURL *urlForValidation = [NSURL URLWithString:completeString];

NSMutableURLRequest *validationRequest = [[NSMutableURLRequest alloc] initWithURL:urlForValidation];

[validationRequest setHTTPMethod:@"POST"];

NSString *strTest = [NSString stringWithFormat:@"receipt=%@", [self base64forData:transaction.transactionReceipt]];

[validationRequest setHTTPBody:[strTest dataUsingEncoding:NSUTF8StringEncoding]];

NSData *responseData = [NSURLConnection sendSynchronousRequest:validationRequest returningResponse:nil error:nil];

NSString *response = [[NSString alloc] initWithData: responseData encoding: NSUTF8StringEncoding];

NSLog(@"%@", response);

PHP:

$url = 'https://sandbox.itunes.apple.com/verifyReceipt';

$encodedData = json_encode( Array( 
    'receipt-data' => $_POST["receipt"] 
) );


//Open a Connection using POST method, as it is required to use POST method.
$ch = curl_init();
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $encodedData);
$encodedResponse = curl_exec($ch);
curl_close($ch);

$response = json_decode( $encodedResponse );

/*
echo "Status Code:";

echo $response->{'status'};
*/

if ($response->{'status'} != 0) {
    echo "FAIL";
} else {
    echo "SUCCESS";
}

Upvotes: 3

Related Questions