IOS IAP APP内支付 Java服务端代码
场景:作为后台需要为app提供服务,在ios中,app内进行支付购买时需要进行二次验证。
基础:可以参考上一篇转载的博文In-App Purchase(iap)快速指南了解原理。
直接先上服务端测试通过的代码:
import java.io.BufferedOutputStream; import java.io.BufferedReader; import java.io.InputStreamReader; import java.net.URL; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.SSLContext; import javax.net.ssl.TrustManager; import javax.net.ssl.X509TrustManager; import org.apache.commons.lang.StringUtils; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import com.alibaba.fastjson.JSONObject; @Controller @RequestMapping("iap") public class IapController { //购买凭证验证地址 private static final String certificateUrl = "https://buy.itunes.apple.com/verifyReceipt"; //测试的购买凭证验证地址 private static final String certificateUrlTest = "https://sandbox.itunes.apple.com/verifyReceipt"; /** * 重写X509TrustManager */ private static TrustManager myX509TrustManager = new X509TrustManager() { @Override public X509Certificate[] getAcceptedIssuers() { return null; } @Override public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { } @Override public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { } }; /** * 接收iOS端发过来的购买凭证 * @param userId * @param receipt * @param chooseEnv */ @RequestMapping("/setIapCertificate") public String setIapCertificate(String userId, String receipt, boolean chooseEnv){ if(StringUtils.isEmpty(userId) || StringUtils.isEmpty(receipt)){ return null; } String url = null; url = chooseEnv == true? certificateUrl:certificateUrlTest; final String certificateCode = receipt; if(StringUtils.isNotEmpty(certificateCode)){ return sendHttpsCoon(url, certificateCode); }else{ return null; } } /** * 发送请求 * @param url * @param strings * @return */ private String sendHttpsCoon(String url, String code){ if(url.isEmpty()){ return null; } try { //设置SSLContext SSLContext ssl = SSLContext.getInstance("SSL"); ssl.init(null, new TrustManager[]{myX509TrustManager}, null); //打开连接 HttpsURLConnection conn = (HttpsURLConnection) new URL(url).openConnection(); //设置套接工厂 conn.setSSLSocketFactory(ssl.getSocketFactory()); //加入数据 conn.setRequestMethod("POST"); conn.setDoOutput(true); conn.setRequestProperty("Content-type","application/json"); JSONObject obj = new JSONObject(); obj.put("receipt-data", code); BufferedOutputStream buffOutStr = new BufferedOutputStream(conn.getOutputStream()); buffOutStr.write(obj.toString().getBytes()); buffOutStr.flush(); buffOutStr.close(); //获取输入流 BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream())); String line = null; StringBuffer sb = new StringBuffer(); while((line = reader.readLine())!= null){ sb.append(line); } return sb.toString(); } catch (Exception e) { return null; } } }
注意:
1. setIapCertificate()方法的输入输出参数自行与ios商定,这里是直接将apple返回的数据返给了app(个人推荐返回boolean,前台只要知道验证是否通过即可)。
2. 参数receipt:BASE64编码过的话
ewoJInNpZ25hdHVyZSIgPSAiQXAzbjVUZmQrVXIvQmFrTEU3VG9Dc3NOSGdxbmI2dnlrM0RLZlFxcnBxQStLV1AzVHVkZDN2N2w0OUEyc2NVY1g2cWNDeEx3bHAzR2RKMG9Ma3IzRzdTaEZFYU5UeTBrL25hRTNoWUIxY0kxajFGM1ppSmxTVC9kL1VDN0ZFY1BUMjR2SUFFZDE2WXFOdHNqWUpNYmJQVGRRR3g4NitFZWlDR21mVDJKS0VrK0FBQURWekNDQTFNd2dnSTdvQU1DQVFJQ0NCdXA0K1BBaG0vTE1BMEdDU3FHU0liM0RRRUJCUVVBTUg4eEN6QUpCZ05WQkFZVEFsVlRNUk13RVFZRFZRUUtEQXBCY0hCc1pTQkpibU11TVNZd0pBWURWUVFMREIxQmNIQnNaU0JEWlhKMGFXWnBZMkYwYVc5dUlFRjFkR2h2Y21sMGVURXpNREVHQTFVRUF3d3FRWEJ3YkdVZ2FWUjFibVZ6SUZOMGIzSmxJRU5sY25ScFptbGpZWFJwYjI0Z1FYVjBhRzl5YVhSNU1CNFhEVEUwTURZd056QXdNREl5TVZvWERURTJNRFV4T0RFNE16RXpNRm93WkRFak1DRUdBMVVFQXd3YVVIVnlZMmhoYzJWU1pXTmxhWEIwUTJWeWRHbG1hV05oZEdVeEd6QVpCZ05WQkFzTUVrRndjR3hsSUdsVWRXNWxjeUJUZEc5eVpURVRNQkVHQTFVRUNnd0tRWEJ3YkdVZ1NXNWpMakVMTUFrR0ExVUVCaE1DVlZNd2daOHdEUVlKS29aSWh2Y05BUUVCQlFBRGdZMEFNSUdKQW9HQkFNbVRFdUxnamltTHdSSnh5MW9FZjBlc1VORFZFSWU2d0Rzbm5hbDE0aE5CdDF2MTk1WDZuOTNZTzdnaTNvclBTdXg5RDU1NFNrTXArU2F5Zzg0bFRjMzYyVXRtWUxwV25iMzRucXlHeDlLQlZUeTVPR1Y0bGpFMU93QytvVG5STStRTFJDbWVOeE1iUFpoUzQ3VCtlWnRERWhWQjl1c2szK0pNMkNvZ2Z3bzdBZ01CQUFHamNqQndNQjBHQTFVZERnUVdCQlNKYUVlTnVxOURmNlpmTjY4RmUrSTJ1MjJzc0RBTUJnTlZIUk1CQWY4RUFqQUFNQjhHQTFVZEl3UVlNQmFBRkRZZDZPS2RndElCR0xVeWF3N1hRd3VSV0VNNk1BNEdBMVVkRHdFQi93UUVBd0lIZ0RBUUJnb3Foa2lHOTJOa0JnVUJCQUlGQURBTkJna3Foa2lHOXcwQkFRVUZBQU9DQVFFQWVhSlYyVTUxcnhmY3FBQWU1QzIvZkVXOEtVbDRpTzRsTXV0YTdONlh6UDFwWkl6MU5ra0N0SUl3ZXlOajVVUllISytIalJLU1U5UkxndU5sMG5rZnhxT2JpTWNrd1J1ZEtTcTY5Tkluclp5Q0Q2NlI0Szc3bmI5bE1UQUJTU1lsc0t0OG9OdGxoZ1IvMWtqU1NSUWNIa3RzRGNTaVFHS01ka1NscDRBeVhmN3ZuSFBCZTR5Q3dZVjJQcFNOMDRrYm9pSjNwQmx4c0d3Vi9abEwyNk0ydWVZSEtZQ3VYaGRxRnd4VmdtNTJoM29lSk9PdC92WTRFY1FxN2VxSG02bTAzWjliN1BSellNMktHWEhEbU9Nazd2RHBlTVZsTERQU0dZejErVTNzRHhKemViU3BiYUptVDdpbXpVS2ZnZ0VZN3h4ZjRjemZIMHlqNXdOelNHVE92UT09IjsKCSJwdXJjaGFzZS1pbmZvIiA9ICJld29KSW05eWFXZHBibUZzTFhCMWNtTm9ZWE5sTFdSaGRHVXRjSE4wSWlBOUlDSXlNREUyTFRBMExUSTRJREF6T2pFNE9qUTVJRUZ0WlhKcFkyRXZURzl6WDBGdVoyVnNaWE1pT3dvSkluVnVhWEYxWlMxcFpHVnVkR2xtYVdWeUlpQTlJQ0prTkdVM01qRmxZelkzWldZeVptVmpZVGRtWW1SaVpESTFZVFExWTJaaU16ZGxNVEJsWVRkaUlqc0tDU0p2Y21sbmFXNWhiQzEwY21GdWMyRmpkR2x2YmkxcFpDSWdQU0FpTVRBd01EQXdNREl3T0RZeU1EUTNNQ0k3Q2draVluWnljeUlnUFNBaU1TNHhJanNLQ1NKMGNtRnVjMkZqZEdsdmJpMXBaQ0lnUFNBaU1UQXdNREF3TURJd09EWXlNRFEzTUNJN0Nna2ljWFZoYm5ScGRIa2lJRDBnSWpFaU93b0pJbTl5YVdkcGJtRnNMWEIxY21Ob1lYTmxMV1JoZEdVdGJYTWlJRDBnSWpFME5qRTRNemczTWpreU9EVWlPd29KSW5WdWFYRjFaUzEyWlc1a2IzSXRhV1JsYm5ScFptbGxjaUlnUFNBaU9FVXhPVVZGUXpRdE16TkVOeTAwTlRNMkxVSTJNa1V0TVRFeVFrRkROamhGUlVORUlqc0tDU0p3Y205a2RXTjBMV2xrSWlBOUlDSXhNalEwSWpzS0NTSnBkR1Z0TFdsa0lpQTlJQ0l4TVRBNE56azRNVFV4SWpzS0NTSmlhV1FpSUQwZ0ltTnZiUzVrYjJOMGIzSkllWE1pT3dvSkluQjFjbU5vWVhObExXUmhkR1V0YlhNaUlEMGdJakUwTmpFNE16ZzNNamt5T0RVaU93b0pJbkIxY21Ob1lYTmxMV1JoZEdVaUlEMGdJakl3TVRZdE1EUXRNamdnTVRBNk1UZzZORGtnUlhSakwwZE5WQ0k3Q2draWNIVnlZMmhoYzJVdFpHRjBaUzF3YzNRaUlEMGdJakl3TVRZdE1EUXRNamdnTURNNk1UZzZORGtnUVcxbGNtbGpZUzlNYjNOZlFXNW5aV3hsY3lJN0Nna2liM0pwWjJsdVlXd3RjSFZ5WTJoaGMyVXRaR0YwWlNJZ1BTQWlNakF4Tmkwd05DMHlPQ0F4TURveE9EbzBPU0JGZEdNdlIwMVVJanNLZlE9PSI7CgkiZW52aXJvbm1lbnQiID0gIlNhbmRib3giOwoJInBvZCIgPSAiMTAwIjsKCSJzaWduaW5nLXN0YXR1cyIgPSAiMCI7Cn0=
未进行编码的话:
{
"signature" = "Ap3n5Tfd+Ur/BakLE7ToCssNHgqnb6vyk3DKfQqrpqA+KWP3Tudd3v7l49A2scUcX6qcCxLwlp3GdJ0oLkr3G7ShFEaNTy0k/naE3hYB1cI1j1F3ZiJlST/d/UC7FEcPT24vIAEd16YqNtsjYJMbbPTdQGx86+EeiCGmfT2JKEk+AAADVzCCA1MwggI7oAMCAQICCBup4+PAhm/LMA0GCSqGSIb3DQEBBQUAMH8xCzAJBgNVBAYTAlVTMRMwEQYDVQQKDApBcHBsZSBJbmMuMSYwJAYDVQQLDB1BcHBsZSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEzMDEGA1UEAwwqQXBwbGUgaVR1bmVzIFN0b3JlIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTE0MDYwNzAwMDIyMVoXDTE2MDUxODE4MzEzMFowZDEjMCEGA1UEAwwaUHVyY2hhc2VSZWNlaXB0Q2VydGlmaWNhdGUxGzAZBgNVBAsMEkFwcGxlIGlUdW5lcyBTdG9yZTETMBEGA1UECgwKQXBwbGUgSW5jLjELMAkGA1UEBhMCVVMwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMmTEuLgjimLwRJxy1oEf0esUNDVEIe6wDsnnal14hNBt1v195X6n93YO7gi3orPSux9D554SkMp+Sayg84lTc362UtmYLpWnb34nqyGx9KBVTy5OGV4ljE1OwC+oTnRM+QLRCmeNxMbPZhS47T+eZtDEhVB9usk3+JM2Cogfwo7AgMBAAGjcjBwMB0GA1UdDgQWBBSJaEeNuq9Df6ZfN68Fe+I2u22ssDAMBgNVHRMBAf8EAjAAMB8GA1UdIwQYMBaAFDYd6OKdgtIBGLUyaw7XQwuRWEM6MA4GA1UdDwEB/wQEAwIHgDAQBgoqhkiG92NkBgUBBAIFADANBgkqhkiG9w0BAQUFAAOCAQEAeaJV2U51rxfcqAAe5C2/fEW8KUl4iO4lMuta7N6XzP1pZIz1NkkCtIIweyNj5URYHK+HjRKSU9RLguNl0nkfxqObiMckwRudKSq69NInrZyCD66R4K77nb9lMTABSSYlsKt8oNtlhgR/1kjSSRQcHktsDcSiQGKMdkSlp4AyXf7vnHPBe4yCwYV2PpSN04kboiJ3pBlxsGwV/ZlL26M2ueYHKYCuXhdqFwxVgm52h3oeJOOt/vY4EcQq7eqHm6m03Z9b7PRzYM2KGXHDmOMk7vDpeMVlLDPSGYz1+U3sDxJzebSpbaJmT7imzUKfggEY7xxf4czfH0yj5wNzSGTOvQ==";
"purchase-info" = "ewoJIm9yaWdpbmFsLXB1cmNoYXNlLWRhdGUtcHN0IiA9ICIyMDE2LTA0LTI4IDAzOjE4OjQ5IEFtZXJpY2EvTG9zX0FuZ2VsZXMiOwoJInVuaXF1ZS1pZGVudGlmaWVyIiA9ICJkNGU3MjFlYzY3ZWYyZmVjYTdmYmRiZDI1YTQ1Y2ZiMzdlMTBlYTdiIjsKCSJvcmlnaW5hbC10cmFuc2FjdGlvbi1pZCIgPSAiMTAwMDAwMDIwODYyMDQ3MCI7CgkiYnZycyIgPSAiMS4xIjsKCSJ0cmFuc2FjdGlvbi1pZCIgPSAiMTAwMDAwMDIwODYyMDQ3MCI7CgkicXVhbnRpdHkiID0gIjEiOwoJIm9yaWdpbmFsLXB1cmNoYXNlLWRhdGUtbXMiID0gIjE0NjE4Mzg3MjkyODUiOwoJInVuaXF1ZS12ZW5kb3ItaWRlbnRpZmllciIgPSAiOEUxOUVFQzQtMzNENy00NTM2LUI2MkUtMTEyQkFDNjhFRUNEIjsKCSJwcm9kdWN0LWlkIiA9ICIxMjQ0IjsKCSJpdGVtLWlkIiA9ICIxMTA4Nzk4MTUxIjsKCSJiaWQiID0gImNvbS5kb2N0b3JIeXMiOwoJInB1cmNoYXNlLWRhdGUtbXMiID0gIjE0NjE4Mzg3MjkyODUiOwoJInB1cmNoYXNlLWRhdGUiID0gIjIwMTYtMDQtMjggMTA6MTg6NDkgRXRjL0dNVCI7CgkicHVyY2hhc2UtZGF0ZS1wc3QiID0gIjIwMTYtMDQtMjggMDM6MTg6NDkgQW1lcmljYS9Mb3NfQW5nZWxlcyI7Cgkib3JpZ2luYWwtcHVyY2hhc2UtZGF0ZSIgPSAiMjAxNi0wNC0yOCAxMDoxODo0OSBFdGMvR01UIjsKfQ==";
"environment" = "Sandbox";
"pod" = "100";
"signing-status" = "0";
}
3. 向苹果发起验证请求的参数一定要通过json格式发送:
conn.setRequestProperty("Content-type","application/json");
JSONObject obj = new JSONObject();
obj.put("receipt-data", code);
4. App Store返回值也是一个JSON对象:
{
“status” : 0,
“receipt” : { … }
}
具体我这边是这样的:
<pre name="code" class="javascript">{
"receipt": {
"original_purchase_date_pst": "2016-04-28 03:18:49 America/Los_Angeles",
"purchase_date_ms": "1461838729285",
"unique_identifier": "d4e721ec67ef2feca7fbdbd25a45cfb37e10ea7b",
"original_transaction_id": "1000000208620470",
"bvrs": "1.1",
"transaction_id": "1000000208620470",
"quantity": "1",
"unique_vendor_identifier": "8E19EEC4-33D7-4536-B62E-112BAC68EECD",
"item_id": "1108798151",
"product_id": "1244",
"purchase_date": "2016-04-28 10:18:49 Etc/GMT",
"original_purchase_date": "2016-04-28 10:18:49 Etc/GMT",
"purchase_date_pst": "2016-04-28 03:18:49 America/Los_Angeles",
"bid": "com.doctorHys",
"original_purchase_date_ms": "1461838729285"
},
"status": 0
}