內(nèi)購流程:
用戶先拿到購買產(chǎn)品的單子举塔,
拿著單子去蘋果那里交錢输玷,交完錢讓蘋果在單子上蓋個(gè)章
3.拿著蓋了章的單子傳給自己的服務(wù)器來驗(yàn)證是否真的支付成功,服務(wù)器是跟蘋果驗(yàn)證(我們客戶端也是可以跟蘋果驗(yàn)證的车柠,只是這樣安全性不高)
4.根據(jù)服務(wù)器返回的信息做具體的處理
先上代碼干貨
- 設(shè)置這個(gè)監(jiān)聽對(duì)象立砸,會(huì)在該界面時(shí)時(shí)監(jiān)測(cè)支付的狀態(tài)變化
-(void)viewWillAppear:(BOOL)animated{
[super viewWillAppear:animated];
// 添加觀察者
[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
}
-(void)viewWillDisappear:(BOOL)animated{
[super viewWillDisappear:animated];
// 移除觀察者
[[SKPaymentQueue defaultQueue] removeTransactionObserver:self];
}
- 點(diǎn)擊某個(gè)商品洞坑,開始購買流程
#pragma Mark -- 內(nèi)購功能模塊
-(void)buyYuBiWithId:(NSString *)ProductID{
if([SKPaymentQueue canMakePayments]){
// productID就是你在創(chuàng)建購買項(xiàng)目時(shí)所填寫的產(chǎn)品ID
self.baseTableView.userInteractionEnabled = NO;
selectProductID = [NSString stringWithFormat:@"%@",ProductID];
[self requestProduct];
}else{
_isBuying = NO;
self.baseTableView.userInteractionEnabled = YES;
DebugLog(@"不允許程序內(nèi)付費(fèi)");
UIAlertView *alertError = [[UIAlertView alloc] initWithTitle:@"溫馨提示"
message:@"請(qǐng)先開啟應(yīng)用內(nèi)付費(fèi)購買功能缠局。"
delegate:nil
cancelButtonTitle:@"確定"
otherButtonTitles: nil];
[alertError show];
}
}
#pragma mark 1.請(qǐng)求所有的商品ID
-(void)requestProduct{
// 1.拿到所有可賣商品的ID數(shù)組
NSSet *sets = [[NSSet alloc]initWithArray:_productIdArray];
// 2.向蘋果發(fā)送請(qǐng)求呀页,請(qǐng)求所有可買的商品
// 2.1.創(chuàng)建請(qǐng)求對(duì)象
SKProductsRequest *sKProductsRequest = [[SKProductsRequest alloc]initWithProductIdentifiers:sets];
// 2.2.設(shè)置代理(在代理方法里面獲取所有的可賣的商品)
sKProductsRequest.delegate = self;
// 2.3.開始請(qǐng)求
[sKProductsRequest start];
}
#pragma mark 2.蘋果那邊的內(nèi)購監(jiān)聽
-(void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response{
NSArray *product = response.products;
if([product count] == 0){
NSLog(@"沒有商品");
return;
}
for (SKProduct *sKProduct in product) {
NSLog(@"pro info");
NSLog(@"SKProduct 描述信息:%@", sKProduct.description);
NSLog(@"localizedTitle 產(chǎn)品標(biāo)題:%@", sKProduct.localizedTitle);
NSLog(@"localizedDescription 產(chǎn)品描述信息:%@",sKProduct.localizedDescription);
NSLog(@"price 價(jià)格:%@",sKProduct.price);
NSLog(@"productIdentifier Product id:%@",sKProduct.productIdentifier);
if([sKProduct.productIdentifier isEqualToString:selectProductID]){
[self buyProduct:sKProduct];
break;
}else{
//NSLog(@"沒有這個(gè)商品");
}
}
}
#pragma mark 內(nèi)購的代碼調(diào)用
-(void)buyProduct:(SKProduct *)product{
// 1.創(chuàng)建票據(jù)
SKPayment *skpayment = [SKPayment paymentWithProduct:product];
// 2.將票據(jù)加入到交易隊(duì)列
[[SKPaymentQueue defaultQueue] addPayment:skpayment];
}
- 下面這個(gè)方法就功能很多了
- 如果內(nèi)購流程中的蘋果支付完成妈拌,但是由于某些原因,沒有結(jié)束該交易,蘋果是會(huì)在頁面出現(xiàn)的時(shí)候尘分,回調(diào)這個(gè)交易的票據(jù)數(shù)據(jù)的猜惋,所以要有一個(gè)變量,標(biāo)志SKPaymentTransactionStatePurchased是充值的時(shí)候培愁,還是頁面啟動(dòng)的時(shí)候回調(diào)的_isBuying
#pragma mark 4.實(shí)現(xiàn)觀察者監(jiān)聽付錢的代理方法,只要交易發(fā)生變化就會(huì)走下面的方法
-(void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions{
/*
SKPaymentTransactionStatePurchasing, 正在購買
SKPaymentTransactionStatePurchased, 已經(jīng)購買
SKPaymentTransactionStateFailed, 購買失敗
SKPaymentTransactionStateRestored, 回復(fù)購買中
SKPaymentTransactionStateDeferred 交易還在隊(duì)列里面著摔,但最終狀態(tài)還沒有決定
*/
for (SKPaymentTransaction *transaction in transactions) {
switch (transaction.transactionState) {
case SKPaymentTransactionStatePurchasing:{
NSLog(@"正在購買");
//存儲(chǔ)購買的人ID
[[NSUserDefaults standardUserDefaults] setObject:[UserModel uid] forKey:@"applePayMan"];
[[NSUserDefaults standardUserDefaults] synchronize];
}break;
case SKPaymentTransactionStatePurchased:{
//[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
if (_isBuying) {//主動(dòng)發(fā)起的回調(diào)
//存儲(chǔ)支付成功的單子
[[StroeObserver shareStoreObserver] saveApplePayHisTransactionId:transaction.transactionIdentifier andState:@"0" isUpdate:NO];
[self buyAppleStoreProductSucceedWithPaymentTransactionp:transaction];
}else{//這是啟動(dòng)的回調(diào)
if (_dbArr.count>0) {//數(shù)據(jù)庫有數(shù)據(jù),判斷是否有記錄
//[SDIndicator showInfoWithMessage:@"數(shù)據(jù)庫有數(shù)據(jù)"];
for (NSDictionary *dic in _dbArr) {
if ([[dic objectForKey:@"transactionIdentifier"] isEqualToString:transaction.transactionIdentifier]) {//這是數(shù)據(jù)庫中的單子
if ([[dic objectForKey:@"state"] isEqualToString:@"2"]) {//單子流程已經(jīng)結(jié)束
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
//刪除數(shù)據(jù)庫中流程結(jié)束的單子
[[StroeObserver shareStoreObserver] deleteSuc:transaction.transactionIdentifier];
break;
}else{//狀態(tài)是0,1的都是支付成功了
//[SDIndicator showInfoWithMessage:@"數(shù)據(jù)庫數(shù)據(jù)充值去"];
//購買后告訴交易隊(duì)列,把這個(gè)成功的交易移除掉-- [queue finishTransaction:transaction]
[self buyAppleStoreProductSucceedWithPaymentTransactionp:transaction];
break;
}
}
}
}
if ([[[NSUserDefaults standardUserDefaults] objectForKey:@"applePayMan"] isEqualToString:[UserModel uid]]) {
//[SDIndicator showInfoWithMessage:@"數(shù)據(jù)庫無數(shù)據(jù)充值去"];
[self buyAppleStoreProductSucceedWithPaymentTransactionp:transaction];
}else{
//[SDIndicator showInfoWithMessage:@"結(jié)束交易"];
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
}
}
}break;
case SKPaymentTransactionStateFailed:{
[self hideHud];
NSLog(@"購買失敗");
_isBuying = NO;
self.baseTableView.userInteractionEnabled = YES;
// 購買失敗也要把這個(gè)交易移除掉
[queue finishTransaction:transaction];
}break;
case SKPaymentTransactionStateRestored:{
[self hideHud];
_isBuying = NO;
self.baseTableView.userInteractionEnabled = YES;
NSLog(@"恢復(fù)購買中,也叫做已經(jīng)購買");
// 恢復(fù)購買中也要把這個(gè)交易移除掉
[queue finishTransaction:transaction];
}break;
case SKPaymentTransactionStateDeferred:{
NSLog(@"交易還在隊(duì)列里面定续,但最終狀態(tài)還沒有決定");
}break;
default:
break;
}
}
}
我們可以看到谍咆,使用數(shù)據(jù)庫了原因是
- 在蘋果返回票據(jù),支付成功的時(shí)候私股,之后的流程摹察,我們走的是自己的服務(wù)器去驗(yàn)證,這樣的話倡鲸,中間流程一旦中斷供嚎,這個(gè)交易就可能沒有結(jié)束,然后呢旦签,蘋果的監(jiān)聽查坪,就會(huì)給回調(diào)回來之前支付成功但是并未結(jié)束交易的票據(jù)數(shù)組。
- 但是并不能直接調(diào)用充值接口宁炫,因?yàn)槲抑Ц冻晒Τナ铮ⅠR殺掉進(jìn)程,重新啟動(dòng)羔巢,換個(gè)賬號(hào)望忆,進(jìn)入充值界面的話,就會(huì)充值給另一個(gè)用戶竿秆,所以需要判斷這個(gè)交易的用戶屬性启摄,采用了數(shù)據(jù)庫保存,發(fā)起的訂單幽钢,當(dāng)然成功的單子就刪除了【當(dāng)然也有個(gè)問題歉备,及時(shí)極限操作,蘋果的支付票據(jù)還沒回來匪燕,我就殺掉app蕾羊,自然也就不能本地保存了,雖然沒人這么做帽驯,所以存了一下龟再,發(fā)起交易的人的uid,方便處理漏掉的單子的時(shí)候尼变,不要給另外的人存了錢利凑,自然最后剩余的情況就讓找客服吧,無解了】
- 這是條分割線
// 蘋果內(nèi)購支付成功
- (void)buyAppleStoreProductSucceedWithPaymentTransactionp:(SKPaymentTransaction *)paymentTransactionp {
NSString * productIdentifier = paymentTransactionp.payment.productIdentifier;
DebugLog(@"productIdentifier Product id:%@", productIdentifier);
NSString *transactionReceiptString= nil;
//系統(tǒng)IOS7.0以上獲取支付驗(yàn)證憑證的方式應(yīng)該改變,切驗(yàn)證返回的數(shù)據(jù)結(jié)構(gòu)也不一樣了哀澈。
NSString *version = [UIDevice currentDevice].systemVersion;
if([version intValue] >= 7.0){
// 驗(yàn)證憑據(jù)牌借,獲取到蘋果返回的交易憑據(jù)
// appStoreReceiptURL iOS7.0增加的,購買交易完成后日丹,會(huì)將憑據(jù)存放在該地址
NSURLRequest * appstoreRequest = [NSURLRequest requestWithURL:[[NSBundle mainBundle]appStoreReceiptURL]];
NSError *error = nil;
NSData * receiptData = [NSURLConnection sendSynchronousRequest:appstoreRequest returningResponse:nil error:&error];
transactionReceiptString = [receiptData base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed];
}else{
NSData * receiptData = paymentTransactionp.transactionReceipt;
// transactionReceiptString = [receiptData base64EncodedString];
transactionReceiptString = [receiptData base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed];
}
// 去驗(yàn)證是否真正的支付成功了
[self checkAppStorePayResultWithBase64String:transactionReceiptString WithPaymentTransactionp:paymentTransactionp];
}
下面方法走哺,接口調(diào)用成功的話,因?yàn)闋顟B(tài)值是0的話哲虾,原因是已經(jīng)充值成功,或者票據(jù)不對(duì)择示,這兩種情況也必須直接結(jié)束交易束凑,所以只要接口調(diào)用成功,一樣結(jié)束交易
接口調(diào)用失敗的話栅盲,再次調(diào)用汪诉,直到成功
- (void)checkAppStorePayResultWithBase64String:(NSString *)base64String WithPaymentTransactionp:(SKPaymentTransaction *)paymentTransactionp{
NSMutableDictionary *prgam = [[NSMutableDictionary alloc] init];
[prgam setValue:base64String forKey:@"receipt-data"];
__weak typeof(self)WS = self;
[Api requestWithMethod:nil withPath:API_URL_Apple_pay withParams:prgam withSuccess:^(id responseObject) {
[WS hideHud];
_isBuying = NO;
WS.baseTableView.userInteractionEnabled = YES;
//返回1成功充值,返回0谈秫,該單已經(jīng)充值過了扒寄,沒結(jié)束交易 或者票據(jù)錯(cuò)誤---都結(jié)束交易
//更新充值成功的單子
[[StroeObserver shareStoreObserver] saveApplePayHisTransactionId:paymentTransactionp.transactionIdentifier andState:@"2" isUpdate:YES];
//結(jié)束蘋果pay的交易隊(duì)列,不然會(huì)出現(xiàn)--(您已購買此 App 內(nèi)購買項(xiàng)目。此項(xiàng)目將免費(fèi)恢復(fù))提示
[[SKPaymentQueue defaultQueue] finishTransaction:paymentTransactionp];
//刪除數(shù)據(jù)庫中流程結(jié)束的單子
[[StroeObserver shareStoreObserver] deleteSuc:paymentTransactionp.transactionIdentifier];
if ([responseObject[@"status"] integerValue] == 1) {
if(_giftRequestBlock){
WS.giftRequestBlock(@"1");
}
[SDIndicator showSuccessWithMessage:@"充值成功"];
[WS getData];
}
} withError:^(NSError *error) {
_isBuying = YES;
WS.baseTableView.userInteractionEnabled = NO;
[WS checkAppStorePayResultWithBase64String:base64String WithPaymentTransactionp:paymentTransactionp];
}];
}
下面在說說拟烫,數(shù)據(jù)庫存儲(chǔ)的邏輯
#import <UIKit/UIKit.h>
#import "StroeObserver.h"
@implementation StroeObserver
+ (StroeObserver *)shareStoreObserver
{
static StroeObserver *_storeOb = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_storeOb = [[StroeObserver alloc] init];
});
return _storeOb;
}
-(void)saveApplePayHisTransactionId:(NSString *)transactionId andState:(NSString *)state isUpdate:(BOOL)isUpdate
{
dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
/*將數(shù)據(jù)存入數(shù)據(jù)庫*/
/*打開數(shù)據(jù)庫*/
DB_INIT(db);
NSString *str = DB_PATH;
DebugLog("%@",str);
DB_OPEN;
/*創(chuàng)建數(shù)據(jù)庫*/
NSString *tableName = [NSString stringWithFormat:@"ApplePayHis_%@",SWUID];
//字段有:transactionIdentifier该编、productIdentifier、uid硕淑、transactionDate(支付時(shí)間)课竣、rechargeDate(充值到賬時(shí)間)、state([未支付]「已支付」和「已充值」)
NSString *tableSql = [NSString stringWithFormat:@"create table if not exists %@(transactionIdentifier,state,uid)",tableName];
DB_CREATE(tableSql)
BOOL INSERT;
if (isUpdate) {
INSERT = [db executeUpdate:[NSString stringWithFormat:@"update %@ set state = ? where transactionIdentifier = ?",tableName],state,transactionId];
}else{
INSERT = [db executeUpdate:[NSString stringWithFormat:@"insert into %@ values(?,?,?)",tableName],transactionId,state,[UserModel uid]];
}
DB_INSERT;
DB_CLOSE;
});
}
//刪除成功的交易
-(void)deleteSuc:(NSString*)transactionIdentifier{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
DB_INIT(db);
DB_OPEN;
NSString *tableName = [NSString stringWithFormat:@"ApplePayHis_%@",SWUID];
BOOL ret = [db executeUpdate:[NSString stringWithFormat:@"delete from %@ where transactionIdentifier='%@'", tableName,transactionIdentifier]];
if (!ret) {
DebugLog(@"刪除失敗");
}else{
DebugLog(@"刪除成功");
}
DB_CLOSE;
});
}
//獲取數(shù)據(jù)庫中的支付成功置媳,充值不成功訂單
- (void)initApplePayHisDataFromDB
{
_dataArray = [[NSMutableArray alloc]init];
dispatch_queue_t queue = dispatch_queue_create("initApplePayHisDataFromDB", DISPATCH_QUEUE_SERIAL);
dispatch_sync(queue, ^{
/*讀取數(shù)據(jù)庫舊數(shù)據(jù)*/
DB_INIT(db);
DB_OPEN;
NSString *tableName = [NSString stringWithFormat:@"ApplePayHis_%@",SWUID];
FMResultSet *rs = [db executeQuery:[NSString stringWithFormat:@"select * from %@ where uid=?",tableName],SWUID];
while ([rs next]) {
NSDictionary *dic = [NSDictionary dictionaryWithObjectsAndKeys:
[rs stringForColumn:@"state"],@"state",
[rs stringForColumn:@"uid"],@"uid",
[rs stringForColumn:@"transactionIdentifier"],@"transactionIdentifier",nil];
[_dataArray addObject:dic];
}
DB_CLOSE;
});
}
@end
就看效果了于樟。。拇囊。