起因是这样,自去年12月份,就陆续有玩家反馈以下问题购买了商品,却无法获得,也无法恢复购买

兑换码无法兑换到商品

重现:在设备1上兑换了A商品,恢复购买和再次免费购买,是无效的,而在设备2上用同一个账号却是有效的。

在设备上再次购买已购买的A商品,提示已购买将免费获取,点击后却无反应。

由于这只能在正式环境上重现,可以代码调试的沙盒测试是不行的,只能通过猜测和上线调试代码才能验证问题

初步猜测:没有拿到收据

收据没有通过二次验证

于是加入了以下代码验证:

var data = new Dictionary

{

{ "receipt_data", transactionResult.receipt },

};

//将内购信息转发到服务器收集查询ServerManager.instance.SendToUrl("purchase/receipts", "POST", data, jObject =>

{

var statusToken = jObject["status"];

if (statusToken.IsNullOrEmpty())

{

callback(false);

return;

}

var status = (int)statusToken;

callback(status == 0);

});

通过收据内容就可以分析。

最后发现,客户端的内购没有回调

//内购状态回调

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

继续往下分析Apple bug

由于内购OC代码是借助Unity插件IOSNative的,所以可能是这部分OC代码问题

内购丢单和兑换码无效是同一个模块导致的

OC代码问题

由于代码是IOSNative的,找了一些主流的内购插件,甚至Unity自带的Unity IAP,底层原理都一样,还是无法解决问题,所以需要自己去写OC代码,把内购信息从OC转发到C#去验证

#import

#import

NSString *managerName = @"IAPManager";

NSString *initSucceedFuncName = @"OnInitSucceed";

NSString *transactionFuncName = @"OnTransactionCompleted";

NSString *restoreCompleteFuncName = @"OnRestoreCompleted";

NSString *splitString = @"-";

@interface IAPManager : NSObject

{

SKProductsRequest *productsRequest;

NSArray *products;

}

-(void)initIAP;

-(void)buy:(NSString *)productIdentifier;

-(void)restore;

@end

@implementation IAPManager

IAPManager *iapManager = nil;

//初始化, 商品id用,分隔

-(void) initIAP:(NSString *)productIdentifiers

{

[[SKPaymentQueue defaultQueue] addTransactionObserver:self];

NSArray *idArray = [productIdentifiers componentsSeparatedByString:splitString];

NSSet *idSet = [NSSet setWithArray:idArray];

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

request.delegate = self;

[request start];

}

//购买

-(void) buy:(NSString *)productIdentifier

{

SKProduct *product = nil;

for (SKProduct *p in products)

{

if ([p.productIdentifier isEqualToString:productIdentifier])

{

product = p;

break;

}

}

if (product != nil)

{

SKMutablePayment *payment = [SKMutablePayment paymentWithProduct:product];

[[SKPaymentQueue defaultQueue] addPayment:payment];

}

}

//恢复购买

-(void) restore

{

[[SKPaymentQueue defaultQueue] restoreCompletedTransactions];

}

//接受初始化后获得的商品信息事件

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

{

products = response.products;

NSString *stringInfos = @"";

for (SKProduct *product in products)

{

NSArray *info = [NSArray arrayWithObjects:product.productIdentifier, product.localizedTitle, product.price.stringValue, product.priceLocale.currencySymbol, nil];

NSString *stringInfo = [info componentsJoinedByString:splitString];

NSNumberFormatter *numberFormatter = [[NSNumberFormatter alloc] init];

[numberFormatter setFormatterBehavior:NSNumberFormatterBehavior10_4];

[numberFormatter setNumberStyle:NSNumberFormatterCurrencyStyle];

[numberFormatter setLocale:product.priceLocale];

NSString *formattedPrice = [numberFormatter stringFromNumber:product.price];

stringInfos = [stringInfos stringByAppendingString:stringInfo];

stringInfos = [stringInfos stringByAppendingString:splitString];

stringInfos = [stringInfos stringByAppendingString:formattedPrice];

stringInfos = [stringInfos stringByAppendingString:@"\n"];

}

[self sendToManager:initSucceedFuncName :stringInfos];

}

-(void) onTransactionCompleted:(SKPaymentTransaction *)transaction

{

NSString *state = nil;

switch (transaction.transactionState)

{

case SKPaymentTransactionStatePurchased:

state = @"purchased";

break;

case SKPaymentTransactionStateRestored:

state = @"restored";

break;

case SKPaymentTransactionStateDeferred:

state = @"deferred";

break;

case SKPaymentTransactionStateFailed:

state = @"failed";

break;

default:

break;

}

NSString* receipt = transaction.transactionReceipt.base64Encoding;

NSString* productId = transaction.payment.productIdentifier;

NSArray *info = [NSArray arrayWithObjects:productId, state, receipt, nil];

NSString *stringInfo = [info componentsJoinedByString:splitString];

[self sendToManager:transactionFuncName:stringInfo];

}

//内购状态回调

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

{

for (SKPaymentTransaction *transaction in transactions)

{

if (transaction.transactionState != SKPaymentTransactionStatePurchasing)

{

[self onTransactionCompleted:transaction];

[[SKPaymentQueue defaultQueue] finishTransaction:transaction];

}

}

}

-(void) paymentQueueRestoreCompletedTransactionsFinished:(SKPaymentQueue *)queue

{

[self sendToManager:restoreCompleteFuncName :nil];

}

-(void) paymentQueue:(SKPaymentQueue *) paymentQueue restoreCompletedTransactionsFailedWithError:(NSError *)error

{

[self sendToManager:restoreCompleteFuncName :nil];

}

-(void)sendToManager:(NSString *)methodName :(NSString *)args

{

UnitySendMessage([managerName UTF8String], [methodName UTF8String], args == nil ? "" : [args UTF8String]);

}

@end

extern "C"

{

bool IsPurchaseAvaible()

{

return [SKPaymentQueue canMakePayments];

}

void InitIAP(char *p)

{

iapManager = [[IAPManager alloc] init];

NSString *list = [NSString stringWithUTF8String:p];

[iapManager initIAP:list];

}

void Buy(char *p)

{

[iapManager buy:[NSString stringWithUTF8String:p]];

}

void Restore()

{

[iapManager restore];

[iapManager refreshReceipt];

}

}

结果上线了一个新的版本还是一样,没有解决

Apple bug

Apple TSI(苹果技术支持)

在设备1上兑换了A商品,恢复购买和再次免费购买,没有“updatedTransactions”的回调,而在设备2上用同一个账号却是有的,然后在Console上把整个过程的log保存下来对比,发现有大量术语和标志的一些不明意义的命名,只能联系Apple TSI帮忙查看,估计内部有对照的一些问题的标志,得到以下回复

如果有“fetchSoftwareAddons”的话,发他看。整理一下log又发了一封邮件,等待的时候重写了内购底层OC的IAP,Apple效率还是挺高的,两天就有回复了,如下

这确实是一个bug,需要反馈到Apple bug去解决。XD

解决方案反馈到Apple Bug,等他们修复

找寻另外的办法解决

到Apple developer forum找解决方案

遇到了不少同样问题的开发者,但都还没有解决方案这个是兑换码一样也是有的玩家无法兑换的

这个是无法恢复购买的

通过另外的方式去恢复购买或兑换

Apple TSI的技术提供了新思路,通过刷新购买收据,解析收据去恢复购买

NSString *receiptCompletedFuncName = @"OnReceiptCompleted";

//刷新凭证

-(void) refreshReceipt

{

SKReceiptRefreshRequest *request = [[SKReceiptRefreshRequest alloc] init];

request.delegate = self;

[request start];

NSLog(@"[IAPManager]: On refresh receipt started");

}

-(void) requestDidFinish:(SKRequest *)request

{

NSLog(@"[IAPManager]: On refresh receipt finished");

if ([request isKindOfClass:[SKReceiptRefreshRequest class]])

{

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

NSData *receiptData = [NSData dataWithContentsOfURL:receiptUrl];

NSString *receiptString = receiptData.base64Encoding;

[self sendToManager:receiptCompletedFuncName:receiptString];

}

}

extern "C"

{

void Restore()

{

...

[iapManager refreshReceipt];

}

}

receipt-data转base64转发到C#服务器处理(目前是转发App Store做二次验证,以后为了更安全需要改动转发到公司服务器验证)

//TODO: 公司服务器验证private void OnReceiptCompleted(string receiptData)

{

var data = new Dictionary

{

{ "receipt-data", receiptData },

};

var binaryData = System.Text.Encoding.UTF8.GetBytes(JsonUtils.Serialize(data));

var www = new WWW("https://buy.itunes.apple.com/verifyReceipt", binaryData);

CoroutineManager.instance.StartCoroutine(() =>

{

if (www.error != null)

{

//错误处理 }

else

{

var jObject = JsonUtils.Deserialize(www.text);

var status = (int)jObject["status"];

if (status == 0)

{

OnReceiptDataVertify(jObject);

}

else if (status == 21007)

{

//sandbox test www = new WWW("https://sandbox.itunes.apple.com/verifyReceipt", binaryData);

CoroutineManager.instance.StartCoroutine(() =>

{

if (www.error != null)

{

//错误处理 }

else

{

Debug.Log(string.Format("[IAPManager]: receiptData {0}", www.text));

jObject = JsonUtils.Deserialize(www.text);

status = (int)jObject["status"];

if (status == 0)

{

OnReceiptDataVertify(jObject);

}

}

}, () => www.isDone);

}

else

{

}

}

}, () => www.isDone);

}

根据收据结构解析jObject的in_app数组即可获知玩家真实拥有的内购商品id,最后问题解决了~

结语所有有丢单问题和兑换码无效的玩家的问题都解决了,不过这个bug还是得反馈的,一个功能对应一段代码,恢复购买目前是通过两种方式实现,其实并不是一个好的代码结构,反馈中,等待Apple bug进一步的回复

OC的语法是shi

与Apple TSI沟通3要素简单描述问题

录制视频重现问题

同时连接Macos的Console.app发调试信息

ios 内购正式环境_iOS内购-部分玩家无法恢复购买相关推荐

  1. ios 内购正式环境_iOS 内购的实现

    自己开发的视频直播项目,牵涉到充值金币,用到了苹果公司的内购,趴坑了两天,这里总结下实现苹果内购. 一. 创建测试App 首先你需要登录 App的ItunesConnection,你会看到如下界面 简 ...

  2. ios 内购正式环境_iOS 内购经验

    最近在做iOS内购,碰到的一些问题及解决方案,希望能帮到大家 1.如果需要内购实现代码,请留下你的QQ 2.关于内购商品被退回 内购被退回,主要有两个原因, 一是标题描述等信息没写清楚,这里需要你填写 ...

  3. ios 内购正式环境_iOS 内购最新讲解

    本文为CocoaChina网友IIronMan投稿 一.总说内购的内容协议.税务和银行业务 信息填写 内购商品的添加 添加沙盒测试账号 内购代码的具体实现 内购的注意事项 二.协议.税务和银行业务 信 ...

  4. ios 内购正式环境_ios内购之服务端操作

    { "status": 0, "environment": "Sandbox", "receipt": { " ...

  5. ios 内购正式环境_iOS app内购

    一.测试账户生成 1.添加沙盒测试员 在App Store Connect中选择 Users and Access打开页面后在sandbox 下点击Testers,点击添加按钮(+),输入测试信息然后 ...

  6. ios 内购正式环境_iOS开发-2017苹果内购最新教程

    公司项目中有虚拟产品,所以要使用苹果内购.自此记录一下苹果内购的流程.前提是已有开发者账号 协议,税务和银行业务 协议,税务和银行业务 协议,税务和银行业务 申请合同 这里提示地址太长,按照要求重新填 ...

  7. ios 内购正式环境_iOS苹果内购(详细步骤)

    #### 一.设置付费协议 1.进入开发者平台,点击如下图所示: 设置 2.查看付费同意条款 1. 设置付费协议 添加这3个信息 4.添加银行账户 5.选择报税表 默认美国 6.根据报税表填写报税情况 ...

  8. ios 内购正式环境_php服务端处理 ios内购充值

    app一直没有接入iOS内购充值,随着业务支付功能越来越多,ios内购充值就提到日程上来了.那么,ios内购充值怎么做呢? 其实iOS内购充值是通过客户端接入iOS的IAP模块(In-AppPurch ...

  9. 为什么苹果内购总是失败_ios内购相关问题被拒总结

    2017年10月9日 10月5日 收到如下邮件,今天试了下内购功能,可以正常充值了. image.png image.png 2017年9月29日 1.今天审核通过了 不过发现appstore下载还是 ...

最新文章

  1. 再谈J.D.Ewards历史与现状,JDE人才缺失现状
  2. java+JBroFuzz对restful api进行fuzz测试
  3. 用C#语言构造蜘蛛程序
  4. 【杂谈】为何有三AI只做原创,从不转载
  5. 【小白学习keras教程】八、Sequential Model和模型函数API两种模型建立方法
  6. 数字图像处理(一):灰度变换和直方图处理
  7. (译)创建.NET Core多租户应用程序-租户解析
  8. 使用SynchronousQueue实现生产者/消费者
  9. Spoken English-口语-练习频次
  10. 一款由css3和jquery实现的卡面折叠式菜单
  11. 符号级别(二)--实际应用
  12. c#如何调用php接口方法参数类型,c# – .NET:使用通用接口参数调用Assembly obj的GetInterface方法...
  13. 大学课程 | 《微机原理与接口技术》知识点总结
  14. 计算机专业专硕考研数学考一还是二,计算机专业考研数学考一还是二
  15. 天下武功唯快不破------实验吧
  16. linux rstudio 中文乱码,谈谈R中的乱码(一)
  17. 微信seo搜索精准引流怎么做呢?(案例效果图)
  18. 766C - Mahmoud and a Message(dp)
  19. uni.getLocation获取位置失败
  20. 免费的XP/Vista无损分区软件 EASEUS Partition Master

热门文章

  1. Golang 见证 godoc 的强大(生成API文档,打印文档)
  2. java 解释器与JIT编译器
  3. 人工智能之数学基础篇—线性代数基础(下)
  4. https 证书过期检测
  5. JavaScript(JS) Math.E
  6. as3 Loader 加载资源后内存泄露无法释放的问题。
  7. Google Earth Engine(GEE)扩展——geetool中的的各类型的其它函数(字符串、数字、列表、字典、几何、特征、影像、地图和特征集合)
  8. VUE 实现购物车或者消息中心未读消息数量的实时监听
  9. 性能测试之JMeter断言
  10. iphone换android app图标,iPhone 终于能随意修改 App 图标啦