iOS 内购

本文详细介绍了App内购的概念、应用场景及开发流程。解释了何时必须使用内购支付,并探讨了苹果平台内购的具体实现,包括如何集成StoreKit框架、处理支付交易及验证商品列表。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

所谓内购就是在App内购买商品,如在游戏App中的购买道具、皮肤等;在电商App中的购买衣食住行的各种商品,如淘宝、京东。内购是指的在App内购买商品所使用的一种支付方式。

购买商品如何支付?

  1. 第三方支付: 支付宝等
  2. 快捷/网银付款:各种银行储蓄卡、信用卡
  3. 苹果的内购

内购简介

什么时候使用内购?

对于App中销售的商品(如道具、皮肤、金币、会员、关卡等)只能在App内使用,就必须使用内购支付方式,这种情况下苹果强制使用内购方式,否则审核拒绝;

对于在App中购买的商品不在App内使用的如淘宝、京东等购买的商品实是在真实世界中使用的,这种情况苹果不强制必须使用内购方式,你想使用什么方式都可以;

内购说明:
  1. 内购苹果要3/7分成
    需要特别注意的是使用内购方式支付,苹果是要收3/7分成。比如用户某买了一个道具10元RMB,苹果拿3元RMB,App开发商拿7元RMB。苹果这是什么都不干还想要钱啊!
  2. 用户购买商品需要绑定银行卡(过程麻烦,用户在绑定过程中很有可能会放弃)
  3. 商品价格不能自定义,只有固定的价格等级

通常我们除了集成内购方式还会集成其他支付方式,通过服务器接口做一个开关配置需要显示的支付方式,当苹果审核时服务器端接口只配置内购一种支付方式,当苹果审核通过了,服务器端将内购方式隐藏,将其他种类的支付方式显示,这样苹果你甭想收钱了!


内购开发流程

  1. 协议、税务和银行业务

    1. 请求协议
    2. 添加新的法人实体
    3. Set Up(设置) 联系人(Contact Info)、税务(Tax Info)、银行信息(Bank Info)
      这里写图片描述

    这里写图片描述

    这里写图片描述

    这里写图片描述

    这里写图片描述

    这里写图片描述

    这里写图片描述

    这里写图片描述

    这里写图片描述

    这里写图片描述

    这里写图片描述

    这里写图片描述

    这里写图片描述

    这里写图片描述

    这里写图片描述

  2. 配置允许购买的项目(消耗型项目和非消耗型项目)
    消耗型项目:购买一次,只能使用一次,用过就没有了,例如 子弹、手榴弹
    非消耗型项目:购买一次,可以一直使用,例如 狙击枪,枪买一次就可以了,但枪上的子弹射击一次就消耗了一枚
    我的App—>功能—>App 内购买项目
    SKProduct: 产品ID(字符串:一般是Bundle ID + 商品ID,例如com.domain.appname.zidan)、商品名称、商品描述、商品图片、价格等级、

  3. 配置测试账号
    用户和职能—>沙箱技术测试员
  4. 代码集成
    导入StoreKit.framework库:从苹果服务器请求可以销售的商品列表
    1. 从自己的App服务器中获取需要销售的商品列表
    2. 将这些商品拿到苹果服务器进行验证,获取允许真正销售的项目,展示在UITableView上

Request Amendments(请求协议)

示例代码


XXProduct

#import <Foundation/Foundation.h>

@interface XXProduct : NSObject

@property(nonatomic, readonly, assign) NSInteger *ID;
@property(nonatomic, readonly) NSString *identifier;
@property(nonatomic, readonly) NSString *title;
@property(nonatomic, readonly) NSString *image;
@property(nonatomic, readonly) NSString *descriptionInfo;
@property(nonatomic, readonly) NSDecimalNumber *price;
@property(nonatomic, readonly) NSInteger quantity;

@property (assign, nonatomic) NSInteger type;  // 商品类型: 消耗型  非消耗型
@property (assign, nonatomic) BOOL isBuy;      // 是否已经购买

@end
//---------------------------------------------------------------------
#import "XXProduct.h"

@implementation XXProduct

@end

ViewController

#import <UIKit/UIKit.h>
@interface ViewController : UITableViewController

@end

//-----------------------------------------------------------------
#import "ViewController.h"
#import "XXProduct.h"

#import <AFNetworking.h>
#import <MJExtension.h>
#import <UIImageView+WebCache.h>
#import <StoreKit/StoreKit.h>


@interface ViewController () <SKProductsRequestDelegate, SKPaymentTransactionObserver>

@property(strong, nonatomic) NSArray<XXProduct *> *productList;
@property(strong, nonatomic) NSArray<SKProduct *> *products;
@property(strong, nonatomic) NSArray<XXProduct *> *dataSource;

@end

@implementation ViewController

static NSString * const ID = @"ViewControllerProductCell";
- (void)viewDidLoad {
    [super viewDidLoad];

    [self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:ID];
    [self requestProductListAndValidate];

    // 恢复购买:以下两行代码执行后会执行到paymentQueue:updatedTransactions:方法中的switch--case SKPaymentTransactionStateRestored:语句
    // 作用是如果yoghurt已经购买过的非消耗型的商品就补再展示“购买”按钮了,可以用“已经购买”进行提示
    [[SKPaymentQueue defaultQueue] restoreCompletedTransactions];
    [[SKPaymentQueue defaultQueue] addTransactionObserver:self];
}


#pragma mark -
#pragma mark - SKProductsRequestDelegate
// 当请求完毕,苹果服务器返回数据时调用
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response {
    NSArray<SKProduct *> *products = response.products;   // 可以销售的商品
    self.products = products;

    NSArray<NSString *> *invalidProductIdentifiers = response.invalidProductIdentifiers;  // 无效的商品ID

    // 因SKProduct中没有商品图片,一般商品列表中要展示每个商品对应的图片的,所以UITableView的数据源最终还是要自己服务器中的商品列表数据
    // 这里将无效的商品过滤掉,剩下的都是可以允许销售的商品
    NSMutableArray *productList = [NSMutableArray array];
    for (XXProduct *product in self.productList) {
        if (![invalidProductIdentifiers containsObject:product.identifier]) {
            [productList addObject:product];
        }
    }

    self.dataSource = productList;
}


#pragma mark - Table view data source
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {

    return self.dataSource.count;
}


- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];

    XXProduct *product = self.dataSource[indexPath.row];
    [cell.imageView sd_setImageWithURL:[NSURL URLWithString:product.image]];
    cell.textLabel.text = product.title;
    cell.detailTextLabel.text = [NSString stringWithFormat:@"%@-%@", product.descriptionInfo, product.price];

    // 模拟已经购买了非消耗型商品不展示购买按钮
    if (product.isBuy) {
        cell.backgroundColor = [UIColor grayColor];
    }
    return cell;
}

// 模拟购买
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    // 1. 获取要购买的商品
    SKProduct *product = self.products[indexPath.row];

    // 2.0 判断当前支付环境能否支付
    if ([SKPaymentQueue canMakePayments]) {
        // 2.1 获取商品对应的小票
        SKPayment *payment = [SKPayment paymentWithProduct:product];

        // 2.2 拿着小票去排队付款
        [[SKPaymentQueue defaultQueue] addPayment:payment];

        // 2.3 监听交易的整个过程
        [[SKPaymentQueue defaultQueue] addTransactionObserver:self];
    } else {
        UIAlertView *alerView = [[UIAlertView alloc] initWithTitle:@"提示"
                                                           message:@"您的手机没有打开程序内付费购买"
                                                          delegate:nil
                                                 cancelButtonTitle:NSLocalizedString(@"关闭",nil) otherButtonTitles:nil];
        [alerView show];
    }

}
hello  16:09:31
#pragma mark -
#pragma mark - SKPaymentTransactionObserver
// 当交易状态发生改变的时候调用
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray<SKPaymentTransaction *> *)transactions {
    for (SKPaymentTransaction *paymentTransaction in transactions) {
        switch (paymentTransaction.transactionState) {
            case SKPaymentTransactionStateDeferred:   // 延迟购买:下单后长时间步支付

                break;
            case SKPaymentTransactionStatePurchasing: // 正在支付

                break;
            case SKPaymentTransactionStatePurchased: {// 支付成功
                // 将该交易成功记录到自己App服务器里
                XXProduct *product = [self getProductByIdentifier:paymentTransaction];
                [self addTransactionSuccessRecored:product];

                UIAlertView *alerView = [[UIAlertView alloc] initWithTitle:@""
                                                                   message:@"购买成功"
                                                                  delegate:nil
                                                         cancelButtonTitle:NSLocalizedString(@"关闭",nil)
                                                         otherButtonTitles:nil];
                [alerView show];

                // 从交易队列中移除掉该交易
                [queue finishTransaction:paymentTransaction];
            }

                break;
            case SKPaymentTransactionStateFailed: {    // 支付失败
                // 将交易失败记录在自己App的服务器中
                XXProduct *product = [self getProductByIdentifier:paymentTransaction];
                [self addTransactionFailRecored:product];

                UIAlertView *alerView = [[UIAlertView alloc] initWithTitle:@"提示" message:@"购买失败,请重新尝试购买" delegate:nil cancelButtonTitle:NSLocalizedString(@"关闭",nil) otherButtonTitles:nil];
                [alerView show];

                // 从交易队列中移除掉该交易
                [queue finishTransaction:paymentTransaction];
            }
                break;
            case SKPaymentTransactionStateRestored: { // 恢复购买
                // 对于非消耗型商品已经购买过了应该展示 “已经购买过了”, 不要展示购买按钮了
                XXProduct *product = [self getProductByIdentifier:paymentTransaction];
                product.isBuy = YES;

                [self.tableView reloadData];
                // 从交易队列中移除掉该交易
                [queue finishTransaction:paymentTransaction];
            }
                break;

        }
    }
}

// 验证商品列表
- (void)requestProductListAndValidate{
    // 1. 从App服务器中获取商品列表
    // 2. 在苹果服务器上验证这些商品,获取真正允许销售的商品
    AFHTTPSessionManager *sessionManager = [AFHTTPSessionManager manager];
    [sessionManager GET:@"http:www.xxx.com/product/list" parameters:nil progress:nil success:^(NSURLSessionDataTask * _Nonnull task, NSDictionary * _Nullable responseObject) {
        NSArray<XXProduct *> *products = [XXProduct mj_objectArrayWithKeyValuesArray:responseObject];
        NSSet<NSString *> *productIdentifiers = [products valueForKeyPath:@"identifier"];
        self.productList = products;

        SKProductsRequest *productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:productIdentifiers];
        productsRequest.delegate = self;
        [productsRequest start];

    } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {

    }];
}

- (XXProduct *)getProductByIdentifier:(SKPaymentTransaction *)paymentTransaction {
    NSString *productIdentifier = paymentTransaction.payment.productIdentifier;
    // 搜索productIdentifier对应的XXProduct
    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF == %@", productIdentifier];
    NSArray *tempArray = [self.dataSource filteredArrayUsingPredicate:predicate];
    XXProduct *product = [tempArray firstObject];

    return product;
}

// 添加交易成功记录
- (void)addTransactionSuccessRecored:(XXProduct *)product {
    NSLog(@"将交易记录记录在自己服务器中的数据库中...");
}

// 添加交易失败记录
- (void)addTransactionFailRecored:(XXProduct *)product {
    NSLog(@"将交易记录记录在自己服务器中的数据库中...");
}

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

@end

相关文章: http://www.jianshu.com/p/d9d742e82188

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

风流 少年

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值