iOS/iPhone/iPad/watchOS/tvOS/MacOSX/Android プログラミング, Objective-C, Cocoa, Swiftなど
アプリケーションは購入情報を得ても、外部のサーバが購入情報によって何らかの対応を行う場合は、それが正しい物である事を確認できないといけない。ということで、Storeレシートの確認に挑戦する。
外部サーバとの通信が必要ない場合。例えば、購入されると、アプリケーション内の機能制限フラグを落として、隠し機能を有効にする場合は、前回までの方法で対応できる。サーバから、購入に対応するデータを取得して、アプリケーションで利用できるようにする場合、外部のサーバへの通知が本当に正しいのか気にする必要があるようだ。そこで、購入が成功するとStoreKitからレシートを渡されるので、されを外部サーバに渡し、外部サーバがSoteにレシートの内容が正しい事を確認するという仕組みのようだ。
サンプルでは、購入成功を受けたメソッドで、簡易的にアプリケーションからStoreにレシートの内容を確認している。
まず初めに、サンプル・コードはBASE64にエンコードするコードだ。簡易なもので、テストも不十分なのであしからず。
#define BASE64PAD @"="
static const char base64Alphabet[64] = {
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
'w', 'x', 'y', 'z', '0', '1', '2', '3',
'4', '5', '6', '7', '8', '9', '+', '/'
};
- (NSString *)stringEncodedWithBase64:(NSData *)data
{
if (! data) return nil;
NSUInteger dataLen = data.length;
unsigned char *dataBytes = (unsigned char *)[data bytes];
NSMutableString *str = [[NSMutableString alloc] init];
NSUInteger dataIndex = 0;
while (dataIndex < dataLen) {
char d[3] = {0, 0, 0};
d[0] = dataBytes[dataIndex];
if ((dataIndex + 1) < dataLen)
d[1] = dataBytes[dataIndex + 1];
if ((dataIndex + 2) < dataLen)
d[2] = dataBytes[dataIndex + 2];
NSUInteger bit6 = 0;
char s[5];
bit6 = (d[0] >> 2) & 0x3F;
s[0] = base64Alphabet[bit6];
bit6 = ((d[1] >> 4) & 0x0F) | ((d[0] << 4) & 0x3F);
s[1] = base64Alphabet[bit6];
bit6 = ((d[2] >> 6) & 0x03) | ((d[1] << 2) & 0x3F);
s[2] = base64Alphabet[bit6];
bit6 = d[2] & 0x3F;
s[3] = base64Alphabet[bit6];
s[4] = '\0';
[str appendString:[NSString stringWithCString:s encoding:NSASCIIStringEncoding]];
dataIndex += 3;
}
if (dataIndex < dataLen) {
NSRange aRange = NSMakeRange(dataLen + 2, (dataIndex - dataLen));
[str replaceCharactersInRange:(NSRange)aRange withString:BASE64PAD];
}
NSUInteger padNum = [str length] % 4;
for (NSUInteger i = 0; i < padNum; i++) {
[str appendString:BASE64PAD];
}
return str;
}
自分自身が分かりやすい事を優先したコードなので、ちょっと、恥ずかしい。
次はレシートを確認するコードだ。
- (void)completeTransaction:(SKPaymentTransaction *)transaction
{
/* NSURL *url = [NSURL URLWithString:@"https://buy.itunes.apple.com/verifyReceipt"]; */
NSURL *url = [NSURL URLWithString:@"https://sandbox.itunes.apple.com/verifyReceipt"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
[request setHTTPMethod:@"POST"];
NSString *json = [NSString stringWithFormat:@"{\"receipt-data\" :\"%@\"}",
[self stringEncodedWithBase64:transaction.transactionReceipt]];
[request setHTTPBody:[json dataUsingEncoding:NSUTF8StringEncoding]];
NSURLResponse *response;
NSError *error;
NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
NSLog(@"%@", [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
}
通信先のURLは"https://buy.itunes.apple.com/verifyReceipt"が正しいのだが、テストなのでサンドボックス環境の"https://sandbox.itunes.apple.com/verifyReceipt"を設定している。
transaction.transactionReceiptがレシートで、これを外部のサーバに私て、外部サーバが上記のようなコードを例えば、PythonやRuby、PHPで記述して、レシートを確認するという事だ。
以下が結果。
{
"receipt":{
...
"status":0}
正しいということか。