博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
java验证苹果支付收据(转载)
阅读量:4042 次
发布时间:2019-05-24

本文共 10396 字,大约阅读时间需要 34 分钟。

转自胖哥的整理,地址:

苹果说明文档:

这是一篇文摘性文章。

验证苹果支付的代码

方法一:使用HttpsURLConnection

响应速度比方法二快。

public static JSONObject verifyReceipt1(String recepit) {          return verifyReceipt1("https://buy.itunes.apple.com/verifyReceipt", recepit);      }      public static JSONObject verifyReceipt1(String url, String receipt) {          try {              HttpsURLConnection connection = (HttpsURLConnection) new URL(url).openConnection();                connection.setRequestMethod("POST");                connection.setDoOutput(true);                connection.setAllowUserInteraction(false);               PrintStream ps = new PrintStream(connection.getOutputStream());                ps.print("{\"receipt-data\": \"" + receipt + "\"}");                ps.close();                BufferedReader br = new BufferedReader(new InputStreamReader(connection.getInputStream()));                String str;                StringBuffer sb = new StringBuffer();                while ((str = br.readLine()) != null) {                    sb.append(str);                  }                br.close();                String resultStr = sb.toString();                JSONObject result = JSONObject.parseObject(resultStr);              if (result != null && result.getInteger("status") == 21007) {                  return verifyReceipt1("https://sandbox.itunes.apple.com/verifyReceipt", receipt);              }              return result;          } catch (Exception e) {              e.printStackTrace();          }          return null;      }

方法二:使用HttpClient

public static JSONObject verifyReceipt2(String receipt) {          return verifyReceipt2("https://buy.itunes.apple.com/verifyReceipt", receipt);      }      public static JSONObject verifyReceipt2(String url, String receipt) {          HttpClient httpClient = new DefaultHttpClient();          HttpPost httpPost = new HttpPost(url);          try {              JSONObject data = new JSONObject();              data.put("receipt-data", receipt);              StringEntity entity = new StringEntity(data.toJSONString());              entity.setContentEncoding("utf-8");              entity.setContentType("application/json");              httpPost.setEntity(entity);              HttpResponse response = httpClient.execute(httpPost);              HttpEntity httpEntity = response.getEntity();              String resultStr = EntityUtils.toString(httpEntity);              JSONObject result = JSONObject.parseObject(resultStr);              httpPost.releaseConnection();              if (result.getInteger("status") == 21007) {                  return verifyReceipt2("https://sandbox.itunes.apple.com/verifyReceipt", receipt);              }              return result;          } catch (Exception e) {              e.printStackTrace();          }          return null;      }

这里的代码仅仅是从苹果获取了JSON对象,并未进行响应的验证。

支付数据的验证

我们来细细看一下返回的JSON,大概是下边这个样子的:

{    "status": 0,    "environment": "Production",    "receipt": {        "receipt_type": "Production",        "adam_id": 2341443613,        "app_item_id": 2234443613,        "bundle_id": "com.xxxxx.xxxxx",        "application_version": "1",        "download_id": 23456572706673,        "version_external_ident ifier": 821223402,        "receipt_creation_date": "2017-01-25 00:52:37 Etc/GMT",        "receipt_creation_date_ms": "3333897657000",        "receipt_creation_date_pst": "2017-01-25 17:57:37 America/Los_Angeles",        "request_date": "2017-01-26 00:57:38 Etc/GMT",        "request_date_ms": "1445897657000",        "request_date_pst": "2017-05-29 17:57:38 America/Los_Angeles",        "original_purchase_date": "2016-01-25 15:37:18 Etc/GMT",        "original_purchase_ date_ms": "145234568000",        "original_purchase_date_pst": "2016-01-25 07:37:18 America/Los_Angeles",        "original_application_version": "12",        "in_app": [             {                 "quantity": "1",                 "product_id": "xxxxxxxxx",                 "transaction_id": "110000290198443",                 "original_transaction_id": "110000290198443",                 "purchase_date": "2017-01-26 00:23:36 Etc/GMT",                 "purchase_date_ms": "1496105856000",                 "purchase_date_pst": "2017-01-26 00:35:30 America/Los_Angeles",                 "original_purchase_date": "2017-01-26 00:57:36 Etc/GMT",                 "original_purchase_date_ms": "14347896000",                 "original_purchase_date_pst": "2017-01-25 17:57:36 America/Los_Angeles",                 "is_trial_period": "false"             }         ]     }}

—————————————————————————————————————————————————————————

重点解释一下in_app,此处是导致漏单情况最严重的地方

in_app返回的是JsonArrary,就是如果该用户支付多次,前两次没有校验(没走完Apple Pay的完整流程链),则最近一次校验会把前几次的校验串返回。in_app返回的是空,也说明校验是有效的。

这样就需要处理JsonArrary的数量>1的情况,取最新的交易时间的,而且价格匹配上,进行去充值操作,额外数据的进行数据存储,方便后续用户投诉时,进行查找凭证。

如果用户过来的请求也可以去匹配一下额外数据存储表,如果匹配成功,则可以充值。(谨慎使用,如果客服有权限补单充值的)

transaction_id:最新票据交易号

original_transaction_id:最初的票据交易号

网上也有相同的情况,参数地址:https://www.cnblogs.com/widgetbox/p/8241333.html

看完这个就很好理解上面出现的问题了,也就是说:

验证票据返回的receipt里面的in_app字段,这个字段包含了所有你未完成交易的票据信息。也就是在上面说到的APP完成交易之后,这个票据信息,就会从in_app中消失。

如果APP不完成交易,这个票据信息就会在in_app中一直保留。(这个情况可能仅限于你的商品类型为消耗型)

 

知道了事件的原委,就很好优化解决了,方案有2个

1.对票据返回的in_app数据全部进行处理,没有充值的全部进行充值

2.仅对最新的充值信息进行处理(我们采取的方案)

因为采用二方案:

如果用户仅进行了一次充值,该充值未到账,他不再进行充值了,那么会无法导致。

如果他通过客服的途径已经进行了补充充值,那么他在下一次充值的时候依旧会把之前的产品票据带回,这时候有可能出现重复充值的情况

—————————————————————————————————————————————————————————

解读一下status:

0 正常21000 App Store不能读取你提供的JSON对象21002 receipt-data域的数据有问题21003 receipt无法通过验证21004 提供的shared secret不匹配你账号中的shared secret21005 receipt服务器当前不可用21006 receipt合法,但是订阅已过期。服务器接收到这个状态码时,receipt数据仍然会解码并一起发送21007 receipt是Sandbox receipt,但却发送至生产系统的验证服务21008 receipt是生产receipt,但却发送至Sandbox环境的验证服务

不难发现我们可以利用 in_app中的quantity、product_id、transaction_id、purchase_date来对支付内容进行检查,当然了记录下返回的receipt文本串也是个不错的方法。

网上有人用MD5值的方法来防止重复支付,其实transaction_id也是可以做唯一区分的。以下是一部分来自网上的代码,

public class IOSAction extends BaseAction{
private static final long serialVersionUID = 1L; /** * 客户端向服务器验证 * * * * checkState A 验证成功有效(返回收据) * B 账单有效,但己经验证过 * C 服务器数据库中没有此账单(无效账单) * D 不处理 * * @return * @throws IOException */ public void IOSVerify() throws IOException { HttpServletRequest request=ServletActionContext.getRequest(); HttpServletResponse response=ServletActionContext.getResponse(); System.out.println(new Date().toLocaleString()+" 来自苹果端的验证..."); //苹果客户端传上来的收据,是最原据的收据 String receipt=request.getParameter("receipt"); System.out.println(receipt); //拿到收据的MD5 String md5_receipt=MD5.md5Digest(receipt); //默认是无效账单 String result=R.BuyState.STATE_C+"#"+md5_receipt; //查询数据库,看是否是己经验证过的账号 boolean isExists=DbServiceImpl_PNM.isExistsIOSReceipt(md5_receipt); String verifyResult=null; if(!isExists){ String verifyUrl=IOS_Verify.getVerifyURL(); verifyResult=IOS_Verify.buyAppVerify(receipt, verifyUrl); //System.out.println(verifyResult); if(verifyResult==null){ //苹果服务器没有返回验证结果 result=R.BuyState.STATE_D+"#"+md5_receipt; }else{ //跟苹果验证有返回结果------------------ JSONObject job = JSONObject.fromObject(verifyResult); String states=job.getString("status"); if(states.equals("0"))//验证成功 { String r_receipt=job.getString("receipt"); JSONObject returnJson = JSONObject.fromObject(r_receipt); //产品ID String product_id=returnJson.getString("product_id"); //数量 String quantity=returnJson.getString("quantity"); //跟苹果的服务器验证成功 result=R.BuyState.STATE_A+"#"+md5_receipt+"_"+product_id+"_"+quantity; //交易日期 String purchase_date=returnJson.getString("purchase_date"); //保存到数据库 DbServiceImpl_PNM.saveIOSReceipt(md5_receipt, product_id, purchase_date, r_receipt); }else{ //账单无效 result=R.BuyState.STATE_C+"#"+md5_receipt; } //跟苹果验证有返回结果------------------ } //传上来的收据有购买信息==end============= }else{ //账单有效,但己验证过 result=R.BuyState.STATE_B+"#"+md5_receipt; } //返回结果 try { System.out.println("验证结果 "+result); System.out.println(); response.getWriter().write(result); } catch (IOException e) { e.printStackTrace(); } } }

特殊场景的处理

有些特殊场景,还是需要前端配合去做的。下边摘录的内容值得了解。

关于漏单

  • 漏单必须要处理,玩家花RMB购买的东西却丢失了,是绝对不能容忍的。所谓的漏单就是玩家已经正常付费,却没有拿到该拿的道具。 
    解决:只要购买成功,便将购买记录(receipt等账单信息)保存下来,然后将账单信息传送给我们游戏服务器,游戏服务器获得账单后,和苹果服务器验证,账单有效的话,回馈给游戏服务器处理,游戏服务器处理后,返回给游戏客户端处理,处理完毕,将本地保存的购买记录删除。
  • 漏单的检测位置 
    解决: 
    2.1 做法1:在任意购买成功之后,顺便检测一次漏单,有漏单数遍处理了。 
    2.2 做法2:是在游戏登陆的时候检测一次漏单,即循环检测漏单数据,挨个发送给服务器验证处理,直到将所有的漏单处理完毕。这是原因是购买服务器未返回结果而客户端崩溃的情况下,玩家再次登陆,会产生漏单。
  • 漏单的版本兼容 
    漏单要做好版本兼容,eg.玩家购买英雄ID为100的英雄,产生了一次漏单,但是一直未再次登陆游戏,由于版权等原因,这个英雄在后期版本中被删除了,如果玩家这是漏单处理,会在服务器获得一个丢弃的英雄,产生数据异常。 
    我的处理是,如果是英雄,检测英雄在本地hero.csv中是否有效,如果有效,检测这个英雄是否已经拥有,如果没有且数据正常,发送给服务器处理漏单,否则丢弃掉这条漏单。 
    还有说苹果服务器漏单过期的说法,不过我没有遇到过,没做处理。
  • 服务器和客户端漏单对应顺序 
    遇到过这种情况,客户端产生了多个漏单,发送给游戏服务器验证,游戏服务器请求苹果服务,苹果服务器返回的receipt的json数据中包含一个所有未处理的订单列表,最后产生的购买数据在最后,客户端的漏单顺序和服务器的验证顺序要保持一致。

确保receipt-data的成功提交与异常处理

建立在IAP Server Model的基础上,并且我们知道手机网络是不稳定的,在付款成功后不能确保把receipt-data一定提交到服务器。如果出现了这样的情况,那就意味着玩家被appstore扣费了,却没收到服务器发放的道具。 

解决这个问题的方法是在客户端提交receipt-data给我们的服务器,让我们的服务器向苹果服务器发送验证请求,验证这个receipt-data账单的有效性. 在没有收到回复之前,客户端必须要把receipt-data保存好,并且定期或在合理的UI界面触发向服务端发起请求,直至收到服务端的回复后删除客户端的receipt账单记录。这里就是我在开头提到的漏单处理了。 
如果是客户端没成功提交receipt-data,那怎么办?就是玩家被扣费了,也收到appstore的消费收据了,却依然没收到游戏道具,于是投诉到游戏客服处。 
这种情况在以往的经验中也会出现,常见的玩家和游戏运营商发生的纠纷。游戏客服向玩家索要游戏账号和appstore的收据单号,通过查询itunes-connect看是否确有这笔订单。如果订单存在,则要联系研发方去查询游戏服务器,看订单号与玩家名是否对应,并且是否已经被使用了,做这一点检查的目的是 为了防止恶意玩家利用已经使用过了的订单号进行欺骗(已验证的账单是可以再次请求验证的,曾经为了测试,将账单手动发给服务器处理并成功),谎称自己没收到商品。这就是上面一节IAP Server Model中红字所提到的安全逻辑的目的。当然了,如果查不到这个订单号,就意味着这个订单确实还没使用过,手动给玩家补发商品即可。 
有朋友问怎么通过itunes-connect查看具体订单,itunes-connect中无法直接看到订单信息,可以用以下方法来查询

  1. 可以通过账单向苹果发送账单验证,有效可以手动补发
  2. 用自己的服务器的记录账单列表对
  3. 利用第三方的TalkingData等交易函数,会自动记录账单数据
你可能感兴趣的文章
图片处理之 截取图片
查看>>
MPMoviePlayerController使用,以…
查看>>
iSecret 官方资料
查看>>
iSecret 1.1 正在审核中
查看>>
IOS开发的开源库
查看>>
IOS开发的开源库
查看>>
iSecret 1.1 正式发布 Congratulat…
查看>>
正则表达式语法
查看>>
SQL语句求总数、求平均数、降序排…
查看>>
计算字符串中各个字符出现的次数
查看>>
双层状态栏问题 跟踪状态栏Fr…
查看>>
CGRectInset、CGRectOffset…
查看>>
总结Objective-C中CGGeometry几何…
查看>>
CPM,CPC,CPL,CPS广告术语大全
查看>>
链表反转 58面试“留念”
查看>>
我的博客今天0岁200天了,我领取了…
查看>>
layoutSubviews 和 layoutIf…
查看>>
关于request.getRealPath(…
查看>>
《转》搞定学习《unix环境高级编程…
查看>>
对应iPhone5 长屏幕的方法 我找到…
查看>>