In-App Purchase iap 内付费 二次验证代码 (java 服务器端)
参考网址:https://blog.csdn.net/a351945755/article/details/22919533
package com.yichangmao.buyVerify.Comm.ios;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.security.MessageDigest;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Locale;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import net.sf.json.JSONObject;
import sun.misc.BASE64Decoder;
import com.yichangmao.buyVerify.R;
import com.yichangmao.buyVerify.Comm.FileUtil;
public class IOS_Verify {
private static class TrustAnyTrustManager implements X509TrustManager {
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[]{};
}
}
private static class TrustAnyHostnameVerifier implements HostnameVerifier {
public boolean verify(String hostname, SSLSession session) {
return true;
}
}
private static final String url_sandbox="https://sandbox.itunes.apple.com/verifyReceipt";
private static final String url_verify="https://buy.itunes.apple.com/verifyReceipt";
/**
* 苹果服务器验证
* @param receipt 账单
* @url 要验证的地址
* @return null 或返回结果
* 沙盒 https://sandbox.itunes.apple.com/verifyReceipt
*
*/
public static String buyAppVerify(String receipt,String verifyState)
{
String url=url_verify;
if(verifyState!=null&&verifyState.equals("Sandbox")){
url=url_sandbox;
}
String buyCode=getBASE64(receipt);
try{
SSLContext sc = SSLContext.getInstance("SSL");
sc.init(null, new TrustManager[]{new TrustAnyTrustManager()}, new java.security.SecureRandom());
URL console = new URL(url);
HttpsURLConnection conn = (HttpsURLConnection) console.openConnection();
conn.setSSLSocketFactory(sc.getSocketFactory());
conn.setHostnameVerifier(new TrustAnyHostnameVerifier());
conn.setRequestMethod("POST");
conn.setRequestProperty("content-type", "text/json");
conn.setRequestProperty("Proxy-Connection", "Keep-Alive");
conn.setDoInput(true);
conn.setDoOutput(true);
BufferedOutputStream hurlBufOus=new BufferedOutputStream(conn.getOutputStream());
String str= String.format(Locale.CHINA,"{\"receipt-data\":\"" + buyCode+"\"}");
hurlBufOus.write(str.getBytes());
hurlBufOus.flush();
InputStream is = conn.getInputStream();
BufferedReader reader=new BufferedReader(new InputStreamReader(is));
String line = null;
StringBuffer sb = new StringBuffer();
while((line = reader.readLine()) != null){
sb.append(line);
}
return sb.toString();
}catch(Exception ex)
{
ex.printStackTrace();
}
return null;
}
/**
* 根据原始收据返回苹果的验证地址:
* * 沙箱 https://sandbox.itunes.apple.com/verifyReceipt
* 真正的地址 https://buy.itunes.apple.com/verifyReceipt
* @param receipt
* @return Sandbox 测试单 Real 正式单
*/
public static String getEnvironment(String receipt)
{
try{
JSONObject job = JSONObject.fromObject(receipt);
if(job.containsKey("environment")){
String evvironment=job.getString("environment");
return evvironment;
}
}catch(Exception ex){
ex.printStackTrace();
}
return "Real";
}
/**
* 用BASE64加密
* @param str
* @return
*/
public static String getBASE64(String str) {
byte[] b = str.getBytes();
String s = null;
if (b != null) {
s = new sun.misc.BASE64Encoder().encode(b);
}
return s;
}
/**
* 解密BASE64字窜
* @param s
* @return
*/
public static String getFromBASE64(String s) {
byte[] b = null;
if (s != null) {
BASE64Decoder decoder = new BASE64Decoder();
try {
b = decoder.decodeBuffer(s);
return new String(b);
} catch (Exception e) {
e.printStackTrace();
}
}
return new String(b);
}
/**
* md5加密方法
* @author: zhengsunlei
* Jul 30, 2010 4:38:28 PM
* @param plainText 加密字符串
* @return String 返回32位md5加密字符串(16位加密取substring(8,24))
* 每位工程师都有保持代码优雅的义务
* each engineer has a duty to keep the code elegant
*/
public final static String md5(String plainText) {
// 返回字符串
String md5Str = null;
try {
// 操作字符串
StringBuffer buf = new StringBuffer();
/**
* MessageDigest 类为应用程序提供信息摘要算法的功能,如 MD5 或 SHA 算法。
* 信息摘要是安全的单向哈希函数,它接收任意大小的数据,并输出固定长度的哈希值。
*
* MessageDigest 对象开始被初始化。
* 该对象通过使用 update()方法处理数据。
* 任何时候都可以调用 reset()方法重置摘要。
* 一旦所有需要更新的数据都已经被更新了,应该调用digest()方法之一完成哈希计算。
*
* 对于给定数量的更新数据,digest 方法只能被调用一次。
* 在调用 digest 之后,MessageDigest 对象被重新设置成其初始状态。
*/
MessageDigest md = MessageDigest.getInstance("MD5");
// 添加要进行计算摘要的信息,使用 plainText 的 byte 数组更新摘要。
md.update(plainText.getBytes());
// 计算出摘要,完成哈希计算。
byte b[] = md.digest();
int i;
for (int offset = 0; offset < b.length; offset++) {
i = b[offset];
if (i < 0) {
i += 256;
}
if (i < 16) {
buf.append("0");
}
// 将整型 十进制 i 转换为16位,用十六进制参数表示的无符号整数值的字符串表示形式。
buf.append(Integer.toHexString(i));
}
// 32位的加密
md5Str = buf.toString();
// 16位的加密
// md5Str = buf.toString().md5Strstring(8,24);
} catch (Exception e) {
e.printStackTrace();
}
return md5Str;
}
}
之前一个项目做的代码全部不见了,过了一段时间后想拿来用,发现早己不了,不得己,摸了半天才弄成功了,汗,不是做IOS开发的,见笑。
1、 在IOS前端拿到的收据(Receipt)格式是:
--------------------------分割线------------------------------------------------------
{
"signature" = "Am7vrfmY+FJq9g8gJDdYMGWOBJiKUUz80nAHooQFwYEZAL9wdXU7uaMiSZn75JQUjO3XfydLs2bwm9VPoKYKTGcft0LrISl7YNlQAWeVfA62F2E1qgTAGVFoTF1k0o3hJR1D/bLoum3i5PrQiScV90s0V77WVon2+B6vqUtHLsZUAAADVzCCA1MwggI7oAMCAQICCGUUkU3ZWAS1MA0GCSqGSIb3DQEBBQUAMH8xCzAJBgNVBAYTAlVTMRMwEQYDVQQKDApBcHBsZSBJbmMuMSYwJAYDVQQLDB1BcHBsZSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEzMDEGA1UEAwwqQXBwbGUgaVR1bmVzIFN0b3JlIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTA5MDYxNTIyMDU1NloXDTE0MDYxNDIyMDU1NlowZDEjMCEGA1UEAwwaUHVyY2hhc2VSZWNlaXB0Q2VydGlmaWNhdGUxGzAZBgNVBAsMEkFwcGxlIGlUdW5lcyBTdG9yZTETMBEGA1UECgwKQXBwbGUgSW5jLjELMAkGA1UEBhMCVVMwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMrRjF2ct4IrSdiTChaI0g8pwv/cmHs8p/RwV/rt/91XKVhNl4XIBimKjQQNfgHsDs6yju++DrKJE7uKsphMddKYfFE5rGXsAdBEjBwRIxexTevx3HLEFGAt1moKx509dhxtiIdDgJv2YaVs49B0uJvNdy6SMqNNLHsDLzDS9oZHAgMBAAGjcjBwMAwGA1UdEwEB/wQCMAAwHwYDVR0jBBgwFoAUNh3o4p2C0gEYtTJrDtdDC5FYQzowDgYDVR0PAQH/BAQDAgeAMB0GA1UdDgQWBBSpg4PyGUjFPhJXCBTMzaN+mV8k9TAQBgoqhkiG92NkBgUBBAIFADANBgkqhkiG9w0BAQUFAAOCAQEAEaSbPjtmN4C/IB3QEpK32RxacCDXdVXAeVReS5FaZxc+t88pQP93BiAxvdW/3eTSMGY5FbeAYL3etqP5gm8wrFojX0ikyVRStQ+/AQ0KEjtqB07kLs9QUe8czR8UGfdM1EumV/UgvDd4NwNYxLQMg4WTQfgkQQVy8GXZwVHgbE/UC6Y7053pGXBk51NPM3woxhd3gSRLvXj+loHsStcTEqe9pBDpmG5+sk4tw+GK3GMeEN5/+e1QT9np/Kl1nj+aBw7C0xsy0bFnaAd1cSS6xdory/CUvM6gtKsmnOOdqTesbp0bs8sn6Wqs0C9dgcxRHuOMZ2tm8npLUm7argOSzQ==";
"purchase-info" = "ewoJIm9yaWdpbmFsLXB1cmNoYXNlLWRhdGUtcHN0IiA9ICIyMDE0LTAyLTEyIDAwOjQ1OjUzIEFtZXJpY2EvTG9zX0FuZ2VsZXMiOwoJInVuaXF1ZS1pZGVudGlmaWVyIiA9ICJmNzFjODA0YmNkMDkwMDg1ZDE3Y2YwN2UyODA1YzFiMGRmYTA1M2VhIjsKCSJvcmlnaW5hbC10cmFuc2FjdGlvbi1pZCIgPSAiMTAwMDAwMDEwMTI2NTU1MSI7CgkiYnZycyIgPSAiMS4wIjsKCSJ0cmFuc2FjdGlvbi1pZCIgPSAiMTAwMDAwMDEwMTI2NTU1MSI7CgkicXVhbnRpdHkiID0gIjEiOwoJIm9yaWdpbmFsLXB1cmNoYXNlLWRhdGUtbXMiID0gIjEzOTIxOTQ3NTMzNjgiOwoJInVuaXF1ZS12ZW5kb3ItaWRlbnRpZmllciIgPSAiRjYzRTdBMzUtMDQwNi00NDVGLUE1QUEtQ0M5OTc0RDRDQTlCIjsKCSJwcm9kdWN0LWlkIiA9ICJjb20ueWNtLnBubS53aTEiOwoJIml0ZW0taWQiID0gIjgwMjc5MzM1MiI7CgkiYmlkIiA9ICJjb20ueWNtLnBubSI7CgkicHVyY2hhc2UtZGF0ZS1tcyIgPSAiMTM5MjE5NDc1MzM2OCI7CgkicHVyY2hhc2UtZGF0ZSIgPSAiMjAxNC0wMi0xMiAwODo0NTo1MyBFdGMvR01UIjsKCSJwdXJjaGFzZS1kYXRlLXBzdCIgPSAiMjAxNC0wMi0xMiAwMDo0NTo1MyBBbWVyaWNhL0xvc19BbmdlbGVzIjsKCSJvcmlnaW5hbC1wdXJjaGFzZS1kYXRlIiA9ICIyMDE0LTAyLTEyIDA4OjQ1OjUzIEV0Yy9HTVQiOwp9";
"environment" = "Sandbox";
"pod" = "100";
"signing-status" = "0";
}
--------------------------分割线------------------------------------------------------
这是购买成功后,app store发回来的收据,我们需要把这个收据发给第三方服务器(我们的服务器)去验证。
2、在跟苹果服务器作二次验证之前,需要将上面的这个原始收据用base64编码过,然后组装成JOSN格式:
{"receipt-data":"base64编码过后的代码"}
* 沙箱 https://sandbox.itunes.apple.com/verifyReceipt
* 真正的地址 https://buy.itunes.apple.com/verifyReceipt
3、苹果服务器返回的格式也是一个JSON格式,拿到里面返回的值“status”,若为0 说明验证成功。
若成功,还会返回收据支付信息内容,格式如下:
{"original_purchase_date_pst":"2014-02-12 00:45:53 America/Los_Angeles","purchase_date_ms":"1392194753368","unique_identifier":"f71c804bcd090085d17cf07e2805c1b0dfa053ea","original_transaction_id":"1000000101265551","bvrs":"1.0","transaction_id":"1000000101265551","quantity":"1","unique_vendor_identifier":"F63E7A35-0406-445F-A5AA-CC9974D4CA9B","item_id":"802793352","product_id":"com.ycm.pnm.wi1","purchase_date":"2014-02-12 08:45:53 Etc/GMT","original_purchase_date":"2014-02-12 08:45:53 Etc/GMT","purchase_date_pst":"2014-02-12 00:45:53 America/Los_Angeles","bid":"com.ycm.pnm","original_purchase_date_ms":"1392194753368"}
--------------------代码为具体代码部分-----------------------------------------------------------------------------------------------
一、具体处理Action
/**
* @author qili
*
*/
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();
}
}
}
In-App Purchase iap 内付费 二次验证代码 (java 服务器端)的更多相关文章
- 苹果IOS内购二次验证返回state为21002的坑
项目是三四年前的老项目,之前有IOS内购二次验证的接口,貌似很久都没用了,然而最近IOS的妹子说接口用不了,让我看看啥问题.接口流程时很简单的,就是前端IOS在购买成功之后,接收到receipt后进行 ...
- 【JAVA】IOS内购二次验证及掉单问题解决
这个估计是我踩过的最大的坑,当时做微信支付的时候也没这么坑爹,当然他俩也半斤八两... 苹果官方明确表示:验证支付时,可能会有一定的延迟.第一次处理的时间就专注的解决这个问题了,忽略了掉单的问题(稍后 ...
- AppStore苹果应用支付开发(In App Purchase)翻译
http://yarin.blog.51cto.com/1130898/549141 一.In App Purchase概览 Store Kit代表App和App Store之间进行通信.程序将从Ap ...
- JAVA项目之苹果IAP内购JAVA服务器验证流程详解
1.前言 本博客是经历过多个项目检验的, 绝对真实, 适应于对苹果iap内购稍微有些了解的JAVA开发人员, 认真看, 定能完美解决苹果内购问题. 苹果IAP内购支付实际上是"将客户端支 ...
- <转>iOS应用程序内使用IAP/StoreKit付费、沙盒(SandBox)测试、创建测试账号流程!
原文地址:http://blog.csdn.net/xiaominghimi/article/details/6937097 //——2012-12-11日更新 获取"产品付费数量等于0 ...
- [IPA]IOS In App Purchase(内购)验证
参考我之前的笔记 苹果内购笔记,在客户端向苹果购买成功之后,我们需要进行二次验证. 二次验证 IOS在沙箱环境下购买成功之后,向苹果进行二次验证,确认用户是否购买成功. 当应用向Apple服务器请求购 ...
- iOS应用内付费(IAP)开发步骤列表
iOS应用内付费(IAP)开发步骤列表 前两天和服务端同事一起,完成了应用内付费(以下简称IAP, In app purchase)的开发工作.步骤繁多,在此把开发步骤列表整理如下.因为只是步骤列表, ...
- [转]iOS 应用内付费(IAP)开发步骤
FROM : http://blog.csdn.net/xiaoxiangzhu660810/article/details/17434907 参考文章链接: (1)http://mobile.51c ...
- 【转】详解iOS应用程序内使用IAP/StoreKit付费、沙盒(SandBox)测试、创建测试账号流程
http://blog.csdn.net/xiaominghimi/article/details/6937097 //——2012-12-11日更新 获取"产品付费数量等于0这个问题& ...
随机推荐
- Creazy Ideas 智能汽车和智能交通
1.现在无人驾驶靠的是摄像头监测障碍物,计算可行路线(这个结论是看到相关视频中百度无人驾驶测试中大大的摄像头推测的,非确切信息).而非像人一样对客观事物的认知学习得到障碍物的信息,如果能将AI融入进去 ...
- Python全栈开发-Day9-线程/GIL/事件/队列
本节内容 进程与线程的概念 Python threading 模块 GIL——global interpreter lock Mutex互斥锁(线程锁) Semaphore信号量 Events事件 Q ...
- hibernate例子
首先下载好hibernate包 如果运行之后出现数据库连接错误 则重新把jar包位置放在web-inf的lib下面再addbulidpath 第一步:创建实体类 Hibernate要求实体类必须提供 ...
- WCF开山篇__图片传输
WCF开山篇__图片传输 一. 简介 Windows Communication Foundation(WCF)是由微软发展的一组数据通信的应用程序接口,可以翻译为Windows通讯接口,它是. ...
- (转)python 全栈开发,Day74(基于双下划线的跨表查询,聚合查询,分组查询,F查询,Q查询)
昨日内容回顾 # 一对多的添加方式1(推荐) # book=Book.objects.create(title="水浒传",price=100,pub_date="164 ...
- 关于JAVA项目中的常用的异常处理情况总结
1. JAVA异常处理 在面向过程式的编程语言中,我们可以通过返回值来确定方法是否正常执行.比如在一个c语言编写的程序中,如果方法正确的执行则返回1.错误则返回0.在vb或delphi开发的应用程序中 ...
- CF-825E Minimal Labels 反向拓扑排序
http://codeforces.com/contest/825/problem/E 一道裸的拓扑排序题.为什么需要反向拓扑排序呢?因为一条大下标指向小下标的边可能会导致小下标更晚分配到号码,导致字 ...
- 02 flask 请求钩子、异常捕获、上下文、Flask-Script 扩展、jinja2 模板引擎、csrf防范
一 请求勾子 在客户端和服务器交互的过程中,有些准备工作或扫尾工作需要处理,比如: 在请求开始时,建立数据库连接: 在请求开始时,根据需求进行权限校验: 在请求结束时,指定数据的交互格式: 为了让每个 ...
- 『MXNet』第十一弹_符号式编程初探
一.符号分类 符号对我们想要进行的计算进行了描述, 下图展示了符号如何对计算进行描述. 我们定义了符号变量A, 符号变量B, 生成了符号变量C, 其中, A, B为参数节点, C为内部节点! mxne ...
- 【Java】【2】String和List相互转换
正文: 1,String转List //常见的为逗号分隔 String str = "a,b,c"; List<String> list1 = Arrays.asLis ...