参考网址: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 服务器端)的更多相关文章

  1. 苹果IOS内购二次验证返回state为21002的坑

    项目是三四年前的老项目,之前有IOS内购二次验证的接口,貌似很久都没用了,然而最近IOS的妹子说接口用不了,让我看看啥问题.接口流程时很简单的,就是前端IOS在购买成功之后,接收到receipt后进行 ...

  2. 【JAVA】IOS内购二次验证及掉单问题解决

    这个估计是我踩过的最大的坑,当时做微信支付的时候也没这么坑爹,当然他俩也半斤八两... 苹果官方明确表示:验证支付时,可能会有一定的延迟.第一次处理的时间就专注的解决这个问题了,忽略了掉单的问题(稍后 ...

  3. AppStore苹果应用支付开发(In App Purchase)翻译

    http://yarin.blog.51cto.com/1130898/549141 一.In App Purchase概览 Store Kit代表App和App Store之间进行通信.程序将从Ap ...

  4. JAVA项目之苹果IAP内购JAVA服务器验证流程详解

    1.前言 本博客是经历过多个项目检验的, 绝对真实, 适应于对苹果iap内购稍微有些了解的JAVA开发人员,  认真看,  定能完美解决苹果内购问题. 苹果IAP内购支付实际上是"将客户端支 ...

  5. <转>iOS应用程序内使用IAP/StoreKit付费、沙盒(SandBox)测试、创建测试账号流程!

    原文地址:http://blog.csdn.net/xiaominghimi/article/details/6937097 //——2012-12-11日更新   获取"产品付费数量等于0 ...

  6. [IPA]IOS In App Purchase(内购)验证

    参考我之前的笔记 苹果内购笔记,在客户端向苹果购买成功之后,我们需要进行二次验证. 二次验证 IOS在沙箱环境下购买成功之后,向苹果进行二次验证,确认用户是否购买成功. 当应用向Apple服务器请求购 ...

  7. iOS应用内付费(IAP)开发步骤列表

    iOS应用内付费(IAP)开发步骤列表 前两天和服务端同事一起,完成了应用内付费(以下简称IAP, In app purchase)的开发工作.步骤繁多,在此把开发步骤列表整理如下.因为只是步骤列表, ...

  8. [转]iOS 应用内付费(IAP)开发步骤

    FROM : http://blog.csdn.net/xiaoxiangzhu660810/article/details/17434907 参考文章链接: (1)http://mobile.51c ...

  9. 【转】详解iOS应用程序内使用IAP/StoreKit付费、沙盒(SandBox)测试、创建测试账号流程

    http://blog.csdn.net/xiaominghimi/article/details/6937097 //——2012-12-11日更新   获取"产品付费数量等于0这个问题& ...

随机推荐

  1. 学习笔记57—归一化 (Normalization)、标准化 (Standardization)和中心化/零均值化 (Zero-centered)

    1 概念   归一化:1)把数据变成(0,1)或者(1,1)之间的小数.主要是为了数据处理方便提出来的,把数据映射到0-1范围之内处理,更加便捷快速.2)把有量纲表达式变成无量纲表达式,便于不同单位或 ...

  2. 学习笔记46—如何使Word和EndNote关联

    1)打开Word文件项目中的选项,然后点击加载项, 2)找到Endnote安装目录,选择目录中的Configure EndNote.exe,选中configuration endnote compon ...

  3. CSRF攻击和防护

    攻击模拟步骤: 防护方法: 在客户端向后端请求界面数据的时候,后端会往响应中的 cookie 中设置 csrf_token 的值 在 Form 表单中添加一个隐藏的的字段,值也是 csrf_token ...

  4. 第 4 章 容器 - 028 - 限制容器对CPU的使用

    限制容器对CPU的使用 默认设置下,所有容器可以平等地使用 host CPU 资源并且没有限制. Docker 可以通过 -c 或 --cpu-shares 设置容器使用 CPU 的权重.如果不指定, ...

  5. linux下逻辑卷管理 调整分区大小

    [root@localhost ~]# df -h Filesystem Size Used Avail Use% Mounted on /dev/mapper/VolGroup-lv_root 50 ...

  6. centos 下卸载mysql

    查看当前已安装服务 [root@localhost]# rpm -qa|grep -i mysqlMySQL-server-5.6.36-1.rhel5.x86_64qt-mysql-4.8.5-13 ...

  7. English trip V1 - B 17. Giving Information 提供信息 Teacher:Taylor Key: Person Information

    In this lesson you will learn to say your phone number and address.  这节课讲学习说你的手机号码和地址. 课上内容(Lesson) ...

  8. IntelliJ IDEA 第一个 Scala 程序

    IntelliJ 安装完成 Scala 插件后,你需要尝试使用 IntelliJ 来创建并且运行第一个程序. 通常这个程序只是简单的输出 Hello World. 创建一个新工程 在文件下面选择新建, ...

  9. 59 Cookie 与 Session

    Cookie Cookie是由服务器创建,然后通过响应发送给客户端的一个键值对. 客户端会保存Cookie,并会标注出Cookie的来源(哪个服务器的Cookie). 当客户端向服务器发出请求时会把所 ...

  10. 原生JS实现瀑布流布局

    瀑布流,又称瀑布流式布局.是比较流行的一种网站页面布局,视觉表现为参差不齐的多栏布局,随着页面滚动条向下滚动,这种布局还会不断加载数据块并附加至当前尾部. 1.首先瀑布流所有的图片应该保持宽度一致,高 ...