質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

88.81%

swift2 消耗型アプリ内課金 サーバー(PHP)側でレシート検証がうまくいかない

解決済

回答 1

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 4,584

Ryo1012

score 13

はじめまして、質問させていただきます。
現在iOS向けのアプリを制作中です。
その際に、消耗型のアプリ内課金の処理を実装する必要があり、なんとか書籍やネット上の情報を手掛かりにコードを組んでみたのですが、iOSとサーバーとのやりとりやレシートの検証のところでつまづいてしまったためどなたかご教授していただけたらと思います。

■環境
iOS側:Xcode7.3 swift2.2
サーバー側:PHP5.6.14

■問題点
1.iOS側でBase64にエンコードしたレシートをPHPにPOSTする部分は成功しているが、PHP側でiTunesConnectにJSON形式で送信すると、返ってきたステータスが21002(プロパティのデータが不正であるか、または欠落)になる。
2.ステータスが0(成功)の場合、その旨をiOS側にレスポンスしたが、その方法が分からない。

この2点のうまく方法をご教授いただきたいです。

■iOS側ソースコード(swift側)

// トランザクションの状態変更時に呼ばれる
func paymentQueue(queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {

    for trans:SKPaymentTransaction in transactions {

        // 購入処理中
        if trans.transactionState == SKPaymentTransactionState.Purchasing {
            // インジゲーター表示
        }
        // 購入成功
        else if trans.transactionState == SKPaymentTransactionState.Purchased {

            // トランザクション完了
            queue.finishTransaction(trans)

            // レシートの処理
            let receiptURL = NSBundle.mainBundle().appStoreReceiptURL
            let receiptData = NSData(contentsOfURL: receiptURL!)

            // レシートの保存
            let storage = NSUserDefaults.standardUserDefaults()
            var receipts = storage.arrayForKey("receipts")

            if (receiptData != nil) {
                if (receipts == nil) {
                    storage.setObject([receiptData!], forKey: "receipts")
                }
                else {
                    receipts!.append(receiptData!)
                    storage.setObject(receipts, forKey: "receipts")
                }
                storage.synchronize()
            }

            // 独自サーバーに未送信レシートを送信
            self.accessServer({
                (success: Bool) in
                if success {
                    // アイテム追加成功(アラート表示)
                    self.showAlert(nil, text: "アイテムが追加されました")
                }
            })
        }

        // 購入失敗(ユーザーキャンセル含む)
        else if trans.transactionState == SKPaymentTransactionState.Failed {

            // ユーザーキャンセル以外
            if trans.error!.code == SKErrorCode.PaymentCancelled.rawValue {
                // 失敗(アラート表示)
                self.showAlert(nil, text: trans.error?.description)
            }

            // トランザクション完了
            queue.finishTransaction(trans)
        }

        // その他のエラー
        else {
            // トランザクション完了
            queue.finishTransaction(trans)
        }
    }
}

// トランザクションの終了時に呼ばれる
func paymentQueue(queue: SKPaymentQueue, removedTransactions transactions: [SKPaymentTransaction]) {

}

// 独自サーバーに未送信レシートを送信
func accessServer(completion: (Bool) -> Void) {
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), {

        let storage = NSUserDefaults.standardUserDefaults()
        var receipts = storage.arrayForKey("receipts")

        var result = false
        if (receipts != nil) {

            // 独自サーバーに未送信レシートを送信
            for (var i = 0; i < receipts!.count; i+=1) {
                let receiptData = receipts![i] as! NSData

                // Base64にエンコード
                let base64 = receiptData.base64EncodedStringWithOptions(NSDataBase64EncodingOptions.Encoding64Charac   LineLength)

                // 独自サーバーに未送信レシートを送信する
                self.sendReceipts(base64)
            }
            result = true

            // 通信成功時にストレージの削除
            if (result) {
                storage.removeObjectForKey("receipts")
                storage.synchronize()
            }
        }

        // 結果を返す
        dispatch_async(dispatch_get_main_queue(), {
            completion(result)
        })
    })
}

// レシートをサーバーに送信
func sendReceipts(var receipts: String) {

    let server  = "ドメイン名" + "receipts.php"
    let request = NSMutableURLRequest(URL: NSURL(string: server)!)

    request.HTTPMethod = "POST"
    receipts           = "RECEIPTS=\(receipts)"

    // StringからNSDataにキャスト
    request.HTTPBody = receipts.dataUsingEncoding(NSUTF8StringEncoding)

    var response: NSURLResponse?

    // サーバへ送信
    do {
        let resultData = try NSURLConnection.sendSynchronousRequest(request, returningResponse: &response)
    }
    catch {
    }
}

■サーバー側ソースコード(PHP)

<?php

// レシート受け取り
$receipt = $_POST['RECEIPTS'];
$postData = json_encode(array('receipt-data' => $receipt));

// 本番環境
$response = post("https://buy.itunes.apple.com/verifyReceipt", $postData);

// 本番環境にテスト環境のレシートを問い合わせてしまった場合、テスト環境に問い合わせなおす。
// 実行時に本番かテストかを判定できないのでこのように対応する。
// http://www.aguuu.com/archives/2012/12/in-app-purchase-annotation/
if ($response->status == 21007) {
    $response = post("https://sandbox.itunes.apple.com/verifyReceipt", $postData);
}
echo $response->status;        // 21002と表示される

// 検証成功の場合
if ($response->status == 0) {
    // 決済毎に transaction_id がユニークになるのでこれを控え、
    // 同じレシートで複数回リクエストがあったら無視するように。
    $transaction_id = $response->receipt->transaction_id;

    // 検証成功の旨をレスポンス
    // ここでiOS側に成功したことをレスポンスしたい
}

function post($endpoint_url, $postData)
{
    $ch = curl_init($endpoint_url);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_POSTFIELDS, $postData);
    curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-type: application/json; charset=UTF-8')); // HTTPヘッダー

    $response = json_decode(curl_exec($ch));
    curl_close($ch);
    return $response;
}
?>

以上、宜しくお願い致します。

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 1

check解決した方法

0

解決致しました。

1.Base64にエンコードした後に不要な文字が混ざっているためそれを削除して、サーバー送信後に検証することによりうまくいきました。

2.検証がうまくいったことをPHP側でprint関数等で出力し、アプリ側の

let resultData = try NSURLConnection.sendSynchronousRequest(request, returningResponse: &response)

にてレスポンスを受け取ることにより解決致しました。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 88.81%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る