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

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

ただいまの
回答率

90.63%

  • Swift

    7010questions

    Swiftは、アップルのiOSおよびOS Xのためのプログラミング言語で、Objective-CやObjective-C++と共存することが意図されています

  • Swift 2

    1331questions

    Swift 2は、Apple社が独自に開発を行っている言語「Swift」のアップグレード版です。iOSやOS X、さらにLinuxにも対応可能です。また、throws-catchベースのエラーハンドリングが追加されています。

swiftで課金(IAP)を実装しているのですが分からないことがあります。

受付中

回答 0

投稿 編集

  • 評価
  • クリップ 1
  • VIEW 415

Kw6ZG2sV

score 56

IOSでアプリ内課金を実装しています。
よくある形のアプリ内で使用できるポイントを課金アイテムとしています。
今までリリースしてきたアプリにも同じ方法で実装していて大半は
うまくいっているのですが(正常に機能している)、
アイテムを購入したのにポイントが反映されないなどのバグも起きているのも現状です。
通信エラーが絡んでたまたま起きたバグかと思っていましたが、最近はそれが多くなって
いるので何かおかしいところがあるのではないかと感じ質問させていただきました。

ちなみにこの課金システムは消耗型で、レシートを自サーバに保存する方法をとっています。
作成したのは自分ではないのですが、このサイトを参考にしたと思われます。
リンク内容

何か問題がある箇所や変更した方がいい箇所などございますでしょうか?
アドバイスを頂ければ幸いでございます。
よろしくお願い致します。

課金部分のソースは以下となります。

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)


        if SKPaymentQueue.default().transactions.count>0 {
            for tempTransaction:SKPaymentTransaction in SKPaymentQueue.default().transactions {
                if tempTransaction.transactionState != .purchasing{
                    SKPaymentQueue.default().finishTransaction(tempTransaction)
                }
            }
        }
    }
    func tap(_ sender:UIGestureRecognizer) {


        let tag = (sender.view?.tag)! - 1

        print(self.productid[tag])
        product_id = self.productid[tag]
        let setdata:Set<String> = [product_id]

        //アプリ内課金許可されているかチェック
        if SKPaymentQueue.canMakePayments() {
            print(SKPaymentQueue.canMakePayments())
            let data = SKProductsRequest(productIdentifiers: setdata)
            data.delegate = self

            // 購入処理中にUIAlertControllerを表示させる
            let alert = UIAlertController(title: "処理中",
                                          message: "\(self.count[tag])コインの購入処理中です",
                preferredStyle: .alert)
            present(alert, animated: true, completion: nil)
            self.alert = alert

            data.start()
        }
    }
    //課金アイテム取得後に呼ばれるメソッド。didReceiveに取得した情報
    func productsRequest(_: SKProductsRequest, didReceive: SKProductsResponse){
        let data = didReceive.products
        let indata = didReceive.invalidProductIdentifiers

        if indata.count == 0 {
            SKPaymentQueue.default().add(self)
            for product_data:SKProduct in data {
                let request_data = SKPayment(product: product_data)
                SKPaymentQueue.default().add(request_data)
            }
        }else{
            print("abcde\(indata)")
        }

    }
    func priceStringFromProduct(product: SKProduct!) -> String {
        let numberFormatter = NumberFormatter()
        numberFormatter.formatterBehavior = .behavior10_4
        numberFormatter.numberStyle = .currency
        numberFormatter.locale = product.priceLocale
        return numberFormatter.string(from: product.price)!
    }
    func idStringFromProduct(product: SKProduct!) -> String {
        let numberFormatter = NumberFormatter()
        numberFormatter.formatterBehavior = .behavior10_4
        numberFormatter.numberStyle = .currency
        //numberFormatter.s = product.productIdentifier
        return product.productIdentifier
    }
    func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {

        for transaction:SKPaymentTransaction in transactions{
            print("transaction.error",transaction.error)

            switch transaction.transactionState {
            case .purchasing:
                print("purchasing")
            case .purchased:
                print("purchased")


                if let receiptUrl:URL = Bundle.main.appStoreReceiptURL{
                    if let receiptData:NSData = NSData(contentsOf: receiptUrl){
                        let receiptBase64Str: String = receiptData.base64EncodedString(options: NSData.Base64EncodingOptions(rawValue: UInt(0)))
                        print("receiptBase64Strrr",receiptBase64Str)
                        param = 自サバのapiurl+receiptBase64Str
                        receipt = receiptBase64Str
                    }
                }
                if product_id == "pranA" {
                    //計測SDKの記述
                }
                //planB~planEまで同じ処理

                print(param)
                let purchase1 = sub_purchase(url_dom: "自社サーバに登録するためのapiのurl", url_param: param)
                purchase1.get(){json in
                    print("purchase1\(json)")
                }
                let purchase2 = sub_purchase2(url_dom: "https://sandbox.itunes.apple.com/verifyReceipt",url_param: receipt)
                purchase2.get(){json in
                    print("purchase2\(json)")
                }


                queue.finishTransaction(transaction)
                //先に出てるalertを閉じる
                alert?.dismiss(animated: true, completion: nil)
                alert = nil
            case .failed:
                print("失敗","failed")
                DispatchQueue.main.async {
                    self.alert?.dismiss(animated: true, completion: nil)
                    self.alert = nil

                    let newAlert = UIAlertController(title: "購入がキャンセルされました",
                                                     message: nil,
                                                     preferredStyle: .alert)
                    let action = UIAlertAction(title: "OK",
                                               style: .default) { action in
                                                self.alert = nil
                    }
                    newAlert.addAction(action)
                    self.present(newAlert, animated: true, completion: nil)
                    self.alert = newAlert
                }
            case .restored:
                print("復元中","restored")
                queue.finishTransaction(transaction)
            default:
                queue.finishTransaction(transaction)
                break
            }
        }
    }
    func paymentQueue(_ queue: SKPaymentQueue,removedTransactions transactions: [SKPaymentTransaction]){
        SKPaymentQueue.default().remove(self)
        print("トランザクション個数",queue.transactions.count)
        print("トランザクション削除","removedtransactions")
        /*let request = SKReceiptRefreshRequest.init()
         request.delegate = self
         request.start()*/
    }
    func paymentQueue(_ queue: SKPaymentQueue, restoreCompletedTransactionsFailedWithError: Error){
        print("エラー",restoreCompletedTransactionsFailedWithError)
    }
    func paymentQueueRestoreCompletedTransactionsFinished(_ queue: SKPaymentQueue){
        print("リストア",queue)
    }
import Foundation
import UIKit

class sub_purchase1 {
    var url_dom = ""
    var url_param = ""

    init(url_dom: String,url_param: String){
        self.url_dom = url_dom
        self.url_param = url_param
    } 
    func get(_ callback: @escaping (_ json:AnyObject) -> Void){
        let url = URL(string: url_dom)
        let config = URLSessionConfiguration.default
        let session = URLSession(configuration: config)
        let request = NSMutableURLRequest(url: url!)
        request.httpMethod = "POST"
        request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField:"content-type")
        request.httpBody = url_param.data(using: String.Encoding.utf8)
        let task = session.dataTask(with: request as URLRequest, completionHandler: {
            (data, response, error)in
            do{
                let json = try JSONSerialization.jsonObject(with: data!, options: JSONSerialization.ReadingOptions.mutableContainers)  as AnyObject
                print("error: \(json["err_msg"])")
                callback(json)
            }catch{
                print("error serializing JSON: \(error)")
            }
        })
        task.resume()
    }

}
import Foundation
import UIKit

class sub_purchase2 {
    var url_dom = ""
    var url_param = ""

    init(url_dom: String,url_param: String){
        self.url_dom = url_dom
        self.url_param = url_param
    }

    func get(_ callback: @escaping (_ json:AnyObject) -> Void){
        let url = URL(string: url_dom)
        let config = URLSessionConfiguration.default
        let session = URLSession(configuration: config)
        let requestContents: NSDictionary = ["receipt-data": url_param] as NSDictionary

        var requestData = NSData()
        do {
            requestData = try JSONSerialization.data(withJSONObject: requestContents, options: JSONSerialization.WritingOptions.prettyPrinted) as NSData
        } catch  {
            print("error serializing JSON: \(error)")
        }

        let request = NSMutableURLRequest(url: url!)
        request.httpMethod = "POST"
        request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField:"content-type")
        request.httpBody = requestData as Data//url_param.data(using: String.Encoding.utf8)
        let task = session.dataTask(with: request as URLRequest, completionHandler: {
            (data, response, error)in
            do{
                let json = try JSONSerialization.jsonObject(with: data!, options: JSONSerialization.ReadingOptions.mutableContainers)  as AnyObject
                //print(json)
                callback(json)
            }catch{
                print("error serializing JSON: \(error)")
            }
        })
        task.resume()
    }

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

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

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

    クリップを取り消します

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

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

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

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

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

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

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

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

    質問の評価を下げる

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

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

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

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

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

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

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

    詳細な説明はこちら

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

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

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

まだ回答がついていません

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

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

関連した質問

  • 解決済

    ツイートの投稿

    大学で卒業研究としてTwitterAPIを利用したアプリケーションを開発しています。 その過程で以下のようにツイート投稿のところで行き詰まりました。 同じようなコードでタイムラ

  • 解決済

    swiftにて画像アップロード(サーバ上へ)

    以前に質問した内容に近いのですが、またご教授いただければ幸いです。 下記のサイトを参考にしながら、ファイルのアップロードをしたいと考えております。 【Swift】Swift

  • 受付中

    Alamofire + SwiftyJSONでreturnがnilになる

    前提・実現したいこと Alamofire+SwiftyJSONを利用して、APIからデータを取得・パースして、returnで戻したい。 発生している問題・エラーメッセージ

  • 解決済

    swift3でのサイトのhtml取得

    サイトのhtmlを取得しようと このサイトを目標にこのサイトを見たりしてやってみているのですがどうしてもうまくいきません。 URLSession.shared...の行でエラー

  • 解決済

    swift3.0でAPIを使いたい

    xcode8でswift3.0を勉強している初心者です。 swift3.0のバージョンアップに伴い、既存のテキストやサンプルコードが一部変更され、 もともとのコードもわからない

  • 解決済

    TableViewのデータソースメソッドが呼び出されません。

    以下のようなコードを書いたのですが、 func tableView(_ tableView: UITableView, cellForRowAt indexPath: Index

  • 解決済

    【swift】dataTask(with: request, completionHandler: ...

    swift2で書かれた下記コードをswift3でビルド使用とすると、 dataTask(with: request, completionHandler: の部分で 「Ca

  • 解決済

    iOSでGoogle Custom Searchを使いたい

    前提・実現したいこと iOSアプリにて、wordImageという名前でOutlet接続したUIImageViewにGoogle Custom Searchエンジンを使用した検索結

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

  • Swift

    7010questions

    Swiftは、アップルのiOSおよびOS Xのためのプログラミング言語で、Objective-CやObjective-C++と共存することが意図されています

  • Swift 2

    1331questions

    Swift 2は、Apple社が独自に開発を行っている言語「Swift」のアップグレード版です。iOSやOS X、さらにLinuxにも対応可能です。また、throws-catchベースのエラーハンドリングが追加されています。