iOS内购遇到刷单问题

问题描述

	最近公司发现公司发现有人通过苹果内购充值,实际上苹果后台查询充值记录并没有相关记录,初步判断可能内购流程出现了问题进行排查。

苹果内购流程图

IOS SDK SDK SERVE APPLE 服务 1、创建订单 2、返回订单号 支付第一步生下单 3、发起支付 4、返回支付结果 支付第二步 支付 5、订单号、苹果返回receipt-data 6、验证支付结果 7、返回验证结果 8、返回最终支付结果 支付第三步 完成支付 IOS SDK SDK SERVE APPLE 服务

通用流程梳理

  1. IOS SDK 请求服务器,创建订单;
  2. 服务器生成订单,并返回订单号;
  3. 发起支付,如果没有登陆会要求用户登陆appleID,如果已经登陆,会弹出购买确认;
  4. 点击购买,等待苹果返回支付结果;
  5. 如果支付成功,苹果会返回receipt-data 数据,与订单号一起发到服务器进行校验;
  6. 接收到IOS SDK参数进行校验,成功后请求APPLE 服务校验支付结果;
  7. 验证返回结果返回进行业务操作,并返回IOS SDK最终支付结果;

问题原因

	苹果服务器再返回给我们服务器订单结果。receipt_data 在越狱环境下是可以被插件伪造的,后台向苹果验证时,居然还能验证通过。下面是几种receipt_data 请求APPLE 服务器返回的结果
正常返回JSON格式为:

第一种

{
    "status": 0, 
    "environment": "Sandbox", 
    "receipt": {
        "receipt_type": "ProductionSandbox", 
        "adam_id": 0, 
        "app_item_id": 0, 
        "bundle_id": "com.xxx.xxx", 
        "application_version": "84", 
        "download_id": 0, 
        "version_external_identifier": 0, 
        "receipt_creation_date": "2016-12-05 08:41:57 Etc/GMT", 
        "receipt_creation_date_ms": "1480927317000", 
        "receipt_creation_date_pst": "2016-12-05 00:41:57 America/Los_Angeles", 
        "request_date": "2016-12-05 08:41:59 Etc/GMT", 
        "request_date_ms": "1480927319441", 
        "request_date_pst": "2016-12-05 00:41:59 America/Los_Angeles", 
        "original_purchase_date": "2013-08-01 07:00:00 Etc/GMT", 
        "original_purchase_date_ms": "1375340400000", 
        "original_purchase_date_pst": "2013-08-01 00:00:00 America/Los_Angeles", 
        "original_application_version": "1.0", 
        "in_app": [
            {
                "quantity": "1", 
                "product_id": "*******【支付定义产品ID】*******", 
                "transaction_id": "10000003970", 
                "original_transaction_id": "10000003970", 
                "purchase_date": "2016-12-05 08:41:57 Etc/GMT", 
                "purchase_date_ms": "1480927317000", 
                "purchase_date_pst": "2016-12-05 00:41:57 America/Los_Angeles", 
                "original_purchase_date": "2016-12-05 08:41:57 Etc/GMT", 
                "original_purchase_date_ms": "1480927317000", 
                "original_purchase_date_pst": "2016-12-05 00:41:57 America/Los_Angeles", 
                "is_trial_period": "false"
            }
        ]
    }
}
	这里边in_app 可能会出现多条数据情况,每当有一笔交易发起的时候,in_app里就会添加收据的一些信息。这些信息会一直保存直到你结束这笔交易。在此之后,下次更新收据时会将其从收据中删除 - 例如,当用户再次购买时,或者您的应用明确刷新收据时。
	非消耗型项目,自动续期订阅,非续期订阅或免费订阅的应用内购买收据将无限期保留在收据中。

第二种

{
    "receipt": {
        "original_purchase_date_pst": "2016-12-03 01:11:01 America/Los_Angeles", 
        "purchase_date_ms": "1480756261254", 
        "unique_identifier": "96f51b28f628493709966f33a1fe7ba", 
        "original_transaction_id": "1000000255766", 
        "bvrs": "82", 
        "transaction_id": "1000000255766", 
        "quantity": "1", 
        "unique_vendor_identifier": "FE358-1362-40FD-870F-DF788AC5", 
        "item_id": "11822945", 
        "product_id": ""*******【支付定义产品ID】*******"", 
        "purchase_date": "2016-12-03 09:11:01 Etc/GMT", 
        "original_purchase_date": "2016-12-03 09:11:01 Etc/GMT", 
        "purchase_date_pst": "2016-12-03 01:11:01 America/Los_Angeles", 
        "bid": "com.xxx.xxx", 
        "original_purchase_date_ms": "1480756261254"
    }, 
    "status": 0
}
	查看资料说是在IOS7以前版本支付和现在版本支付验证返回不用的数据结构。
越狱订单receipt_data向苹果服务器校验后如下:
{
    "status": 0, 
    "environment": "Production", 
    "receipt": {
        "receipt_type": "Production", 
        "adam_id": 1377028992, 
        "app_item_id": 1377028992, 
        "bundle_id": "com.xxx.xxx", 
        "application_version": "3", 
        "download_id": 80042231041057, 
        "version_external_identifier": 827853261, 
        "receipt_creation_date": "2018-07-23 07:30:45 Etc/GMT", 
        "receipt_creation_date_ms": "1532331045000", 
        "receipt_creation_date_pst": "2018-07-23 00:30:45 America/Los_Angeles", 
        "request_date": "2018-07-23 07:33:54 Etc/GMT", 
        "request_date_ms": "1532331234485", 
        "request_date_pst": "2018-07-23 00:33:54 America/Los_Angeles", 
        "original_purchase_date": "2018-07-01 12:16:21 Etc/GMT", 
        "original_purchase_date_ms": "1530447381000", 
        "original_purchase_date_pst": "2018-07-01 05:16:21 America/Los_Angeles", 
        "original_application_version": "3", 
        "in_app": [ ]
    }
}

解决方法

将校验逻辑进行修改,不能只校验status=0

  • 首先客户端传参数需要增加:product_id,transaction_id
//该方法为监听内购交易结果的回调
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray<SKPaymentTransaction *> *)transactions
transactions 为一个数组 遍历就可以得到 SKPaymentTransaction 对象的元素transaction。然后从transaction里可以取到以下这两个个参数,product_id,transaction_id。另外从沙盒里取到票据信息receipt_data 
我们先看怎么取到以上的三个参数
//获取receipt_data
NSData *data = [NSData dataWithContentsOfFile:[[[NSBundle mainBundle] appStoreReceiptURL] path]];
NSString * receipt_data = [data base64EncodedStringWithOptions:0];
//获取product_id
NSString *product_id = transaction.payment.productIdentifier;
//获取transaction_id
NSString * transaction_id = transaction.transactionIdentifier;
这是我们必须要传给服务器的三个字段。以上三个字段需要做好空值校验,避免崩溃。
下面我们来解释一下,为什么要给服务器传这三个参数。
receipt_data:这个不解释了 大家都懂 不传的话 服务器根本没法校验
product_id:这个也不用解释 内购产品编号 你不传的话 服务器不知道你买的哪个订单
transaction_id:这个是交易编号,是必须要传的。因为你要是防止越狱下内购被破解就必须要校验in_app这个参数。而这个参数的数组元素有可能为多个,你必须得找到一个唯一标示,才可以区分订单到底是那一笔。
  • 服务器逻辑修改
    • 先判断是否重复分发内购产品。收到客户端上报的transaction_id 后,直接MD5后去数据库查,能查到说明是重复订单,返回相应错误码给客户端,查不到去苹果那边校验。
      沙箱校验地址 = “https://sandbox.itunes.apple.com/verifyReceipt”;
      正式校验地址 = “https://buy.itunes.apple.com/verifyReceipt”;
    • 服务器拿到苹果校验结果,首先判断订单状态是否成功。
    • 如果订单成功在判断格式为IOS7 之前还是之后。
      • 如果为IOS7 之前版本直接判断transaction_id、product_id;
      • 如果为IOS7 之后判断in_app 是否有值,如果没有直接返回失败,如果存在遍历查询对应transaction_id并比较product_id;
    • 以上校验都正确就可以把订单充值进去,给用户分发内购产品。

注:后台传入参数一定要进行存储。

解析

苹果IAP官方文档

iOS内购遇到刷单问题_第1张图片
iOS内购遇到刷单问题_第2张图片
说明:in_app参数可能为空,如果为空得话,也需要将这笔交易认为是有效的交易。当然我们肯定不能这么干,这个参数是必须必须要校验的,不然越狱环境下,分分钟就把你内购破解了。我去校验了很多正常用户的内购订单,没发现一个in_app参数是为空的。但为了保险,还是让后台把所有前端传的receipt_data等参数不管成功失败都保存下来,万一哪个用户因此投诉充值不到账,我们有据可查。

参考:https://www.jianshu.com/p/5cf686e92924
参考:https://www.jianshu.com/p/7e7c3a918946utm_campaign=hugo&utm_medium=reader_share&utm_content=note&utm_source=qq

你可能感兴趣的:(错误记录,Java)