iOS苹果内购(详细步骤)

admin 5617 2025-10-30 20:07:36

注意 apple Pay 不是苹果内购

1.第一步,先到 App Store Connect 中 的 协议部分,去填写信息,(这部分一般法务去解决)

(1)查看付费同意条款

(2)设置付费协议,添加账户,

(3)选择报税表等

(4)填写联系人

2.添加内购项目 (这个需要开发参与)

(1)添加内购项目

此处的产品ID 为唯一ID: 一般的规则: bundleID.xxx ;

(2)设置内购项目类型

消耗型和非消耗型的区别是:

消耗型:可以重复多次购买

非消耗型:只能购买一次,不会过期,例如:永久会员等;

(3).设置名称,可以自定义;

(4)按照要求设置销售范围,价格,税务类别

(5)添加App Store 本地化版本;(只要是设置语言)

(6)App Store 推广

此处推广图也要是唯一的,不能和其余的商品推广图一致;(不能包含alpha通道)

如果设计给的图不合规,可以尝试把图片用预览打开后 导出 ,取消alpha ,导出即可使用;

(7)审核信息就是,商品在App 中的页面位置截图,以及描述

(8)存储即可

此时我们已经创建了这个商品,接下来就是在项目中获取商品信息,并支付

3.在项目中的代码操作:

逻辑: 先通过后端的接口获取到商品的产品ID,然后在页面中发起购买请求,这个请求是向App Store发出的,整个过程不用与后端交互,等结果返回后,将支付凭证发给后端即可.

具体的代码,可以参考:

#import

#import

typedef enum : NSUInteger {

ApplePayManagerTypeSuccess, // 购买成功

ApplePayManagerTypeFailed, // 购买失败

ApplePayManagerTypeCancle, // 取消购买

ApplePayManagerTypeVerFailed, // 订单校验失败

ApplePayManagerTypeVerSuccess, // 订单校验成功

ApplePayManagerTypeNotArrow, // 不允许内购

} ApplePayManagerType;

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

NS_ASSUME_NONNULL_BEGIN

@interface ApplePayManager : NSObject

@property (nonatomic, strong)UIView * targetView;

+ (instancetype)shareAPManager;

//开始内购

- (void)startPurchWithDict:(NSDictionary *)purch completeHandle:(IAPCompletionHandle)handle;

#import "ApplePayManager.h"

#import "MineMemberInAppVerifyReceiptRequest.h"

@interface ApplePayManager ()

@property (nonatomic,copy) NSString * purchID;

@property (nonatomic,copy) IAPCompletionHandle handle;

@property (nonatomic,strong) NSDictionary * parmas;

@end

@implementation ApplePayManager

+(instancetype)shareAPManager{

static ApplePayManager *IAPManager = nil;

static dispatch_once_t onceToken;

dispatch_once(&onceToken,^{

IAPManager = [[ApplePayManager alloc] init];

});

return IAPManager;

}

- (instancetype)init{

self = [super init];

if (self) {

// 购买监听写在程序入口,程序挂起时移除监听,这样如果有未完成的订单将会自动执行并回调 paymentQueue:updatedTransactions:方法

[[SKPaymentQueue defaultQueue] addTransactionObserver:self];

}

return self;

}

- (void)dealloc{

[[SKPaymentQueue defaultQueue] removeTransactionObserver:self];

}

#pragma mark - public 去苹果服务器请求产品信息

-(void)startPurchWithDict:(NSDictionary *)purch completeHandle:(IAPCompletionHandle)handle{

if (purch.allKeys.count > 0) {

self.parmas = purch;

if ([purch objectForKey:@"productId"]) {

if ([SKPaymentQueue canMakePayments]) {

// 开始购买服务

self.purchID = [purch objectForKey:@"productId"];

self.handle = handle;

NSSet *nsset = [NSSet setWithArray:@[self.purchID]];

SKProductsRequest *request = [[SKProductsRequest alloc] initWithProductIdentifiers:nsset];

request.delegate = self;

[request start];

dispatch_async(dispatch_get_main_queue(), ^{

[self.targetView makeToastActivity:CSToastPositionCenter];

});

}else{

[self handleActionWithType:ApplePayManagerTypeNotArrow data:nil];

}

}

}

}

#pragma mark --收到产品返回信息 SKProductsRequestDelegate

- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response{

NSArray *product = response.products;

if([product count] <= 0){

NSLog(@"--------------没有商品------------------");

return;

}

SKProduct *p = nil;

for(SKProduct *pro in product){

if([pro.productIdentifier isEqualToString:self.purchID]){

p = pro;

break;

}

}

NSLog(@"productID:%@", response.invalidProductIdentifiers);

NSLog(@"产品付费数量:%lu",(unsigned long)[product count]);

NSLog(@"%@",[p description]);

NSLog(@"%@",[p localizedTitle]);

NSLog(@"%@",[p localizedDescription]);

NSLog(@"%@",[p price]);

NSLog(@"%@",[p productIdentifier]);

NSLog(@"发送购买请求");

//发送内购请求

SKPayment *payment = [SKPayment paymentWithProduct:p];

[[SKPaymentQueue defaultQueue] addPayment:payment];

}

//请求失败

- (void)request:(SKRequest *)request didFailWithError:(NSError *)error{

// [self.targetView hideToastActivity];

NSLog(@"------------------错误-----------------:%@", error);

}

- (void)requestDidFinish:(SKRequest *)request{

// [self.targetView hideToastActivity];

NSLog(@"------------反馈信息结束-----------------");

}

#pragma mark - SKPaymentTransactionObserver // 监听购买结果

- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions{

[self.targetView hideToastActivity];

for (SKPaymentTransaction *tran in transactions) {

NSLog(@"%ld====",(long)tran.transactionState);

switch (tran.transactionState) {

case SKPaymentTransactionStatePurchased: //交易完成

//订阅特殊处理

if(tran.originalTransaction){

//如果是自动续费的订单originalTransaction会有内容

NSLog(@"自动续费的订单,originalTransaction = %@",tran.originalTransaction.transactionIdentifier);

//tran.originalTransaction.transactionIdentifier

//SKPaymentTransaction

[self completeTransaction:tran isAutomatically:YES];

//[self verifyPurchaseWithPaymentTransaction:tran isTestServer:NO];

}else{

//普通购买,以及 第一次购买 自动订阅

// NSLog(@"%@-------",tran.transactionIdentifier);

[self completeTransaction:tran isAutomatically:NO];

}

break;

case SKPaymentTransactionStatePurchasing://商品添加进列表

//NSLog(@"%ld====",tran.error.code);

//NSLog(@"%@====",[[NSString alloc]initWithData:tran.payment.requestData encoding:NSUTF8StringEncoding]);

//[TDGAVirtualCurrency onChargeRequst:@"" iapId:@"" currencyAmount:0 currencyType:@"" virtualCurrencyAmount:0 paymentType:@""];

break;

case SKPaymentTransactionStateRestored://购买过

NSLog(@"已经购买过商品");

// 消耗型不支持恢复购买

//[[SKPaymentQueue defaultQueue] finishTransaction:tran];

break;

case SKPaymentTransactionStateFailed://交易失败

NSLog(@"%@====",tran.error);

//SKErrorUnknown

[self failedTransaction:tran];

break;

default:

break;

}

}

}

/// AppStore 从促销点击购买处理

- (BOOL)paymentQueue:(SKPaymentQueue *)queue shouldAddStorePayment:(SKPayment *)payment forProduct:(SKProduct *)product {

// 直接返回 NO,不交给苹果处理

NSString *productID = product.productIdentifier;

NSLog(@"AppStore 促销点击购买: productIdentifier - %@", productID);

return YES;

}

#pragma mark - delegate

// 交易结束

- (void)completeTransaction:(SKPaymentTransaction *)transaction isAutomatically:(BOOL)isAutomatically{

NSLog(@"%@", transaction.description);

// Your application should implement these two methods.

// 票据的校验是保证内购安全完成的非常关键的一步,一般有三种方式:

// 1、服务器验证,获取票据信息后上传至信任的服务器,由服务器完成与App Store的验证(提倡使用此方法,比较安全)

// 2、本地票据校验

// 3、本地App Store请求验证

NSURL *recepitURL = [[NSBundle mainBundle] appStoreReceiptURL];

NSData *receipt = [NSData dataWithContentsOfURL:recepitURL];

// 向自己的服务器验证购买凭证

//NSError *error;

//转化为base64字符串

NSString *receiptString=[receipt base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed];

NSLog(@"receipt ==== \n%@", receiptString);

//最终将这个receipt3的发送给服务器去验证就没问题啦!

//自动订阅(自动续费月卡)需要多加一个参数

NSString * product_id = transaction.payment.productIdentifier;

NSString * transaction_id = transaction.transactionIdentifier;

NSMutableDictionary * requestContents = [[NSMutableDictionary alloc]init];

//订阅特殊处理

if (isAutomatically) {

//如果是自动续费的订单originalTransaction会有内容

NSString * transaction_id2 = transaction.originalTransaction.transactionIdentifier;

NSString * transaction_id = transaction.transactionIdentifier;

// [requestContents addEntriesFromDictionary:@{@"receipt": receipt3,@"password":secretKey,@"product_id":product_id,@"transaction_id":transaction_id,@"originalTransaction":transaction_id2}];

}else{

if (self.parmas.allKeys.count > 0) {

// [requestContents addEntriesFromDictionary:@{@"receipt": receipt3,@"uid":self.parmas[@"uid"],@"amount":self.parmas[@"amount"],@"actorid":self.parmas[@"userRoleId"],@"server":self.parmas[@"serverId"],@"order_no":self.parmas[@"cpOrderNo"],@"password":secretKey,@"product_id":product_id,@"transaction_id":transaction_id}];

}

}

[self.targetView makeToastActivity:CSToastPositionCenter];

NSDictionary * dic = @{@"productId": product_id, @"transactionId": transaction_id, @"receipt": receiptString, @"isSandbox": @0};

[MineMemberInAppVerifyReceiptRequest requestWithDic:dic requestFinish:^(MineMemberInAppVerifyReceiptRequest * request) {

[self.targetView hideToastActivity];

int code = [[request.responseJson objectForKey:@"code"] intValue];

NSString * stateString = [request.responseJson objectForKey:@"msg"];

if (code == 200) {

[self handleActionWithType:ApplePayManagerTypeVerSuccess data:nil];

}else {

[self.targetView makeToast:@"校验失败" duration:2 position:CSToastPositionCenter];

}

} requestFailed:^(MineMemberInAppVerifyReceiptRequest *request) {

[self.targetView hideToastActivity];

[self.targetView makeToast:@"请求失败" duration:2 position:CSToastPositionCenter];

}];

//本地像苹果app store验证,上面是像自己的服务器验证

[self verifyPurchaseWithPaymentTransaction:transaction isTestServer:YES];

// 验证成功与否都注销交易,否则会出现虚假凭证信息一直验证不通过,每次进程序都得输入苹果账号

[[SKPaymentQueue defaultQueue] finishTransaction:transaction];

//[self verifyPurchaseWithPaymentTransaction:transaction isTestServer:NO];

}

// 交易失败

- (void)failedTransaction:(SKPaymentTransaction *)transaction{

if (transaction.error.code != SKErrorPaymentCancelled) {

[self handleActionWithType:ApplePayManagerTypeFailed data:nil];

}else{

[self handleActionWithType:ApplePayManagerTypeCancle data:nil];

}

[[SKPaymentQueue defaultQueue] finishTransaction:transaction];

}

- (void)verifyPurchaseWithPaymentTransaction:(SKPaymentTransaction *)transaction isTestServer:(BOOL)flag{

//交易验证

NSURL *recepitURL = [[NSBundle mainBundle] appStoreReceiptURL];

NSData *receipt = [NSData dataWithContentsOfURL:recepitURL];

if(!receipt){

// 交易凭证为空验证失败

[self handleActionWithType:ApplePayManagerTypeVerFailed data:nil];

return;

}

// 购买成功将交易凭证发送给服务端进行再次校验

// [self handleActionWithType:ApplePayManagerTypeSuccess data:receipt];

NSError *error;

NSDictionary *requestContents = @{

@"receipt-data": [receipt base64EncodedStringWithOptions:0]

};

NSData *requestData = [NSJSONSerialization dataWithJSONObject:requestContents

options:0

error:&error];

if (!requestData) { // 交易凭证为空验证失败

[self handleActionWithType:ApplePayManagerTypeVerFailed 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 *session = [NSURLSession sharedSession];

NSURLSessionDataTask * dataTask = [session dataTaskWithRequest:storeRequest completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {

if (error) {

// 无法连接服务器,购买校验失败

[self handleActionWithType:ApplePayManagerTypeVerFailed data:nil];

} else {

NSError *error;

NSDictionary *jsonResponse = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error];

if (!jsonResponse) {

// 苹果服务器校验数据返回为空校验失败

[self handleActionWithType:ApplePayManagerTypeVerFailed data:nil];

}

// 先验证正式服务器,如果正式服务器返回21007再去苹果测试服务器验证,沙盒测试环境苹果用的是测试服务器

NSString *status = [NSString stringWithFormat:@"%@",jsonResponse[@"status"]];

if (status && [status isEqualToString:@"21007"]) {

// [self verifyPurchaseWithPaymentTransaction:transaction isTestServer:YES];

}else if(status && [status isEqualToString:@"0"]){

// [self handleActionWithType:ApplePayManagerTypeVerSuccess data:nil];

}

}

}];

[dataTask resume];

// 验证成功与否都注销交易,否则会出现虚假凭证信息一直验证不通过,每次进程序都得输入苹果账号

[[SKPaymentQueue defaultQueue] finishTransaction:transaction];

}

#pragma mark - private

- (void)handleActionWithType:(ApplePayManagerType)type data:(NSData *)data{

switch (type) {

case ApplePayManagerTypeSuccess:

NSLog(@"购买成功");

break;

case ApplePayManagerTypeFailed:

NSLog(@"购买失败");

break;

case ApplePayManagerTypeCancle:

NSLog(@"用户取消购买");

[self.targetView makeToast:@"用户取消购买" duration:2 position:CSToastPositionCenter];

break;

case ApplePayManagerTypeVerFailed:

[self.targetView makeToast:@"订单校验失败" duration:2 position:CSToastPositionCenter];

NSLog(@"订单校验失败");

break;

case ApplePayManagerTypeVerSuccess:

NSLog(@"订单校验成功");

break;

case ApplePayManagerTypeNotArrow:

NSLog(@"不允许程序内付费");

break;

default:

break;

}

if(_handle){

_handle(type,data);

}

}

上报完成即可

在App 提交审核的时候,要附带着添加指定的内购产品,

注意: 在未审核通过之前,这些商品,均是沙盒测试环境下的,支付并不会涉及金额交易,而且后端可以查询到支付信息

上一篇
下一篇
相关文章