iOS-內(nèi)購(gòu)編程

內(nèi)購(gòu)所需要的資料整理總結(jié)屁倔。
引用大佬的經(jīng)典文章:https://www.cnblogs.com/TheYouth/p/6847014.html

思維導(dǎo)圖


內(nèi)購(gòu)相關(guān)信息配置.png

重點(diǎn)總結(jié):
1.獲取內(nèi)購(gòu)列表(從App內(nèi)讀取或從自己服務(wù)器讀缺住)
2.App Store請(qǐng)求可用的內(nèi)購(gòu)列表
3.向用戶展示內(nèi)購(gòu)列表
4.用戶選擇了內(nèi)購(gòu)列表,再發(fā)個(gè)購(gòu)買請(qǐng)求,收到購(gòu)買完成的回調(diào)(購(gòu)買完成后會(huì)把錢(qián)打給申請(qǐng)內(nèi)購(gòu)的銀行卡內(nèi))
5.購(gòu)買流程結(jié)束后, 向服務(wù)器發(fā)起驗(yàn)證憑證以及支付結(jié)果的請(qǐng)求
6.自己的服務(wù)器將支付結(jié)果信息返回給前端并發(fā)放虛擬產(chǎn)品
7.服務(wù)端的工作比較簡(jiǎn)單,分4步:
-7.1.接收ios端發(fā)過(guò)來(lái)的購(gòu)買憑證供炎。
-7.2.判斷憑證是否已經(jīng)存在或驗(yàn)證過(guò),然后存儲(chǔ)該憑證疾党。
-7.3.將該憑證發(fā)送到蘋(píng)果的服務(wù)器驗(yàn)證音诫,并將驗(yàn)證結(jié)果返回給客戶端。
-7.4.如果需要雪位,修改用戶相應(yīng)的會(huì)員權(quán)限竭钝。
-7.5.考慮到網(wǎng)絡(luò)異常情況,服務(wù)器的驗(yàn)證應(yīng)該是一個(gè)可恢復(fù)的隊(duì)列,如果網(wǎng)絡(luò)失敗了香罐,應(yīng)該 進(jìn)行重試卧波。
簡(jiǎn)單來(lái)說(shuō)就是將該購(gòu)買憑證用Base64編碼,然后POST給蘋(píng)果的驗(yàn)證服務(wù)器庇茫,蘋(píng)果將驗(yàn)證結(jié)果以JSON形式返回港粱。

一、使用注意事項(xiàng)及遇到的坑
1.使用注意
-1.1. 代碼中的_currentProId所填寫(xiě)的是你的購(gòu)買項(xiàng)目的的ID旦签,這個(gè)和第二步創(chuàng)建的內(nèi)購(gòu)的productID要一致查坪,產(chǎn)品id與_currentProId一致。
-1.2. 在監(jiān)聽(tīng)購(gòu)買結(jié)果后宁炫,一定要調(diào)用[[SKPaymentQueue defaultQueue] finishTransaction:tran];來(lái)允許你從支付隊(duì)列中移除交易偿曙。
-1.3. 真機(jī)測(cè)試的時(shí)候,一定要退出原來(lái)的賬號(hào)(app store 登錄的賬號(hào)退出)羔巢,才能用沙盒測(cè)試賬號(hào)望忆。
-1.4. 請(qǐng)務(wù)必使用真機(jī)來(lái)測(cè)試,一切以真機(jī)為準(zhǔn)竿秆。
-1.5. 項(xiàng)目的Bundle identifier需要與您申請(qǐng)AppID時(shí)填寫(xiě)的bundleID一致启摄,不然會(huì)無(wú)法請(qǐng)求到商品信息。
-1.6. 沙盒環(huán)境測(cè)試appStore內(nèi)購(gòu)流程的時(shí)候袍辞,請(qǐng)使用沒(méi)越獄的設(shè)備鞋仍。
-1.7. 二次驗(yàn)證,請(qǐng)注意區(qū)分宏搅吁, 測(cè)試用沙盒驗(yàn)證,App Store審核的時(shí)候也使用的是沙盒購(gòu)買落午,所以驗(yàn)證購(gòu)買憑證的時(shí)候需要判斷返回Status Code決定是否去沙盒進(jìn)行二次驗(yàn)證谎懦,為了線上用戶的使用,驗(yàn)證的順序肯定是先驗(yàn)證正式環(huán)境溃斋,此時(shí)若返回值為21007,就需要去沙盒二次驗(yàn)證,因?yàn)榇速?gòu)買的是在沙盒進(jìn)行的当编。
-1.8.貨幣類型(Bank Account Currency) :填CNY(如果你的app在中國(guó)使用的話)箩绍。
2.獲取不到商品信息
-2.1.確定配置環(huán)節(jié)正確。
-2.2.確定是真機(jī)測(cè)試且手機(jī)沒(méi)有越獄梳侨。
-2.3.確定內(nèi)購(gòu)商品添加到了需要內(nèi)購(gòu)功能的App中蛉威。
-2.4.確定當(dāng)前運(yùn)行的App的Bundle ID和后臺(tái)配置的App的Bundle ID是一致的。
-2.5.可以嘗試先刪除舊App走哺,再重新編譯生成新的蚯嫌,避免新App未覆蓋錯(cuò)誤。
-2.6.這里要提一點(diǎn),沙盒的測(cè)試賬號(hào)和你請(qǐng)求商品信息沒(méi)有關(guān)系择示。請(qǐng)求商品信息的流程是束凑,你在后臺(tái)配置好了內(nèi)購(gòu)商品,并且將其添加到了需要集成內(nèi)購(gòu)功能的App中栅盲,然后你請(qǐng)求商品汪诉。請(qǐng)求到商品后的流程是這樣的,蘋(píng)果系統(tǒng)會(huì)自動(dòng)彈出登錄框讓你登錄賬號(hào)谈秫。然后根據(jù)提示操作進(jìn)行購(gòu)買摩瞎,這里的賬號(hào)就是你配置的沙盒測(cè)試賬號(hào)。

二孝常、為什么要使用內(nèi)購(gòu)旗们?和內(nèi)購(gòu)是什么?
1.如果你購(gòu)買的商品构灸,是在本app中使用和消耗的上渴,就一定要用內(nèi)購(gòu),否則會(huì)被拒絕上線喜颁,例如:游戲幣稠氮,在線書(shū)籍,app中使用的道具等。
本例中半开,就是直播中你用來(lái)打賞用的金幣隔披,那東西可就屬于消耗型的。
2.如果是直接購(gòu)買商城之類的快遞包郵的那些東東寂拆,那就直接調(diào)用支付寶奢米,微信啦,之類的三方支付就好了纠永,淘寶鬓长,京東都玩過(guò)哈!
比較坑的一點(diǎn)就是尝江,內(nèi)購(gòu)的話涉波,還要和蘋(píng)果3/7分成,那就可以說(shuō)炭序,充值相同的錢(qián)啤覆,相對(duì)來(lái)說(shuō),iOS是比安卓虧的惭聂!

三窗声、怎樣使用內(nèi)購(gòu)?
1.使用內(nèi)購(gòu)需要哪些資料
1張visa銀行卡彼妻,appid嫌佑,1張銀行卡與蘋(píng)果三七分打錢(qián)用
(1)協(xié)議豆茫、稅務(wù)和銀行業(yè)務(wù)
  聯(lián)系人信息:(appid賬號(hào)人)姓名,郵箱屋摇,電話號(hào)碼揩魂,地址(城市、具體街道分行寫(xiě))
  visa銀行卡信息:開(kāi)戶行炮温,開(kāi)戶行所在地址火脉,開(kāi)戶行的郵政編碼,開(kāi)戶行持有人卡號(hào)柒啤,開(kāi)戶行持有人姓名
  稅務(wù)信息:1.會(huì)問(wèn)你是不是美國(guó)居民選擇NO. 2. 有沒(méi)有在美國(guó)從事商業(yè)性活動(dòng)倦挂,選擇NO. 之后填寫(xiě)個(gè)人或組織名稱,所在國(guó)家担巩,受益方式(獨(dú)立開(kāi)發(fā)者選擇個(gè)人)方援,居住地址,郵寄地址涛癌,聲明人犯戏,頭銜
(2)內(nèi)購(gòu)產(chǎn)品id的配置 (開(kāi)發(fā)人員配置)
內(nèi)購(gòu)產(chǎn)品:如果是金幣或其它消耗品的產(chǎn)品的話用消耗性型項(xiàng)目,參考名稱(內(nèi)購(gòu)項(xiàng)目拳话,比如金幣100),產(chǎn)品id,定價(jià)信息先匪,使用內(nèi)購(gòu)的快照,顯示名稱弃衍,描述呀非。
(3)用戶職能
   測(cè)試員:添加水箱測(cè)試員及沙箱賬號(hào),水箱測(cè)試賬號(hào)不能是正常使用的appid賬號(hào)镜盯,直接使用一個(gè)沒(méi)有注冊(cè)過(guò)的郵箱賬號(hào)即可岸裙。
姓名,測(cè)試賬號(hào)密碼形耗,appstore地區(qū)(必須填對(duì))哥桥。

四、操作流程圖解與代碼

創(chuàng)建app后填寫(xiě)用戶信息.png

進(jìn)入蘋(píng)果開(kāi)發(fā)者中心選擇App Store Connect.png

功能簡(jiǎn)介 :
1.我的App主要用于管理自己的App應(yīng)用激涤,例如編輯資料,上架判呕,下架等倦踢。
2.銷售和趨勢(shì)主要是來(lái)查看App在各個(gè)平臺(tái)的下載量,收入等方面數(shù)據(jù),里面有曲線圖等圖文結(jié)合的方式給我們參考侠草。
3.付款和財(cái)務(wù)報(bào)告顯示的是你的收入以及付款等相關(guān)信息辱挥。
4.iAd主要是跟廣告有關(guān),開(kāi)發(fā)者可以登錄到Workbench边涕,通過(guò)iAd對(duì)應(yīng)用的廣告進(jìn)行控制晤碘。
5.用戶和職能用于生成相應(yīng)賬號(hào)褂微,例如蘋(píng)果沙盒測(cè)試賬號(hào)。
6.協(xié)議园爷,稅務(wù)和銀行業(yè)務(wù)則是你銀行相關(guān)賬戶的信息設(shè)置宠蚂。

流程
1.注冊(cè)app,填寫(xiě)協(xié)議、稅務(wù)和銀行業(yè)務(wù)
注冊(cè)app,需要設(shè)置Bundle identifier童社,此個(gè)步驟這里就不在寫(xiě)了
填寫(xiě)協(xié)議求厕、稅務(wù)和銀行業(yè)務(wù)

選擇協(xié)議稅務(wù)和銀行業(yè)務(wù).png

選擇申請(qǐng)合同類型
頁(yè)面內(nèi)容:
Request Contracts(申請(qǐng)合同)
Contracts In Effect(已生效合同)。
合同類型:
iOS Free Application(免費(fèi)應(yīng)用合同)
iOS Paid Application(付費(fèi)應(yīng)用合同)
iAd App NetNetwork(廣告合同)

付費(fèi)或者內(nèi)購(gòu)的App必須同意這個(gè)協(xié)議.png

選擇查看并同意.png
  1. 設(shè)置協(xié)議稅務(wù)扰楼、銀行卡信息
    當(dāng)我們點(diǎn)擊申請(qǐng)iOS Paid Application合同后呀癣,該合同的狀態(tài)會(huì)變成如下的樣子,
沒(méi)有填寫(xiě)稅務(wù)和銀行業(yè)務(wù).png

選擇設(shè)置稅務(wù)和銀行信息.png

2.1設(shè)置聯(lián)系人信息
如果你沒(méi)有添加過(guò)聯(lián)系人弦赖,你需要通過(guò)Add New Contact按鈕來(lái)添加一個(gè)新的聯(lián)系人项栏。然后指定聯(lián)系人的職務(wù),職務(wù)如下:
Senior Management:高管
Financial:財(cái)務(wù)
Technical:技術(shù)支持
Legal:法務(wù)
Marketing:市場(chǎng)推廣
如果你是獨(dú)立開(kāi)發(fā)者蹬竖,可以全部填你自己一個(gè)人沼沈。

需要填寫(xiě)的信息.png

銀行卡信息

添加銀行賬戶.png

稅務(wù)報(bào)表

除了第一個(gè)美國(guó)其他都可以不選,仔細(xì)閱讀按照公司需要選擇.png

選擇U.S Tax Forms案腺,選擇后會(huì)問(wèn)你兩個(gè)問(wèn)題庆冕,
第一個(gè)問(wèn)題如下:詢問(wèn)你是否是美國(guó)居民,有沒(méi)有美國(guó)伙伴關(guān)系或者美國(guó)公司劈榨,如果沒(méi)有直接選擇No访递。
接下來(lái)第二個(gè)問(wèn)題如下:詢問(wèn)你有沒(méi)有在美國(guó)的商業(yè)性活動(dòng),沒(méi)有也直接選No同辣。
然后填寫(xiě)保稅表

選擇否并提交.png

也是選擇否并提交.png

這個(gè)表單填寫(xiě)后無(wú)法更改.png

3.配置內(nèi)購(gòu)產(chǎn)品ID
完成以上操作拷姿,并且蘋(píng)果審核完畢之后,就可以配置內(nèi)購(gòu)產(chǎn)品了旱函。
登錄 iTunesConnect -->我的App 模塊找到需要內(nèi)購(gòu)的App响巢,最后找到頁(yè)面如下:

選擇功能.png

選擇app內(nèi)購(gòu)買項(xiàng)目,點(diǎn)擊+號(hào).png

選擇類型棒妨,以后增加類型需要重新審核的.png

填寫(xiě)虛擬商品信息.png

商品介紹.png

填寫(xiě)沙箱測(cè)試員和添加內(nèi)購(gòu)產(chǎn)品注意事項(xiàng)
1踪古、郵箱必須是沒(méi)有注冊(cè)或者說(shuō)關(guān)聯(lián)過(guò)appstore的郵箱。
2券腔、密碼必須有一個(gè)是大寫(xiě)字母有一個(gè)是小寫(xiě)字母(蘋(píng)果規(guī)定的,理解)伏穆。
3、內(nèi)購(gòu)屏幕截圖規(guī)格必須是312*290纷纫,且最低分辨率是72ppi枕扫。
4、內(nèi)購(gòu)的價(jià)格是蘋(píng)果規(guī)定的不能自定義(坑啊)辱魁。

4.增加內(nèi)購(gòu)測(cè)試賬號(hào)
4.1 內(nèi)購(gòu)測(cè)試之前準(zhǔn)備
4.1.1烟瞧、什么是內(nèi)購(gòu)測(cè)試賬號(hào)(what)及為什么使用內(nèi)購(gòu)測(cè)試賬號(hào)(why)?
iOS應(yīng)用里面用到了蘋(píng)果應(yīng)用內(nèi)付費(fèi)(IAP)功能诗鸭,在項(xiàng)目上線前一定要進(jìn)行功能測(cè)試。測(cè)試肯定是需要的参滴,何況這個(gè)跟money有關(guān)强岸。。卵洗。開(kāi)發(fā)完成了之后请唱,如何進(jìn)行測(cè)試呢?難道我測(cè)試個(gè)內(nèi)購(gòu)功能要自己掏錢(qián)过蹂?就算是也是公司掏錢(qián)十绑,但是蘋(píng)果要吃掉3成的啊,想想如果是99刀的商品酷勺,點(diǎn)下購(gòu)買的時(shí)候心里都有點(diǎn)發(fā)慌本橙。。脆诉。
蘋(píng)果當(dāng)然沒(méi)這么坑了甚亭,測(cè)試內(nèi)購(gòu),蘋(píng)果提供了沙盒賬號(hào)(也叫沙箱賬號(hào))的方式击胜。這個(gè)沙箱賬號(hào)其實(shí)是虛擬的AppleID亏狰,在開(kāi)發(fā)者賬號(hào)后臺(tái)的iTune Connect上配置了之后就能使用沙盒賬號(hào)測(cè)試內(nèi)購(gòu),有了沙盒賬號(hào)偶摔,就能體驗(yàn)一把土豪的感覺(jué)了暇唾,游戲鉆石什么的隨便充,反正不用我的錢(qián)辰斋。
注意:你可以把沙盒賬號(hào)看做是一個(gè)虛擬的AppleID策州,這個(gè)AppleID只有進(jìn)行內(nèi)購(gòu)測(cè)試的功能。重要宫仗,重要够挂,重要,這個(gè)虛擬的賬號(hào)只能在自己的測(cè)試號(hào)中使用藕夫,如果在其它地方如appstore使用的話會(huì)提示賬號(hào)無(wú)效之類的話孽糖。

4.1.2、如何使用內(nèi)購(gòu)測(cè)試賬號(hào)(how)毅贮?
4.1.2.1作用內(nèi)購(gòu)賬號(hào)的前提
1)內(nèi)購(gòu)的商品ID梭姓,價(jià)格等相關(guān)信息已經(jīng)錄入到開(kāi)發(fā)者后臺(tái)了(不然那你買什么)
2)開(kāi)發(fā)者后臺(tái)已經(jīng)創(chuàng)建好沙盒測(cè)試賬號(hào)了(下面我們會(huì)將如何創(chuàng)建)
3)你要有一部真機(jī)(iPhone或iPad都行,別用模擬器就好嫩码。而且不能是越獄機(jī))
4)bundleID別搞錯(cuò)了,開(kāi)發(fā)者賬號(hào)罪既、證書(shū)铸题、bundleID要一致
5)如果你是第一次在這個(gè)開(kāi)發(fā)者賬號(hào)上集成內(nèi)購(gòu)功能铡恕,
請(qǐng)先將iTune Connect上的稅務(wù)協(xié)議都填寫(xiě)好,否則內(nèi)購(gòu)時(shí)會(huì)發(fā)現(xiàn)商品ID無(wú)效丢间。
重要探熔,如果不添加稅務(wù)協(xié)議會(huì)報(bào)錯(cuò),找不到商品烘挫。

點(diǎn)擊用戶和訪問(wèn).png

點(diǎn)擊測(cè)試員.png

點(diǎn)擊加號(hào)添加測(cè)試員诀艰,一定不能是appleID的郵箱.png

重要:郵箱一定是平時(shí)不用的,不能是appleID的郵箱.png

5.代碼及業(yè)務(wù)邏輯
業(yè)務(wù)邏輯
5.1.獲取內(nèi)購(gòu)列表(從App內(nèi)讀取或從自己服務(wù)器讀纫)
5.2.App Store請(qǐng)求可用的內(nèi)購(gòu)列表
5.3.向用戶展示內(nèi)購(gòu)列表
5.4.用戶選擇了內(nèi)購(gòu)列表其垄,再發(fā)個(gè)購(gòu)買請(qǐng)求,收到購(gòu)買完成的回調(diào)(購(gòu)買完成后會(huì)把錢(qián)打給申請(qǐng)內(nèi)購(gòu)的銀行卡內(nèi))
5.5.購(gòu)買流程結(jié)束后, 向服務(wù)器發(fā)起驗(yàn)證憑證以及支付結(jié)果的請(qǐng)求
5.6.自己的服務(wù)器將支付結(jié)果信息返回給前端并發(fā)放虛擬產(chǎn)品
服務(wù)端的工作比較簡(jiǎn)單卤橄,分4步:
5.7.接收ios端發(fā)過(guò)來(lái)的購(gòu)買憑證绿满。
5.8.判斷憑證是否已經(jīng)存在或驗(yàn)證過(guò),然后存儲(chǔ)該憑證窟扑。
5.9.將該憑證發(fā)送到蘋(píng)果的服務(wù)器驗(yàn)證喇颁,并將驗(yàn)證結(jié)果返回給客戶端。
如果需要嚎货,修改用戶相應(yīng)的會(huì)員權(quán)限橘霎。
考慮到網(wǎng)絡(luò)異常情況,服務(wù)器的驗(yàn)證應(yīng)該是一個(gè)可恢復(fù)的隊(duì)列殖属,如果網(wǎng)絡(luò)失敗了姐叁,應(yīng)該進(jìn)行重試。
簡(jiǎn)單來(lái)說(shuō)就是將該購(gòu)買憑證用Base64編碼忱辅,然后POST給蘋(píng)果的驗(yàn)證服務(wù)器七蜘,蘋(píng)果將驗(yàn)證結(jié)果以JSON形式返回。

內(nèi)購(gòu)流程.png

代碼部分

.h部分

//
//  StoreIPAManager.h
//  FamilyNames
//
//  Created by mac126 on 2019/1/12.
//  Copyright ? 2019 裴鐸. All rights reserved.
//

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

typedef enum {
    SIAPPurchSuccess = 0,       // 購(gòu)買成功
    SIAPPurchFailed = 1,        // 購(gòu)買失敗
    SIAPPurchCancle = 2,        // 取消購(gòu)買
    SIAPPurchVerFailed = 3,     // 訂單校驗(yàn)失敗
    SIAPPurchVerSuccess = 4,    // 訂單校驗(yàn)成功
    SIAPPurchNotArrow = 5,      // 不允許內(nèi)購(gòu)
}SIAPPurchType;

typedef void (^IAPCompletionHandle)(SIAPPurchType type,NSData *data);

@interface StoreIPAManager : NSObject

/**
 獲取內(nèi)購(gòu)業(yè)務(wù)對(duì)象

 @return 內(nèi)購(gòu)業(yè)務(wù)對(duì)象
 */
+ (instancetype)shareSIAPManager;

/**
 開(kāi)始內(nèi)購(gòu)

 @param purchID 蘋(píng)果后臺(tái)的虛擬商品ID
 @param handle 請(qǐng)求事務(wù)回調(diào)類型墙懂,返回的數(shù)據(jù)
 */
- (void)startPurchWithID:(NSString *)purchID completeHandle:(IAPCompletionHandle)handle;
@end

NS_ASSUME_NONNULL_END

.m部分

//
//  StoreIPAManager.m
//  FamilyNames
//
//  Created by mac126 on 2019/1/12.
//  Copyright ? 2019 裴鐸. All rights reserved.
//

/** 打印日志 
#ifdef DEBUG //調(diào)試階段

#define PDFUNC K_Log(@"%s",__func__);
#define K_Log(...); NSLog(__VA_ARGS__);

#else //發(fā)布階段

#define K_Log(...);

#endif
*/

#import "StoreIPAManager.h"
#import "NSData+Base64.h"
#import <StoreKit/StoreKit.h>

@interface StoreIPAManager ()<SKPaymentTransactionObserver,SKProductsRequestDelegate>{
    NSString            *_purchID;
    IAPCompletionHandle  _handle;
}

@end

@implementation StoreIPAManager

#pragma mark - ??life cycle
+ (instancetype)shareSIAPManager{

    static StoreIPAManager *IAPManager = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken,^{
        IAPManager = [[StoreIPAManager alloc] init];
    });
    return IAPManager;
}
- (instancetype)init{
    self = [super init];
    if (self) {
        // 購(gòu)買監(jiān)聽(tīng)寫(xiě)在程序入口,程序掛起時(shí)移除監(jiān)聽(tīng),這樣如果有未完成的訂單將會(huì)自動(dòng)執(zhí)行并回調(diào) paymentQueue:updatedTransactions:方法
        [[SKPaymentQueue defaultQueue] addTransactionObserver:self];
    }
    return self;
}

- (void)dealloc{
    [[SKPaymentQueue defaultQueue] removeTransactionObserver:self];
}


#pragma mark - ??public
- (void)startPurchWithID:(NSString *)purchID completeHandle:(IAPCompletionHandle)handle{
    if (purchID) {
        if ([SKPaymentQueue canMakePayments]) {
            // 開(kāi)始購(gòu)買服務(wù)
            _purchID = purchID;
            _handle = handle;
            NSSet *nsset = [NSSet setWithArray:@[purchID]];
            SKProductsRequest *request = [[SKProductsRequest alloc] initWithProductIdentifiers:nsset];
            request.delegate = self;
            [request start];
        }else{
            [self handleActionWithType:SIAPPurchNotArrow data:nil];
        }
    }
}
#pragma mark - ??private
- (void)handleActionWithType:(SIAPPurchType)type data:(NSData *)data{
    switch (type) {
        case SIAPPurchSuccess:
            K_Log(@"購(gòu)買成功");
            break;
        case SIAPPurchFailed:
            K_Log(@"購(gòu)買失敗");
            break;
        case SIAPPurchCancle:
            K_Log(@"用戶取消購(gòu)買");
            break;
        case SIAPPurchVerFailed:
            K_Log(@"訂單校驗(yàn)失敗");
            break;
        case SIAPPurchVerSuccess:
            K_Log(@"訂單校驗(yàn)成功");
            break;
        case SIAPPurchNotArrow:
            K_Log(@"不允許程序內(nèi)付費(fèi)");
            break;
        default:
            break;
    }
    if(_handle){
        _handle(type,data);
    }
}
#pragma mark - ??delegate
// 交易結(jié)束
- (void)completeTransaction:(SKPaymentTransaction *)transaction{
    // Your application should implement these two methods.
    NSString *productIdentifier = transaction.payment.productIdentifier;
    NSData *transactionReceiptData = [NSData dataWithContentsOfURL:[[NSBundle mainBundle] appStoreReceiptURL]];
    NSString *receipt = [transactionReceiptData base64EncodedStringWithOptions:NSDataBase64Encoding64CharacterLineLength];
    if ([productIdentifier length] > 0) {
        // 向自己的服務(wù)器驗(yàn)證購(gòu)買憑證橡卤,把receipt傳給后臺(tái)
                // 這一步盡量用NSURLSession寫(xiě),AFN等第三方容易造成21002(蘋(píng)果服務(wù)器返回的狀態(tài)碼损搬,代表數(shù)據(jù)缺失)
                // AFN等第三方在內(nèi)部對(duì)數(shù)據(jù)進(jìn)行反序列化的時(shí)候丟失數(shù)據(jù)了, 如果不用自動(dòng)反序列化可能會(huì)好一點(diǎn),
                // 自行嘗試那種都可以只要數(shù)據(jù)完整的傳給后臺(tái),后臺(tái)完整的傳給蘋(píng)果服務(wù)器就OK
                NSMutableDictionary * parameters = [NSMutableDictionary dictionary];
    parameters[@"自己后臺(tái)定義的字段"] = receipt;

    NSURL * url = [NSURL URLWithString:@"自己后臺(tái)服務(wù)器地址")];
    NSMutableURLRequest *storeRequest = [NSMutableURLRequest requestWithURL:url];
    [storeRequest setHTTPMethod:@"POST"];

    // 設(shè)置頭部參數(shù)
    //[storeRequest addValue:@"如果需要把token拼在頭部發(fā)給后臺(tái)" forHTTPHeaderField:@"token"];

    // 遍歷字典碧库,以“key=value&”的方式創(chuàng)建參數(shù)字符串。
    NSMutableString *parameterString = [[NSMutableString alloc]init];
    int pos = 0;
    for (NSString * key in parameters.allKeys) {
        // 拼接字符串
        [parameterString appendFormat:@"%@=%@", key, parameters[key]];
        if(pos< parameters.allKeys.count - 1){
            [parameterString appendString:@"&"];
        }
        pos++;
    }
    // NSString轉(zhuǎn)成NSData數(shù)據(jù)類型巧勤。
    NSData *parametersData = [parameterString dataUsingEncoding:NSUTF8StringEncoding];
    [storeRequest setHTTPBody:parametersData];
    [[[NSURLSession sharedSession] dataTaskWithRequest:storeRequest completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        if (error) {
K_Log(@"本次網(wǎng)絡(luò)請(qǐng)求請(qǐng)求失敗嵌灰,并不是購(gòu)買失敗");
        } else {
K_Log(@"請(qǐng)求成功");
        }
    }] resume];
    }

    [self verifyPurchaseWithPaymentTransaction:transaction isTestServer:NO];
}

// 交易失敗
- (void)failedTransaction:(SKPaymentTransaction *)transaction{
    if (transaction.error.code != SKErrorPaymentCancelled) {
        [self handleActionWithType:SIAPPurchFailed data:nil];
    }else{
        [self handleActionWithType:SIAPPurchCancle data:nil];
    }

    [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
}

- (void)verifyPurchaseWithPaymentTransaction:(SKPaymentTransaction *)transaction isTestServer:(BOOL)flag{
    //交易驗(yàn)證
    NSURL *recepitURL = [[NSBundle mainBundle] appStoreReceiptURL];
    NSData *receipt = [NSData dataWithContentsOfURL:recepitURL];

    if(!receipt){
        // 交易憑證為空驗(yàn)證失敗
        [self handleActionWithType:SIAPPurchVerFailed data:nil];
        return;
    }
    // 購(gòu)買成功將交易憑證發(fā)送給服務(wù)端進(jìn)行再次校驗(yàn)
    [self handleActionWithType:SIAPPurchSuccess data:receipt];

    NSError *error;
    NSDictionary *requestContents = @{
                                      @"receipt-data": [receipt base64EncodedStringWithOptions:0]
                                      };
    NSData *requestData = [NSJSONSerialization dataWithJSONObject:requestContents
                                                          options:0
                                                            error:&error];

    if (!requestData) { // 交易憑證為空驗(yàn)證失敗
        [self handleActionWithType:SIAPPurchVerFailed data:nil];
        return;
    }

    //In the test environment, use https://sandbox.itunes.apple.com/verifyReceipt
    //In the real environment, use https://buy.itunes.apple.com/verifyReceipt

    NSString *serverString = @"https://buy.itunes.apple.com/verifyReceipt";
    if (flag) {
        serverString = @"https://sandbox.itunes.apple.com/verifyReceipt";
    }
    NSURL *storeURL = [NSURL URLWithString:serverString];
    NSMutableURLRequest *storeRequest = [NSMutableURLRequest requestWithURL:storeURL];
    [storeRequest setHTTPMethod:@"POST"];
    [storeRequest setHTTPBody:requestData];

    [[[NSURLSession sharedSession] dataTaskWithRequest:storeRequest completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        if (error) {
            // 無(wú)法連接服務(wù)器,購(gòu)買校驗(yàn)失敗
            [self handleActionWithType:SIAPPurchVerFailed data:nil];
        } else {
            NSError *error;
            NSDictionary *jsonResponse = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error];
            if (!jsonResponse) {
                // 蘋(píng)果服務(wù)器校驗(yàn)數(shù)據(jù)返回為空校驗(yàn)失敗
                [self handleActionWithType:SIAPPurchVerFailed data:nil];
            }
            // 先驗(yàn)證正式服務(wù)器,如果正式服務(wù)器返回21007再去蘋(píng)果測(cè)試服務(wù)器驗(yàn)證,沙盒測(cè)試環(huán)境蘋(píng)果用的是測(cè)試服務(wù)器
            NSString *status = [NSString stringWithFormat:@"%@",jsonResponse[@"status"]];
            if (status && [status isEqualToString:@"21007"]) {
                [self verifyPurchaseWithPaymentTransaction:transaction isTestServer:YES];
            }else if(status && [status isEqualToString:@"0"]){
                [self handleActionWithType:SIAPPurchVerSuccess data:nil];
            }
            K_Log(@"----驗(yàn)證結(jié)果 %@",jsonResponse);
        }
    }] resume];
    // 驗(yàn)證成功與否都注銷交易,否則會(huì)出現(xiàn)虛假憑證信息一直驗(yàn)證不通過(guò),每次進(jìn)程序都得輸入蘋(píng)果賬號(hào)
    [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
}

#pragma mark - SKProductsRequestDelegate
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response{
    NSArray *product = response.products;
    if([product count] <= 0){
        K_Log(@"--------------沒(méi)有商品------------------");
        return;
    }

    SKProduct *p = nil;
    for(SKProduct *pro in product){
        if([pro.productIdentifier isEqualToString:_purchID]){
            p = pro;
            break;
        }
    }
    K_Log(@"productID:%@", response.invalidProductIdentifiers);
    K_Log(@"產(chǎn)品付費(fèi)數(shù)量:%lu",(unsigned long)[product count]);
    K_Log(@"%@",[p description]);
    K_Log(@"%@",[p localizedTitle]);
    K_Log(@"%@",[p localizedDescription]);
    K_Log(@"%@",[p price]);
    K_Log(@"%@",[p productIdentifier]);
    K_Log(@"發(fā)送購(gòu)買請(qǐng)求");

    SKPayment *payment = [SKPayment paymentWithProduct:p];
    [[SKPaymentQueue defaultQueue] addPayment:payment];
}

//請(qǐng)求失敗
- (void)request:(SKRequest *)request didFailWithError:(NSError *)error{
    K_Log(@"------------------錯(cuò)誤-----------------:%@", error);
}

- (void)requestDidFinish:(SKRequest *)request{
    K_Log(@"------------反饋信息結(jié)束-----------------");
}

#pragma mark - SKPaymentTransactionObserver
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray<SKPaymentTransaction *> *)transactions{
    for (SKPaymentTransaction *tran in transactions) {
        switch (tran.transactionState) {
            case SKPaymentTransactionStatePurchased:{
                [self completeTransaction:tran];
            }break;
            case SKPaymentTransactionStatePurchasing:{
                K_Log(@"商品添加進(jìn)列表");
            }break;
            case SKPaymentTransactionStateRestored:{
                K_Log(@"已經(jīng)購(gòu)買過(guò)商品");
                // 消耗型不支持恢復(fù)購(gòu)買
                [[SKPaymentQueue defaultQueue] finishTransaction:tran];
            }break;
            case SKPaymentTransactionStateFailed:{
                [self failedTransaction:tran];
            }break;
            default: break;
        }
    }
}



@end
/*注意事項(xiàng):
 1.沙盒環(huán)境測(cè)試appStore內(nèi)購(gòu)流程的時(shí)候,請(qǐng)使用沒(méi)越獄的設(shè)備颅悉。
 2.請(qǐng)務(wù)必使用真機(jī)來(lái)測(cè)試沽瞭,一切以真機(jī)為準(zhǔn)。
 3.項(xiàng)目的Bundle identifier需要與您申請(qǐng)AppID時(shí)填寫(xiě)的bundleID一致剩瓶,不然會(huì)無(wú)法請(qǐng)求到商品信息驹溃。
 4.如果是你自己的設(shè)備上已經(jīng)綁定了自己的AppleID賬號(hào)請(qǐng)先注銷掉,否則你哭爹喊娘都不知道是怎么回事城丧。
 5.訂單校驗(yàn) 蘋(píng)果審核app時(shí),仍然在沙盒環(huán)境下測(cè)試豌鹤,所以需要先進(jìn)行正式環(huán)境驗(yàn)證亡哄,如果發(fā)現(xiàn)是沙盒環(huán)境則轉(zhuǎn)到沙盒驗(yàn)證。
 識(shí)別沙盒環(huán)境訂單方法:
 1.根據(jù)字段 environment = sandbox布疙。
 2.根據(jù)驗(yàn)證接口返回的狀態(tài)碼,如果status=21007蚊惯,則表示當(dāng)前為沙盒環(huán)境。
 蘋(píng)果反饋的狀態(tài)碼:
 21000 App Store無(wú)法讀取你提供的JSON數(shù)據(jù)
 21002 訂單數(shù)據(jù)不符合格式灵临,數(shù)據(jù)缺失了
 21003 訂單無(wú)法被驗(yàn)證
 21004 你提供的共享密鑰和賬戶的共享密鑰不一致
 21005 訂單服務(wù)器當(dāng)前不可用
 21006 訂單是有效的截型,但訂閱服務(wù)已經(jīng)過(guò)期。當(dāng)收到這個(gè)信息時(shí)俱诸,解碼后的收據(jù)信息也包含在返回內(nèi)容中
 21007 訂單信息是測(cè)試用(sandbox)菠劝,但卻被發(fā)送到產(chǎn)品環(huán)境中驗(yàn)證
 21008 訂單信息是產(chǎn)品環(huán)境中使用,但卻被發(fā)送到測(cè)試環(huán)境中驗(yàn)證
 */

在需要的位置倒入業(yè)務(wù)類頭文件睁搭,調(diào)用下面的代碼

// ID:iTunesConnect 蘋(píng)果后臺(tái)配置的產(chǎn)品ID
// 這里應(yīng)該彈出指示器(作用是禁止用戶多次操作)赶诊,避免用戶多次點(diǎn)擊購(gòu)買按鈕后程序會(huì)崩潰
    [[StoreIPAManager shareSIAPManager] startPurchWithID:ID completeHandle:^(SIAPPurchType type,NSData *data) {
        //請(qǐng)求事務(wù)回調(diào)類型,返回的數(shù)據(jù)园骆, 
                //結(jié)束指示器舔痪。
        K_Log(@"SIAPPurchType :%d", type);
        K_Log(@"購(gòu)買結(jié)束返回:%@", data);
    }];
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市锌唾,隨后出現(xiàn)的幾起案子锄码,更是在濱河造成了極大的恐慌,老刑警劉巖晌涕,帶你破解...
    沈念sama閱讀 210,835評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件滋捶,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡余黎,警方通過(guò)查閱死者的電腦和手機(jī)重窟,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,900評(píng)論 2 383
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)惧财,“玉大人巡扇,你說(shuō)我怎么就攤上這事】逯裕” “怎么了厅翔?”我有些...
    開(kāi)封第一講書(shū)人閱讀 156,481評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)搀突。 經(jīng)常有香客問(wèn)我刀闷,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,303評(píng)論 1 282
  • 正文 為了忘掉前任涩赢,我火速辦了婚禮戈次,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘筒扒。我一直安慰自己,他們只是感情好绊寻,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,375評(píng)論 5 384
  • 文/花漫 我一把揭開(kāi)白布花墩。 她就那樣靜靜地躺著,像睡著了一般澄步。 火紅的嫁衣襯著肌膚如雪冰蘑。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,729評(píng)論 1 289
  • 那天村缸,我揣著相機(jī)與錄音祠肥,去河邊找鬼。 笑死梯皿,一個(gè)胖子當(dāng)著我的面吹牛仇箱,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播东羹,決...
    沈念sama閱讀 38,877評(píng)論 3 404
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼剂桥,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了属提?” 一聲冷哼從身側(cè)響起权逗,我...
    開(kāi)封第一講書(shū)人閱讀 37,633評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎冤议,沒(méi)想到半個(gè)月后斟薇,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,088評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡恕酸,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,443評(píng)論 2 326
  • 正文 我和宋清朗相戀三年堪滨,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片尸疆。...
    茶點(diǎn)故事閱讀 38,563評(píng)論 1 339
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡椿猎,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出寿弱,到底是詐尸還是另有隱情犯眠,我是刑警寧澤,帶...
    沈念sama閱讀 34,251評(píng)論 4 328
  • 正文 年R本政府宣布症革,位于F島的核電站筐咧,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜量蕊,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,827評(píng)論 3 312
  • 文/蒙蒙 一铺罢、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧残炮,春花似錦韭赘、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,712評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至苞冯,卻和暖如春袖牙,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背舅锄。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,943評(píng)論 1 264
  • 我被黑心中介騙來(lái)泰國(guó)打工鞭达, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人皇忿。 一個(gè)月前我還...
    沈念sama閱讀 46,240評(píng)論 2 360
  • 正文 我出身青樓畴蹭,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親禁添。 傳聞我的和親對(duì)象是個(gè)殘疾皇子撮胧,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,435評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容

  • 一般來(lái)說(shuō),開(kāi)發(fā)人員剛接觸內(nèi)購(gòu)老翘,都會(huì)遇到流程不清楚芹啥、千頭萬(wàn)緒。如何一次性搞定內(nèi)購(gòu)問(wèn)題铺峭? 一墓怀、掌握內(nèi)購(gòu)流程: 1、完成...
    little_ma閱讀 53,488評(píng)論 80 145
  • 一.總說(shuō)內(nèi)購(gòu)的內(nèi)容 協(xié)議卫键、稅務(wù)和銀行業(yè)務(wù) 信息填寫(xiě) 內(nèi)購(gòu)商品的添加 添加沙盒測(cè)試賬號(hào) 內(nèi)購(gòu)代碼的具體實(shí)現(xiàn) 內(nèi)購(gòu)的注...
    默默_David閱讀 3,643評(píng)論 0 6
  • iOS應(yīng)用如果涉及到支付功能傀履,分為兩類:第三方支付和蘋(píng)果內(nèi)購(gòu)。那么什么情況下選擇使用第三方支付莉炉,又在什么情況下選擇...
    ZfRee閱讀 38,819評(píng)論 36 66
  • 自己開(kāi)發(fā)的視頻直播項(xiàng)目钓账,牽涉到充值金幣,用到了蘋(píng)果公司的內(nèi)購(gòu)絮宁,趴坑了兩天梆暮,這里總結(jié)下實(shí)現(xiàn)蘋(píng)果內(nèi)購(gòu)。 一. 創(chuàng)建測(cè)試...
    Leo丶Dicaprio閱讀 3,364評(píng)論 8 7
  • 最近在做內(nèi)購(gòu)項(xiàng)目SDK绍昂,現(xiàn)將集成過(guò)程和集成內(nèi)購(gòu)過(guò)程中遇到的問(wèn)題記載下來(lái): 項(xiàng)目中使用到了中間貨幣(金幣)的形式來(lái)進(jìn)...
    yahoouchen閱讀 3,860評(píng)論 0 10