Lameのプログラマの日記

LameというグループでiOSアプリを作っている大学生たちです

【Swift】誰かアプリ内課金実装について教えてーーー泣

きむです! 結局昨日は朝の6:00(4/19のAM:6:00)までかけてとりあえずアプリを審査待ちの状態まで持って行きましたw そこでつまずいた、今でもちょっとよくわかっていないアプリ内課金実装について自分のアプリでやった事と疑問点をまとめて、あわよくば誰かさんが教えてくれたらな〜😋と思っています笑

とりあえず実際に書いたコード

↓この記事を参考にまずはiTunes Connectの設定をします。(これも大分重かったけど一応できました。)

アプリ内課金の実装方法 - TERAKOYA

そしてまたまたこの記事と全く同じコードを書きました。 以下が実際のソースコードです。(関係ないとこは省略しています)

import UIKit
import AVFoundation
import Accounts

class ViewController: UIViewController, AVAudioPlayerDelegate, GADBannerViewDelegate , GADRewardBasedVideoAdDelegate,PurchaseManagerDelegate {
    
    /*
    *課金ここから
    */
    let productIdentifiers : [String] = ["com.Lame.TenPazzle_10","com.Lame.TenPazzle_55","com.Lame.TenPazzle_110"]
    @IBOutlet weak var priceLabel: UILabel!
    /*
    *課金ここまで
    */
    
    //userDの初期値
    let userDefaults = UserDefaults.standard

    @IBOutlet weak var cartButton: UIButton!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        
        /*
        *課金ここから
        */
        // プロダクト情報取得
        fetchProductInformationForIds(productIdentifiers)
        /*
        *課金ここまで
        */
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

    @IBAction func tapCartButton(_ sender: UIButton) {
        if userDefaults.string(forKey: "volume") == "1"{
        koukaonPlayer.currentTime = 0
        koukaonPlayer.play()
        }
        
        let alert: UIAlertController = UIAlertController(title: "", message: "SHOP", preferredStyle:  UIAlertControllerStyle.alert)
        
        let AdOffAction: UIAlertAction = UIAlertAction(title: "広告削除    ¥360", style: UIAlertActionStyle.default, handler:{
            // ボタンが押された時の処理を書く(クロージャ実装)
            (action: UIAlertAction!) -> Void in
            print("OK")
        })
 
        let hint10Action: UIAlertAction = UIAlertAction(title: "ヒント 10個    ¥120", style: UIAlertActionStyle.default, handler:{
            // ボタンが押された時の処理を書く(クロージャ実装)
            (action: UIAlertAction!) -> Void in
            print("OK")
            self.startPurchase(productIdentifier: self.productIdentifiers[0])
        })
        let hint55Action: UIAlertAction = UIAlertAction(title: "ヒント 55個    ¥600", style: UIAlertActionStyle.default, handler:{
            // ボタンが押された時の処理を書く(クロージャ実装)
            (action: UIAlertAction!) -> Void in
            print("OK")
            self.startPurchase(productIdentifier: self.productIdentifiers[1])
        })
        let hint110Action: UIAlertAction = UIAlertAction(title: "ヒント 110個   ¥840", style: UIAlertActionStyle.default, handler:{
            // ボタンが押された時の処理を書く(クロージャ実装)
            (action: UIAlertAction!) -> Void in
            print("OK")
            self.startPurchase(productIdentifier: self.productIdentifiers[2])
        })
        // キャンセルボタン
        let cancelAction: UIAlertAction = UIAlertAction(title: "キャンセル", style: UIAlertActionStyle.cancel, handler:{
            // ボタンが押された時の処理を書く(クロージャ実装)
            (action: UIAlertAction!) -> Void in
            print("Cancel")
        })
        
        // ③ UIAlertControllerにActionを追加
        alert.addAction(cancelAction)
        //alert.addAction(AdOffAction)
        alert.addAction(hint10Action)
        alert.addAction(hint55Action)
        alert.addAction(hint110Action)
        
        // ④ Alertを表示
        present(alert, animated: true, completion: nil)
    }
    
    /*
    *課金ここから
    */
    //------------------------------------
    // 課金処理開始
    //------------------------------------
    func startPurchase(productIdentifier : String) {
        print("課金処理開始!!")
        //デリゲード設定
        PurchaseManager.sharedManager().delegate = self
        //プロダクト情報を取得
        ProductManager.productsWithProductIdentifiers(productIdentifiers: [productIdentifier], completion: { (products, error) -> Void in
            if (products?.count)! > 0 {
                //課金処理開始
                PurchaseManager.sharedManager().startWithProduct((products?[0])!)
                self.userDefaults.set(productIdentifier, forKey: "productIdentifier")
                self.userDefaults.synchronize()
            }
            if (error != nil) {
                print(error)
            }
        })
    }
    // リストア開始
    func startRestore() {
        //デリゲード設定
        PurchaseManager.sharedManager().delegate = self
        //リストア開始
        PurchaseManager.sharedManager().startRestore()
    }
    //課金終了時に呼び出される
    func purchaseManager(_ purchaseManager: PurchaseManager!, didFinishPurchaseWithTransaction transaction: SKPaymentTransaction!, decisionHandler: ((_ complete: Bool) -> Void)!) {
        print("課金終了!!")
        //---------------------------
        // コンテンツ解放処理
        if userDefaults.string(forKey: "productIdentifier")! == "com.Lame.TenPazzle_10"{
            
            let hint = userDefaults.integer(forKey: "hint") + 10
            userDefaults.set("\(hint)", forKey: "hint")
            userDefaults.synchronize()
            
            //アラートで知らせる
            let alert: UIAlertController = UIAlertController(title: "課金完了", message: "ヒントを10個ゲットしました!", preferredStyle:  UIAlertControllerStyle.alert)
            
            let hintAction: UIAlertAction = UIAlertAction(title: "OK", style: UIAlertActionStyle.cancel, handler:{
                // ボタンが押された時の処理を書く(クロージャ実装)
                (action: UIAlertAction!) -> Void in
                print("OK")
            })
            
            alert.addAction(hintAction)
            DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
                // ④ Alertを表示
                self.present(alert, animated: true, completion: nil)
            }
            
        }else if userDefaults.string(forKey: "productIdentifier")! == "com.Lame.TenPazzle_55"{
            
            let hint = userDefaults.integer(forKey: "hint") + 50
            userDefaults.set("\(hint)", forKey: "hint")
            userDefaults.synchronize()
            
            //アラートで知らせる
            let alert: UIAlertController = UIAlertController(title: "課金完了", message: "ヒントを55個ゲットしました!", preferredStyle:  UIAlertControllerStyle.alert)
            
            let hintAction: UIAlertAction = UIAlertAction(title: "OK", style: UIAlertActionStyle.cancel, handler:{
                // ボタンが押された時の処理を書く(クロージャ実装)
                (action: UIAlertAction!) -> Void in
                print("OK")
            })
            
            alert.addAction(hintAction)
            DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
                // ④ Alertを表示
                self.present(alert, animated: true, completion: nil)
            }
            
        }else if userDefaults.string(forKey: "productIdentifier")! == "com.Lame.TenPazzle_110"{
            
            let hint = userDefaults.integer(forKey: "hint") + 110
            userDefaults.set("\(hint)", forKey: "hint")
            userDefaults.synchronize()
            
            //アラートで知らせる
            let alert: UIAlertController = UIAlertController(title: "課金完了", message: "ヒントを110個ゲットしました!", preferredStyle:  UIAlertControllerStyle.alert)
            
            let hintAction: UIAlertAction = UIAlertAction(title: "OK", style: UIAlertActionStyle.cancel, handler:{
                // ボタンが押された時の処理を書く(クロージャ実装)
                (action: UIAlertAction!) -> Void in
                print("OK")
            })
            
            alert.addAction(hintAction)
            DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
                // ④ Alertを表示
                self.present(alert, animated: true, completion: nil)
            }
            
        }
        
        //コンテンツ解放が終了したら、この処理を実行(true: 課金処理全部完了, false 課金処理中断)
        decisionHandler(true)
    }
    //課金終了時に呼び出される(startPurchaseで指定したプロダクトID以外のものが課金された時。)
    func purchaseManager(_ purchaseManager: PurchaseManager!, didFinishUntreatedPurchaseWithTransaction transaction: SKPaymentTransaction!, decisionHandler: ((_ complete: Bool) -> Void)!) {
        print("課金終了(指定プロダクトID以外)!!")
        //---------------------------
        // コンテンツ解放処理
        // コンテンツ解放処理
        if userDefaults.string(forKey: "productIdentifier")! == "com.Lame.TenPazzle_10"{
            
            let hint = userDefaults.integer(forKey: "hint") + 10
            userDefaults.set("\(hint)", forKey: "hint")
            userDefaults.synchronize()
            
            //アラートで知らせる
            let alert: UIAlertController = UIAlertController(title: "課金完了", message: "ヒントを10個ゲットしました!", preferredStyle:  UIAlertControllerStyle.alert)
            
            let hintAction: UIAlertAction = UIAlertAction(title: "OK", style: UIAlertActionStyle.cancel, handler:{
                // ボタンが押された時の処理を書く(クロージャ実装)
                (action: UIAlertAction!) -> Void in
                print("OK")
            })
            
            alert.addAction(hintAction)
            DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
                // ④ Alertを表示
                self.present(alert, animated: true, completion: nil)
            }
            
        }else if userDefaults.string(forKey: "productIdentifier")! == "com.Lame.TenPazzle_55"{
            
            let hint = userDefaults.integer(forKey: "hint") + 50
            userDefaults.set("\(hint)", forKey: "hint")
            userDefaults.synchronize()
            
            //アラートで知らせる
            let alert: UIAlertController = UIAlertController(title: "課金完了", message: "ヒントを55個ゲットしました!", preferredStyle:  UIAlertControllerStyle.alert)
            
            let hintAction: UIAlertAction = UIAlertAction(title: "OK", style: UIAlertActionStyle.cancel, handler:{
                // ボタンが押された時の処理を書く(クロージャ実装)
                (action: UIAlertAction!) -> Void in
                print("OK")
            })
            
            alert.addAction(hintAction)
            DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
                // ④ Alertを表示
                self.present(alert, animated: true, completion: nil)
            }
            
        }else if userDefaults.string(forKey: "productIdentifier")! == "com.Lame.TenPazzle_110"{
            
            let hint = userDefaults.integer(forKey: "hint") + 110
            userDefaults.set("\(hint)", forKey: "hint")
            userDefaults.synchronize()
            
            //アラートで知らせる
            let alert: UIAlertController = UIAlertController(title: "課金完了", message: "ヒントを110個ゲットしました!", preferredStyle:  UIAlertControllerStyle.alert)
            
            let hintAction: UIAlertAction = UIAlertAction(title: "OK", style: UIAlertActionStyle.cancel, handler:{
                // ボタンが押された時の処理を書く(クロージャ実装)
                (action: UIAlertAction!) -> Void in
                print("OK")
            })
            
            alert.addAction(hintAction)
            DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
                // ④ Alertを表示
                self.present(alert, animated: true, completion: nil)
            }
            
        }
        //---------------------------
        
        //コンテンツ解放が終了したら、この処理を実行(true: 課金処理全部完了, false 課金処理中断)
        decisionHandler(true)
    }
    //課金失敗時に呼び出される
    func purchaseManager(_ purchaseManager: PurchaseManager!, didFailWithError error: NSError!) {
        print("課金失敗!!")
        // TODO errorを使ってアラート表示
        //アラートで知らせる
        let alert: UIAlertController = UIAlertController(title: "課金失敗!", message: "通信環境の良いところで行なってね", preferredStyle:  UIAlertControllerStyle.alert)
        
        let hintAction: UIAlertAction = UIAlertAction(title: "とじる", style: UIAlertActionStyle.cancel, handler:{
            // ボタンが押された時の処理を書く(クロージャ実装)
            (action: UIAlertAction!) -> Void in
            print("OK")
        })
        
        alert.addAction(hintAction)
        DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
            // ④ Alertを表示
            self.present(alert, animated: true, completion: nil)
        }
        
    }
    // リストア終了時に呼び出される(個々のトランザクションは”課金終了”で処理)
    func purchaseManagerDidFinishRestore(_ purchaseManager: PurchaseManager!) {
        print("リストア終了!!")
        // TODO インジケータなどを表示していたら非表示に
    }
    // 承認待ち状態時に呼び出される(ファミリー共有)
    func purchaseManagerDidDeferred(_ purchaseManager: PurchaseManager!) {
        print("承認待ち!!")
        // TODO インジケータなどを表示していたら非表示に
    }
    // プロダクト情報取得
    fileprivate func fetchProductInformationForIds(_ productIds:[String]) {
        ProductManager.productsWithProductIdentifiers(productIdentifiers: productIds,completion: {[weak self] (products : [SKProduct]?, error : NSError?) -> Void in
            if error != nil {
                if self != nil {
                }
                print(error?.localizedDescription)
                return
            }
            for product in products! {
                let priceString = ProductManager.priceStringFromProduct(product: product)
                if self != nil {
                    print(product.localizedTitle + ":\(priceString)")
                    //self?.priceLabel.text = product.localizedTitle + ":\(priceString)"
                }
                print(product.localizedTitle + ":\(priceString)" )
            }
        })
    }
    /*
    *課金ここまで
    */

}

見にくくてすみません... 自分が特にわからなかったのはリストアの処理と課金処理開始の時持ってきたproductIDの判別方法です。 リストアはとりあえず

// リストア開始
    func startRestore() {
        //デリゲード設定
        PurchaseManager.sharedManager().delegate = self
        //リストア開始
        PurchaseManager.sharedManager().startRestore()
    }

これだけ書いとけばいいんですかね?? あとはproductIDは

// 課金処理開始
    func startPurchase(productIdentifier : String) {
        print("課金処理開始!!")
        //デリゲード設定
        PurchaseManager.sharedManager().delegate = self
        //プロダクト情報を取得
        ProductManager.productsWithProductIdentifiers(productIdentifiers: [productIdentifier], completion: { (products, error) -> Void in
            if (products?.count)! > 0 {
                //課金処理開始
                PurchaseManager.sharedManager().startWithProduct((products?[0])!)
                self.userDefaults.set(productIdentifier, forKey: "productIdentifier")
                self.userDefaults.synchronize()
            }
            if (error != nil) {
                print(error)
            }
        })
    }

このときにuserDefaultで保存して課金終了時にuserDefaultと参照して指定の個数のヒントをあげているんですけどこんな原始的なやり方でいいんでしょうか...?

あとは実機で確認しようとしても課金ボタンを押してから課金までの処理がとても長くて本当にあっているのかと不安になります。Testモードだからなのかな...? とりあえず実機で確認したところ課金はできるにはできるので審査に出しましたがおそらくここからリジェクトパラダイス何だろうな...

という事でまだリリースはできてませんがこのゲーム以外と難易度も高くて飽きないと思うのでリリースしたときにはぜひインストールお願いいたします!

またゲームのプレイ画面などは追って紹介したいと思います!