<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>In-App Purchase &#8211; 隨心所欲</title>
	<atom:link href="https://doeverythingiwant.com/tag/in-app-purchase/feed/" rel="self" type="application/rss+xml" />
	<link>https://doeverythingiwant.com</link>
	<description>iOS Developer 的隨筆記錄</description>
	<lastBuildDate>Mon, 20 Nov 2023 15:11:06 +0000</lastBuildDate>
	<language>zh-TW</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=7.1</generator>

<image>
	<url>https://doeverythingiwant.com/wp-content/uploads/2025/08/cropped-0802.png</url>
	<title>In-App Purchase &#8211; 隨心所欲</title>
	<link>https://doeverythingiwant.com</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>實作 StoreKit 2 的 IAP (In-App Purchase)</title>
		<link>https://doeverythingiwant.com/implement-storekit-2-iap-in-app-purchase/</link>
					<comments>https://doeverythingiwant.com/implement-storekit-2-iap-in-app-purchase/#respond</comments>
		
		<dc:creator><![CDATA[艾普利]]></dc:creator>
		<pubDate>Sun, 25 Dec 2022 13:34:31 +0000</pubDate>
				<category><![CDATA[寫程式]]></category>
		<category><![CDATA[In-App Purchase]]></category>
		<category><![CDATA[iOS]]></category>
		<category><![CDATA[Storekit]]></category>
		<guid isPermaLink="false">https://doeverythingiwant.com/?p=200</guid>

					<description><![CDATA[Photo by Kelly Sikkema on Unsp&#8230;]]></description>
										<content:encoded><![CDATA[
<p class="has-text-align-center wp-block-paragraph">Photo by <a href="https://unsplash.com/@kellysikkema?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText" target="_blank" rel="noopener">Kelly Sikkema</a> on <a href="https://unsplash.com/s/photos/buy?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText" target="_blank" rel="noopener">Unsplash</a></p>



<p class="wp-block-paragraph">在 WWDC21時 Apple 發表了StoreKit 2，除了增加更多與商品相關的參數外，也調整了過去 IAP 的程式流程，並使用到同樣時間發表的新技術async /await 讓程式看起來更為簡潔，至於好不好閱讀的話…如果你是習慣使用過去 StoreKit 的流程，在看新流程的時候會需要看個幾次才會比較理解。</p>



<span id="more-200"></span>



<h2 class="wp-block-heading">Async / await</h2>



<p class="wp-block-paragraph">在正式開始之前，有幾個必需要先知道一下，第一個就是Async/await，非同步處理的新機制，Google 一下會發現其他語言其實早就有實作這樣的機制，主要是為了取代 Closure 的寫法，讓閱讀起來更為方便，在寫法上，想要支援async的 function 就是要在後方加上 <strong>async</strong> 關鍵字，依照 function 需要可以不用加上 <strong>throws</strong>，但要記得處理 error。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-swift" data-lang="Swift"><code>func purchase(_ product: Product) async throws -&gt; Transaction? {
...
}</code></pre></div>



<p class="wp-block-paragraph">在使用這個function的時候就要加上 <strong>await</strong> 關鍵字</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-swift" data-lang="Swift"><code> do {
    if try await store.purchase(product) != nil {
      ...
    }
 } catch {
    print(&quot;Error :\(error)&quot;)
 }</code></pre></div>



<h2 class="wp-block-heading">Testing in Xcode</h2>



<p class="wp-block-paragraph">另一個要知道的是，利用 Xcode 來進行IAP 的測試，過去若需要測試 IAP 流程的話，就必需要去 App Store Connect 建立App 與 IAP 項目，但是要讓 App 可以抓取到 App Store Connect 上的IAP 項目，就需要先提供銀行帳戶資訊給Apple ，還需要同意付款(Apple 付款給你) 的條約，對只是想要練習IAP 的人來說，十分地不方便。</p>



<p class="wp-block-paragraph">但現在只要做好設定，就可以在Xcode 裡測試IAP 的流程，官方文件在這，<a rel="noreferrer noopener" href="https://developer.apple.com/documentation/xcode/setting-up-storekit-testing-in-xcode" target="_blank">Setting up StoreKit testing in Xcode</a> ，所有類型的IAP 項目都可以測試，也可以在沒有網路的情況下測試。</p>



<p class="wp-block-paragraph">要在 Xcode 最重要的是要設定一個 StoreKit configuration，這個config 檔裡設定了IAP 項目資料，主要是 &#8220;Reference Name&#8221;、&#8221;Product ID&#8221;、&#8221;Price&#8221;，另外還要設定對應不同系語的 &#8220;Display name&#8221;與&#8221;Description&#8221; ，是App Store Connect 的簡化版設定，若是要設定Subscription 項目的話也是要設定一個 Subscription Group 的</p>



<figure class="wp-block-image size-large"><img fetchpriority="high" decoding="async" width="1024" height="265" src="https://doeverythingiwant.com/wp-content/uploads/2022/11/截圖-2022-11-27-下午4.45.50-1024x265.png" alt="" class="wp-image-1919" srcset="https://doeverythingiwant.com/wp-content/uploads/2022/11/截圖-2022-11-27-下午4.45.50-1024x265.png 1024w, https://doeverythingiwant.com/wp-content/uploads/2022/11/截圖-2022-11-27-下午4.45.50-300x78.png 300w, https://doeverythingiwant.com/wp-content/uploads/2022/11/截圖-2022-11-27-下午4.45.50-768x199.png 768w, https://doeverythingiwant.com/wp-content/uploads/2022/11/截圖-2022-11-27-下午4.45.50-1536x398.png 1536w, https://doeverythingiwant.com/wp-content/uploads/2022/11/截圖-2022-11-27-下午4.45.50-2048x531.png 2048w, https://doeverythingiwant.com/wp-content/uploads/2022/11/截圖-2022-11-27-下午4.45.50-1920x497.png 1920w, https://doeverythingiwant.com/wp-content/uploads/2022/11/截圖-2022-11-27-下午4.45.50-1170x303.png 1170w, https://doeverythingiwant.com/wp-content/uploads/2022/11/截圖-2022-11-27-下午4.45.50-585x152.png 585w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p class="wp-block-paragraph">準備好 StoreKit 的 configuration 檔案之後，記得至 Scheme 上選用設定好的Configuration 不然是無法使用的。</p>



<figure class="wp-block-image size-large"><img decoding="async" width="1024" height="551" src="https://doeverythingiwant.com/wp-content/uploads/2022/11/截圖-2022-11-27-下午4.55.16-1024x551.png" alt="" class="wp-image-1920" srcset="https://doeverythingiwant.com/wp-content/uploads/2022/11/截圖-2022-11-27-下午4.55.16-1024x551.png 1024w, https://doeverythingiwant.com/wp-content/uploads/2022/11/截圖-2022-11-27-下午4.55.16-300x161.png 300w, https://doeverythingiwant.com/wp-content/uploads/2022/11/截圖-2022-11-27-下午4.55.16-768x413.png 768w, https://doeverythingiwant.com/wp-content/uploads/2022/11/截圖-2022-11-27-下午4.55.16-1536x826.png 1536w, https://doeverythingiwant.com/wp-content/uploads/2022/11/截圖-2022-11-27-下午4.55.16-2048x1102.png 2048w, https://doeverythingiwant.com/wp-content/uploads/2022/11/截圖-2022-11-27-下午4.55.16-1920x1033.png 1920w, https://doeverythingiwant.com/wp-content/uploads/2022/11/截圖-2022-11-27-下午4.55.16-1170x629.png 1170w, https://doeverythingiwant.com/wp-content/uploads/2022/11/截圖-2022-11-27-下午4.55.16-585x315.png 585w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p class="wp-block-paragraph">前置作業準備好之後，終於要開始進入正題</p>



<h2 class="wp-block-heading"><strong>取得商品資訊</strong></h2>



<p class="wp-block-paragraph">先建立一個 Store manager</p>



<p class="wp-block-paragraph">和先前一樣，在請求商品資訊的時候，必需要先知道 Product ID 呼叫 <code>try await Product.products(for: productIds) </code>就可以取得商品</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-swift" data-lang="Swift"><code>@MainActor
    func requestProducts() async {
        do {
            let storeProducts = try await Product.products(for: productInfo.keys)
            
            var newDrinks: [Product] = []
            var newVip: [Product] = []
            
            for product in storeProducts {
                switch product.type {
                case .consumable:
                    newDrinks.append(product)
                case .nonConsumable:
                    newVip.append(product)
                default:
                    //Ignore this product.
                    print(&quot;Unknown product&quot;)
                }
            }
            
            drinks = sortByPrice(newDrinks)
            vipTicket = sortByPrice(newVip)
            
        } catch {
            print(&quot;Failed product request: \(error)&quot;)
        }
    }</code></pre></div>



<ul class="wp-block-list">
<li><code>productInfo.keys </code>指的就是所有的 Product ID，因為productInfo 是個 Dictionary 且 Key 就是 Product ID ，所以直接呼叫<code>productInfo.keys</code>來取得所有的 Product ID</li>
</ul>



<ul class="wp-block-list">
<li>這個 <code>requestProducts()</code> 使用了 <code>@MainActor</code> 這是 SwiftUI 為了取代 Dispatch 的語法，由於 <code>Product.products(for: productInfo.keys)</code>，因為<code>requestProducts()</code>在之後的使用上也想要使用 async / await 的方式，也因為<code>Product.products(for: productInfo.keys)</code>是 基於 async / await 的方式撰寫，因此也加上了 <code>async</code> 關鍵字</li>
</ul>



<ul class="wp-block-list">
<li><code>storeProducts</code> 取得IAP 項目之後，可以使用 <code>product.type</code> 得知這個項目是屬於那種類型的項目，可以使用type 就可以來為Product 分類。</li>
</ul>



<h2 class="wp-block-heading"><strong>購買商品</strong></h2>



<p class="wp-block-paragraph">購買商品的時候是呼叫 <code>try await product.purchase()</code> 就行了，再來就等到結果回傳就好了</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-swift" data-lang="Swift"><code>func purchase(_ product: Product) async throws -&gt; Transaction? {
        
        let result = try await product.purchase()
        
        switch result {
        case .success(let verification):
            let transaction = try checkVerified(verification)
            
            await updatePurchasedIDs(transaction)
            
            // finish a transaction
            await transaction.finish()
            
            return transaction
            
        case .userCancelled, .pending:
            return nil
        default:
            return nil
        }
    }</code></pre></div>



<ul class="wp-block-list">
<li><code>func purchase(_ product: Product) async throws</code> 它會把 Error 丟出來就表示呼叫它的需要處理 Error 狀態，像這樣的Error 一般需要給使用者回饋反應，所以官方的Demo code 是把這個 Error 丟出來的，讓View 的部分去處理這個 Error ，就看怎麼使用它</li>



<li>result 有幾種狀態，可以依照狀態做各別處理
<ul class="wp-block-list">
<li>success 成功完成動作，它會給一個verification 可以利用這個去取得Transaction</li>



<li>userCancelled 表示使用者自行取消動作</li>



<li>pending 表示還未處理完，purchase 正在處於待處理狀態</li>
</ul>
</li>



<li>checkVerified，在收到 Success 的時候需要去取得Transaction，基本上從官方 Demo code 直接搬過來用就好了</li>
</ul>



<div class="hcb_wrap"><pre class="prism line-numbers lang-swift" data-lang="Swift"><code>func checkVerified&lt;T&gt;(_ result: VerificationResult&lt;T&gt;) throws -&gt; T {
        //Check if the transaction passes StoreKit verification.
        switch result {
        case .unverified:
            //StoreKit has parsed the JWS but failed verification. Don&#39;t deliver content to the user.
            throw StoreError.failedVerification
        case .verified(let safe):
            //If the transaction is verified, unwrap and return it.
            return safe
        }
    }</code></pre></div>



<ul class="wp-block-list">
<li><code>await updatePurchasedIDs(transaction)</code> 這行是為了更新 UI 而做的，所以依照需撾求去處理就好了，可以從 Transaction 當中取得 Product id  或是 Expiration Date ，一共有多少參數可以參考官方件 <a href="https://developer.apple.com/documentation/storekit/transaction/transaction_properties" target="_blank" rel="noopener">transaction properties</a></li>
</ul>



<p class="wp-block-paragraph"></p>



<h2 class="wp-block-heading">監聽交易</h2>



<p class="wp-block-paragraph">App 開啟的時候，需要監聽IAP 項目的狀態，在官方的 Demo 是使用 E<code>nvironment Object</code> 讓App 全部的地方都可以知道其狀態，在 <code>ContentView</code> 要建立 <code>@StateObject var store: Store = Store()</code>再利用  <code>.environmentObject(store)</code> 讓其他頁面使用</p>



<p class="wp-block-paragraph">因此在 Store manager 需要增加一個<code> var listenerTask: Task? = nil</code> 用來接收 IAP 項目的狀態，程式也可以直接從 Demo code 裡直接搬來使用</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-swift" data-lang="Swift"><code>func listenForTransactions() -&gt; Task&lt;Void, Error&gt; {
        return Task.detached {
            
            for await result in Transaction.updates {
                do {
                    let transcation = try self.checkVerified(result)
                    
                    await self.updatePurchasedIDs(transcation)
                    
                    await transcation.finish()
                } catch {
                    print(&quot;Transcation failed verification&quot;)
                }
            }
            
        }
    }</code></pre></div>



<p class="wp-block-paragraph">主要是要檢查 <code>Transaction.updates</code> 裡的資料，它們被包在 <code>Task.detached</code> 裡，再來就和 <code>purchase</code> success 要做的事差不多了</p>



<h3 class="wp-block-heading">更新畫面</h3>



<p class="wp-block-paragraph">最後有教怎麼在進到畫面的時候做IAP 項目是否有購買的確認，首先，在 View 上需要新增一個</p>



<p class="wp-block-paragraph"> <code>@State var isPurchased = false</code></p>



<p class="wp-block-paragraph">在 onAppear 的地方寫上下面程式</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-swift" data-lang="Swift"><code>.onAppear {
    Task {
       isPurchased = (try? await store.isPurchased(drink.id)) ?? false
    }
}</code></pre></div>



<p class="wp-block-paragraph">在 store manager 的地方再加上</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-swift" data-lang="Swift"><code>func isPurchased(_ productId: String) async throws -&gt; Bool {
        
    guard let result = await Transaction.latest(for: productId) else {
        return false
    }
        
    let transcation = try checkVerified(result)
        
    return transcation.revocationDate == nil && !transcation.isUpgraded
}</code></pre></div>



<p class="wp-block-paragraph"><code>Transaction.latest(for: productId)</code> 可以得知指定的Product ID 的最後的狀態， <code>transcation.isUpgraded</code> 是只有 Subscription 型的項目才會有的狀態，true 表示已經換成比較高等的服務了</p>



<h2 class="wp-block-heading">Server?</h2>



<p class="wp-block-paragraph">在官方的說明中，Server 可以利用 Apple 給的 Notification 知道使用者的交易狀態，過去大多數情況都會從 App 端去把 Receipt 上傳給 Server ，但在 StoreKit2的時候並沒有看到相關教學，但也可能是漏看到，在Apple 的新Notification 所能得知的資訊也比先前的更多，當使用者購買行為有任何變更時， Server 都可以收到通知，詳情可以看 <a href="https://developer.apple.com/documentation/appstoreservernotifications" target="_blank" rel="noopener">Apple store server notifications</a></p>



<p class="wp-block-paragraph"></p>



<h2 class="wp-block-heading">Reference</h2>



<p class="wp-block-paragraph">上述程式碼都是用Apple 提供的 Simple code <a rel="noreferrer noopener" href="https://developer.apple.com/documentation/storekit/in-app_purchase/implementing_a_store_in_your_app_using_the_storekit_api" target="_blank">Implementing a store in your app using the StoreKit API</a> 去修改成自己需要的或直接拿來用的，因此可直接參考使用</p>



<p class="wp-block-paragraph">最後祝大家 Coding 愉快!!</p>



<p class="wp-block-paragraph"></p>



<p class="wp-block-paragraph"></p>
]]></content:encoded>
					
					<wfw:commentRss>https://doeverythingiwant.com/implement-storekit-2-iap-in-app-purchase/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>實作 IAP (In-App Purchase)</title>
		<link>https://doeverythingiwant.com/implement-storekit/</link>
					<comments>https://doeverythingiwant.com/implement-storekit/#respond</comments>
		
		<dc:creator><![CDATA[艾普利]]></dc:creator>
		<pubDate>Mon, 26 Sep 2022 14:47:00 +0000</pubDate>
				<category><![CDATA[寫程式]]></category>
		<category><![CDATA[IAP]]></category>
		<category><![CDATA[In-App Purchase]]></category>
		<category><![CDATA[iOS]]></category>
		<category><![CDATA[Storekit]]></category>
		<guid isPermaLink="false">https://doeverythingiwant.com/?p=191</guid>

					<description><![CDATA[Photo by Kelly Sikkema on Unsp&#8230;]]></description>
										<content:encoded><![CDATA[
<p class="has-text-align-center wp-block-paragraph">Photo by <a href="https://unsplash.com/@kellysikkema?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText" target="_blank" rel="noopener">Kelly Sikkema</a> on <a href="https://unsplash.com/s/photos/buy?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText" target="_blank" rel="noopener">Unsplash</a></p>



<p class="wp-block-paragraph">去年WWDC針對IAP 用的StoreKit 做了大更新，這個SDK 已經用了快10年，中間雖然也是有做過一些些調整，本次可是整個SDK流程都改變了，不過可惜的事，必需要iOS15之後才可以使用新流程，若App支援iOS 14以下的話，還是需要使用過去的那套流程</p>



<span id="more-191"></span>



<p class="wp-block-paragraph">有一個無論是新舊流程都可以使用的功能，在過去若要測試IAP 的時候一定要去 App store connect 中設定好商品、沙箱測試人員…等等之後才可以進行測試。</p>



<p class="wp-block-paragraph">現在可以直接使用Xcode 就可以測試了!! 可以測試的範圍可以到購買完成，當然如果要想得到Receipt 還是要在App store connect 設定好且用實機進行測試</p>



<p class="wp-block-paragraph"><a href="https://developer.apple.com/documentation/xcode/setting-up-storekit-testing-in-xcode#see-also" target="_blank" rel="noopener"><strong>Apple Developer Documentation</strong><br>&nbsp;<em>Setting Up StoreKit Testing in Xcode </em>developer.apple.com</a><a href="https://developer.apple.com/documentation/xcode/setting-up-storekit-testing-in-xcode#see-also" target="_blank" rel="noopener"></a></p>



<p class="wp-block-paragraph">此文章會先介紹StoreKit 的程式寫法，StoreKit 2 會撰寫另一篇文章</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p class="wp-block-paragraph">在開始之前，簡單說明一下對於App 而言的IAP 流程是怎麼樣的</p>



<figure class="wp-block-image size-large"><img decoding="async" width="1024" height="468" src="https://doeverythingiwant.com/wp-content/uploads/2022/09/6666-1024x468.png" alt="" class="wp-image-193" srcset="https://doeverythingiwant.com/wp-content/uploads/2022/09/6666-1024x468.png 1024w, https://doeverythingiwant.com/wp-content/uploads/2022/09/6666-300x137.png 300w, https://doeverythingiwant.com/wp-content/uploads/2022/09/6666-768x351.png 768w, https://doeverythingiwant.com/wp-content/uploads/2022/09/6666-1536x702.png 1536w, https://doeverythingiwant.com/wp-content/uploads/2022/09/6666-2048x936.png 2048w, https://doeverythingiwant.com/wp-content/uploads/2022/09/6666-600x274.png 600w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p class="wp-block-paragraph">App 裡要做的事情大致上有四件事</p>



<p class="wp-block-paragraph"><strong>1、取得商品資訊</strong></p>



<p class="wp-block-paragraph"><strong>2、購買商品</strong></p>



<p class="wp-block-paragraph"><strong>3、交易狀態處理</strong></p>



<p class="wp-block-paragraph"><strong>4、取得收據</strong></p>



<p class="wp-block-paragraph">一般大多數都是由後端Server 去處理Verify Receipt，且Apple也不希望是由App 端去進行，若有需要相關資訊的可以參考官網說明</p>



<p class="wp-block-paragraph"><a href="https://developer.apple.com/documentation/storekit/original_api_for_in-app_purchase/validating_receipts_with_the_app_store" target="_blank" rel="noopener"><strong>Apple Developer Documentation</strong><br><em>Validating Receipts with App Store</em>developer.apple.com</a><a href="https://developer.apple.com/documentation/storekit/original_api_for_in-app_purchase/validating_receipts_with_the_app_store" target="_blank" rel="noopener"></a></p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p class="wp-block-paragraph">終於要開始寫程式了!</p>



<p class="wp-block-paragraph">可至官網下載 Demo Code</p>



<p class="wp-block-paragraph"><a href="https://developer.apple.com/documentation/storekit/original_api_for_in-app_purchase/offering_completing_and_restoring_in-app_purchases" target="_blank" rel="noopener"><strong>Apple Developer Documentation</strong><br><em>Offering, Completing and Restoring In-App Purchases</em>developer.apple.com</a><a href="https://developer.apple.com/documentation/storekit/original_api_for_in-app_purchase/offering_completing_and_restoring_in-app_purchases" target="_blank" rel="noopener"></a></p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading"><strong>取得商品資訊</strong></h2>



<p class="wp-block-paragraph">要取得商品資訊之後，必需要先知道Product ID ，用Product ID 去向Apple 要取商品資料</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-swift" data-lang="Swift"><code>private(set) var products: [SKProduct] = []
private var productRequest: SKProductsRequest!
    
// MARK: - get production
func getProductInfo() {
    let productIdentifiers = Set([&quot;Item01&quot;, &quot;Item02&quot;, &quot;Item03&quot;])

    productRequest = SKProductsRequest(productIdentifiers: productIdentifiers)
    productRequest.delegate = self

    productRequest.start()
}

----------

extension IAPManager: SKProductsRequestDelegate {
    func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
        if !response.products.isEmpty {
            self.products = response.products
        }
    
        if response.invalidProductIdentifiers.count &gt; 0 {
            print(&quot;invalidProductionIdentifiers: \(response.invalidProductIdentifiers)&quot;)
        }
    }
}</code></pre></div>



<p class="wp-block-paragraph">取得商品使用 StoreKit 中的 SKProductsRequest，initial 的時候把ProductID 帶入，有一點要留意一下，因為是使用Set 所以取得商品後需要自己去做排序喔</p>



<pre class="wp-block-preformatted">productRequest.start()</pre>



<p class="wp-block-paragraph">呼叫start() 之後就可以從SKProductsRequestDelegate中的productsRequest裡取得商品，invalidProductIdentifiers 裡的Product ID 是在App Store 找不到相對應的商品</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">購買商品</h2>



<p class="wp-block-paragraph">從App Store上取得的商品物件是 SKProduct ，在購買的時候也要使用SKProduct 進行</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-swift" data-lang="Swift"><code>func buyProduct(product: SKProduct) {
    let payment = SKPayment(product: product)
    SKPaymentQueue.default().add(payment)
}</code></pre></div>



<p class="wp-block-paragraph">購買商品的時候需要建立 SKPayment 物件，並把要購買的SKProduct 放進去，接著就是將它放入 SKPaymentQueue裡就行了</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">交易狀態處理</h2>



<p class="wp-block-paragraph">要得知交易的狀態，需要成為 SKPaymentQueue的observer</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-swift" data-lang="Swift"><code>func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -&gt; Bool {
  ...
  SKPaymentQueue.default().add(IAPManager.shared)
  ...
  return true
}</code></pre></div>



<p class="wp-block-paragraph">之後就可以在 <strong>func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) </strong>中取得交易狀態</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-swift" data-lang="Swift"><code>extension IAPManager: SKPaymentTransactionObserver {
    func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
        for transaction in transactions {
            switch transaction.transactionState {
            
            case .purchasing:
                print(&quot;[IAPurchase] purchasing&quot;)
            case .purchased:
                print(&quot;[IAPurchase] purchased&quot;)
                SKPaymentQueue.default().finishTransaction(transaction)
            case .failed:
                print(&quot;[IAPurchase] failed error \(String(describing: transaction.error?.localizedDescription))&quot;)
                SKPaymentQueue.default().finishTransaction(transaction)
            case .restored:
                print(&quot;[IAPurchase] restored&quot;)
                SKPaymentQueue.default().finishTransaction(transaction)
            case .deferred:
                print(&quot;[IAPurchase] deferred&quot;)
            @unknown default:
                print(&quot;[IAPurchase] Unexpected transaction state \(transaction.transactionState)&quot;)
            }
        }
    }
}</code></pre></div>



<p class="wp-block-paragraph">交易狀態共有五種</p>



<p class="wp-block-paragraph"><strong>.purchasing</strong>，正在交易中</p>



<p class="wp-block-paragraph"><strong>.purchased</strong>，交易成功，記得寫上 finishTransaction 結束交易 ，不然下次開啟App 的時候還會再收到一次</p>



<p class="wp-block-paragraph"><strong>.failed</strong>，交易失敗，就看是否有需要處理交易失敗的情況，記得也要寫上 finishTransaction 結束交易</p>



<p class="wp-block-paragraph"><strong>.restored</strong>，若有實作Restore 功能，Restored 就是完成後的狀態，一樣記得要finishTransaction</p>



<p class="wp-block-paragraph"><strong>.deferred</strong>，交易正在 Queue 正等待被處理，基本上和&nbsp;.purchasing 差不多是過渡情況</p>



<p class="wp-block-paragraph"></p>



<p class="wp-block-paragraph">就看是否要針對其狀態進行處理，一般&nbsp;.purchasing 與&nbsp;.deferred 不需要做什麼特別處理，不過UI上需要顯示 Indicator 讓使用者知道現在正在處理中</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">取得收據</h2>



<p class="wp-block-paragraph">當收到交易成功的狀態時，就可以在本機上找到收據，一般流程需要就收據丟回給Server 去做後續驗証處理</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-swift" data-lang="Swift"><code>func fetchReceipt() -&gt; String {
        if let appStoreReceiptURL = Bundle.main.appStoreReceiptURL,
           FileManager.default.fileExists(atPath: appStoreReceiptURL.path) {
            do {
                let receiptData = try Data(contentsOf: appStoreReceiptURL, options: .alwaysMapped)
                let receiptString = receiptData.base64EncodedString(options: [])
                
                return receiptString
            } catch {
                print(&quot;[IAPurchase] Couldn&#39;t read receipt data with error: &quot; + error.localizedDescription)
                return &quot;&quot;
            }
        }
        return &quot;&quot;
}</code></pre></div>



<p class="wp-block-paragraph">需要使用 <strong>FileManager</strong> 去取得收據，再把它轉成Base64的字串，因為若要拿這個收據到Apple 上去驗証的話，就需要把收據轉成加密過的 Base64字串，若想知道如何驗証收據可以參考官方文件</p>



<p class="wp-block-paragraph"><a href="https://developer.apple.com/documentation/storekit/original_api_for_in-app_purchase/validating_receipts_with_the_app_store" target="_blank" rel="noopener"><strong>Apple Developer Documentation</strong><br><em>Validating receipts with the app store</em>developer.apple.com</a><a href="https://developer.apple.com/documentation/storekit/original_api_for_in-app_purchase/validating_receipts_with_the_app_store" target="_blank" rel="noopener"></a></p>



<p class="wp-block-paragraph">大多數的情況下都是由Server 去進行收據驗証工作，除非是訂閱型商品，不然App 可以利用交易狀態來得知是否購買成功。</p>



<p class="wp-block-paragraph">以上是就基本的IAP 實作，基本上官方Demo Code 都寫的很清楚，只要了解官方程式實作起來就不會太難。</p>



<p class="wp-block-paragraph">那最後就祝大家寫Code 愉快!!</p>



<p class="wp-block-paragraph"></p>
]]></content:encoded>
					
					<wfw:commentRss>https://doeverythingiwant.com/implement-storekit/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>
