iOS的应用内支付In-app purchase的开发

很早之前想写一写这个,但是因为写起来太繁琐,所以就一直没有写下去的欲望。今天一天坐着发呆,刚好抽空把这个总结一下。

这里主要以单机应用为例来开发,因为如果是和服务器交互的话,也就是多了服务器的验证过程,当然都有服务器了,为什么不去做个开关去用支付宝微信呢?O(∩_∩)O哈哈~

想使用In-App Purchase(以下简称IAP)完成App内付费前,先去看看是必须要用IAP支付,还是需要用支付宝或者微信支付

在苹果制定的游戏规则中,所有在App内提供的服务需要付费时,都应当使用IAP,比如软件功能、游戏道具;所有在App外提供的服务需要付费时,都应使用其他支付方式,比如支付宝

在IAP里,可以出售:

  • 数字内容:比如游戏关卡解锁、游戏道具等;
  • 软件功能:如各种扩展features;

而比如实物,例如美团外卖的饭、公司的玩偶等都是不可以使用IAP支付,而需要去接入支付宝、微信支付这类支付服务。

当然如果你非要想去用IAP去兑换实物,那可以先用IAP去兑换虚拟货币,比如QQ币,然后再用QQ币去购买。

顺便说一下如果你想用线下自己的兑换码去兑换购买的那些东西,这如果被审核发现会被拒的。

一、准备工作

首先去iTunes Connect,点击Agreements, Tax, and Banking,填写Contact Info, Bank Info, Tax Info。如果填过就不用再填了。

屏幕快照 2018-08-15 下午3.45.34.png

然后去对应的app详情,选择功能标签,然后添加需要的支付类型。

屏幕快照 2018-08-15 下午3.47.57.png

每个类型区别说明的很清楚。

屏幕快照 2018-08-15 下午2.52.11.png

  • 消耗品(Consumable products):比如游戏内金币等。
  • 不可消耗品(Non-consumable products):简单来说就是一次购买,终身可用(用户可随时从App Store restore)。
  • 自动更新订阅品(Auto-renewable subscriptions):和不可消耗品的不同点是有失效时间。比如一整年的付费周刊。在这种模式下,开发者定期投递内容,用户在订阅期内随时可以访问这些内容。订阅快要过期时,系统将自动更新订阅(如果用户同意)。
  • 非自动更新订阅品(Non-renewable subscriptions):一般使用场景是从用户从IAP购买后,购买信息存放在自己的开发者服务器上。失效日期/可用是由开发者服务器自行控制的,而非由App Store控制,这一点与自动更新订阅品有差异。
  • 免费订阅品(Free subscriptions):在Newsstand中放置免费订阅的一种方式。免费订阅永不过期。只能用于Newsstand-enabled apps。

最常用的就是消耗品,创建名字,id唯一,然后下面填写介绍就行了。
而单机用户如果想可以让账号换设备了恢复,而自己又不想搭建服务器,并且一次买断的就可以创建不可消耗品,如果不是买断服务,那就可以创建自动更新订阅品

二、内容托管上传In-app purchase content

托管内容仅限于针对不可消耗品,如果你不需要这个功能,可以跳过这部分。如果你想使用换账号恢复功能,自己又不想搭建服务器,那可以开启苹果的内容托管。开启后,会显示等待上传。

屏幕快照 2018-08-15 下午2.50.24.png

在项目创建新的target

屏幕快照 2018-08-15 下午3.54.51.png

类型选择In-app purchase content

屏幕快照 2018-08-15 下午3.54.58.png

创建好之后可以修改product id,修改为自己在后台创建的产品ID

屏幕快照 2018-08-15 下午3.55.14.png

然后可以选择这个target 》 archive打包之后上传到App Store,上传的时候会让你选择上传到哪个app,选择之后上传即可

屏幕快照 2018-08-15 下午3.57.52.png

上传完成之后,去对应的那个详情,在托管选项里面就会多出来一个pkg的包

屏幕快照 2018-08-15 下午2.50.17.png

三、代码使用

封装了一下支付和不可消耗品的恢复,没有服务器验证的步骤。如果是搭建的服务器的话,那最好就是去服务器验证,可以参考这个文章:In-App Purchase Walk Through

.h文件

#import <Foundation/Foundation.h>

typedef void(^paySuccess)(BOOL paySuccess);
typedef void(^restoreSuccess)(BOOL restoreSuccess);

@interface HDPayTools : NSObject

///支付对应的productID
- (void)startPayWithProductID:(NSString *)productID withCompleteHandler:(paySuccess)paySuccessHandler;

///恢复
- (void)restoreWithCompleteHandler:(restoreSuccess)restoreSuccessHandler;
@end

.m文件

#import "HDPayTools.h"
#import <StoreKit/StoreKit.h>
#import <SVProgressHUD.h>

@interface HDPayTools() <SKProductsRequestDelegate,SKPaymentTransactionObserver>
@property (copy, nonatomic) NSString *m_productID;
@property (copy, nonatomic) paySuccess paySuccessHandler;
@property (copy, nonatomic) restoreSuccess restoreSuccessHandler;
@end

@implementation HDPayTools

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

- (instancetype)init {
    self = [super init];
    if (self) {
        [[SKPaymentQueue defaultQueue]  addTransactionObserver: self];
    }
    return self;
}

- (void)startPayWithProductID:(NSString *)productID withCompleteHandler:(paySuccess)paySuccessHandler {
    //下单
    [SVProgressHUD showWithStatus:nil];
    self.paySuccessHandler = paySuccessHandler;
    self.m_productID = productID;
    NSSet *productIDs = [NSSet setWithObject:_m_productID];
    SKProductsRequest *request= [[SKProductsRequest alloc] initWithProductIdentifiers:productIDs];
    request.delegate = self;
    [request start];
}

- (void)restoreWithCompleteHandler:(restoreSuccess)restoreSuccessHandler {
    self.restoreSuccessHandler = restoreSuccessHandler;
    [SVProgressHUD showWithStatus:nil];
    [[SKPaymentQueue defaultQueue] restoreCompletedTransactions];
}

- (void)p_startPayWithProduct:(SKProduct *)product {
    SKPayment *payment = [SKPayment paymentWithProduct:product];
    [[SKPaymentQueue defaultQueue] addPayment:payment];
}

- (void)p_finishedTransaction:(SKPaymentTransaction *)transaction {
    NSLog(@"支付成功");
    if (self.paySuccessHandler) {
        self.paySuccessHandler(true);
    }
    [SVProgressHUD showSuccessWithStatus:@"购买成功"];
    [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
}

- (void)p_failedTransaction:(SKPaymentTransaction *)transaction {
    NSLog(@"支付失败");
    if (self.paySuccessHandler) {
        self.paySuccessHandler(false);
    }
    [SVProgressHUD showErrorWithStatus:@"操作失败"];
    [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
}

- (void)p_alreadyBuyWithTransaction:(SKPaymentTransaction *)transaction {
    [SVProgressHUD showSuccessWithStatus:@"恢复成功"];
    [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
}

#pragma mark -
#pragma mark - SKProductsRequestDelegate
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response {
    NSArray *myProducts = response.products;
    if (myProducts.count == 0) {
        SKMutablePayment *mPayment = [[SKMutablePayment alloc] init];
        mPayment.productIdentifier = _m_productID;
        [[SKPaymentQueue defaultQueue] addPayment:mPayment];
    } else {
        for (SKProduct *product in myProducts)
        {
            //product
            [self p_startPayWithProduct:product];
            break;
        }
    }
}

- (void)request:(SKRequest *)request didFailWithError:(NSError *)error
{
    NSLog(@"%@",error);
}

#pragma mark -
#pragma mark - SKPaymentTransactionObserver
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray<SKPaymentTransaction *> *)transactions {
    for (SKPaymentTransaction *transaction in transactions) {
        switch (transaction.transactionState) {
            case SKPaymentTransactionStatePurchasing:
            {
                NSLog(@"商品加入列表,正在购买中...");
            }
                break;
            case SKPaymentTransactionStatePurchased:// 购买完成
            {
                NSLog(@"购买完成");
                [self p_finishedTransaction:transaction];
            }
                break;
            case SKPaymentTransactionStateFailed:// 交易失败
            {
                [self p_failedTransaction:transaction];
            }
                break;
            case SKPaymentTransactionStateRestored: //已经购买过该商品
            {
                NSLog(@"已经购买过该商品");
                [self p_alreadyBuyWithTransaction:transaction];
            }
                break;
            default:
                break;
        }
    }
}

- (void)paymentQueue:(SKPaymentQueue *)queue restoreCompletedTransactionsFailedWithError:(NSError *)error {
    NSLog(@"%@",error);
    [SVProgressHUD showErrorWithStatus:error.localizedDescription];
}

- (void)paymentQueueRestoreCompletedTransactionsFinished:(SKPaymentQueue *)queue {
    NSLog(@"成功");
    if (self.restoreSuccessHandler) {
        self.restoreSuccessHandler(true);
    }
}
@end

然后可以使用沙盒账号测试。

四、提交审核上线

第一次提交付费需要苹果审核,等包提交到App Store的时候,选择需要审核的包,然后往下拉选择要提交审核的付费项目。

屏幕快照 2018-08-15 下午2.49.52.png

然后提交送审即可。

Last modification:August 15th, 2018 at 04:20 pm
如果看了这个文章可以让你少加会班,可以请我喝杯可乐
已打赏名单
微信公众号

Leave a Comment