All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.badlogic.gdx.pay.server.impl.PurchaseVerifieriOSApple Maven / Gradle / Ivy

/*******************************************************************************
 * Copyright 2011 See AUTHORS file.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 ******************************************************************************/

package com.badlogic.gdx.pay.server.impl;

import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;

import javax.net.ssl.HttpsURLConnection;

import com.badlogic.gdx.pay.PurchaseManagerConfig;
import com.badlogic.gdx.pay.Transaction;

/** Purchase verifier for iOS/Apple. Return true if the purchase appears valid.
 * @author noblemaster */
public class PurchaseVerifieriOSApple extends PurchaseVerifierBase {

	// sandbox URL
	private final static String SANDBOX_URL = "https://sandbox.itunes.apple.com/verifyReceipt";
	// production URL
	private final static String PRODUCTION_URL = "https://buy.itunes.apple.com/verifyReceipt";

	/** True for sandbox mode. */
	private boolean sandbox;
	
	public PurchaseVerifieriOSApple() {
		this(false);
	}
	
	public PurchaseVerifieriOSApple (boolean sandbox) {
		this.sandbox = sandbox;
	}

	@Override
	public String storeName () {
		return PurchaseManagerConfig.STORE_NAME_IOS_APPLE;
	}

	@Override
	public boolean isValid (Transaction transaction) {
		// the transaction data is our original == receipt!
		String receipt = transaction.getTransactionDataSignature();

		final String jsonData = "{\"receipt-data\" : \"" + receipt + "\"}";
		try {
			// send the data to Apple
			final URL url = new URL(sandbox ? SANDBOX_URL : PRODUCTION_URL);
			final HttpURLConnection conn = (HttpsURLConnection)url.openConnection();
			conn.setRequestMethod("POST");
			conn.setDoOutput(true);
			conn.setRequestProperty("Content-Type", "application/json");
			conn.setRequestProperty("Accept", "application/json");
			final OutputStreamWriter wr = new OutputStreamWriter(conn.getOutputStream());
			wr.write(jsonData);
			wr.flush();

			// obtain the response
			int status = extractStatus(conn.getInputStream());

			wr.close();
			
			switch (status) {
				case -1: log(status + ": Status extraction failed"); return false;
				case 0: return true;
				case 21000: log(status + ": App store could not read"); return false;
				case 21002: log(status + ": Data was malformed"); return false;
				case 21003: log(status + ": Receipt not authenticated"); return false;
				case 21004: log(status + ": Shared secret does not match"); return false;
				case 21005: log(status + ": Receipt server unavailable"); return false;
				case 21006: log(status + ": Receipt valid but sub expired"); return false;
				case 21007: log(status + ": Sandbox receipt sent to Production environment"); return false;
				case 21008: log(status + ": Production receipt sent to Sandbox environment"); return false;
			   default:
			   	// unknown error code (nevertheless a problem)
			   	log("Unknown error: status code = " + status);
			   	return false;
			}
		} catch (IOException e) {
			// I/O-error: let's assume bad news...
			error("I/O error during verification: " + e, e);
			return false;
		}
	}

	/**
	 * Attempt to extract message from incoming json stream
	 * The contents should be something along the lines of '{"status":21004}'
	 * Override this method if you want to use more robust json parser
	 *
	 * @param inputStream input stream with json message
	 * @return extracted status or -1 if not possible
	 */
	protected int extractStatus (InputStream inputStream) {
		int status = -1;
		BufferedReader rd = null;
		try {
			rd = new BufferedReader(new InputStreamReader(inputStream));
			String line;
			// verify the response: something like {"status":21004} etc...
			final String search = "\"status\":";
			while ((line = rd.readLine()) != null) {
				int indexOf = line.indexOf(search);
				if (indexOf == -1) continue;
				int start = indexOf + search.length();
				while (Character.isWhitespace(line.charAt(start))) {
					start++;
				}
				int end = start + 1;
				while (Character.isDigit(line.charAt(end))) {
					end++;
				}
				status = Integer.parseInt(line.substring(start, end));
				break;
			}
		} catch (IOException ex) {
			error("Status extraction failed: " + ex, ex);
		} catch (NumberFormatException ex) {
			error("Status extraction failed: " + ex, ex);
		} catch (IndexOutOfBoundsException ex) {
			error("Status extraction failed: " + ex, ex);
		} finally {
			try {
				if (rd != null) rd.close();
			} catch (IOException ex) {
				error("Close failed: ", ex);
			}
		}
		return status;
	}
	
	/** Just used for testing... */
	public static void main(String[] args) {
		// test in sandbox-mode
		PurchaseVerifieriOSApple verifier = new PurchaseVerifieriOSApple(true);
		
		// our sample receipt for the sandbox (returns error 21004)
		String receipt = "{\n" +
			"\"signature\" = \"AluGxOuMy+RT1gkyFCoD1i1KT3KUZl+F5FAAW0ELBlCUbC9dW14876aW0OXBlNJ6pXbBBFB8K0LDy6LuoAS8iBiq3529aRbVRUSKCPeCDZ7apC2zqFYZ4N7bSFDMeb92wzN0X/dELxlkRH4bWjO67X7gnHcN47qHoVckSlGo/mpbAAADVzCCA1MwggI7oAMCAQICCGUUkU3ZWAS1MA0GCSqGSIb3DQEBBQUAMH8xCzAJBgNVBAYTAlVTMRMwEQYDVQQKDApBcHBsZSBJbmMuMSYwJAYDVQQLDB1BcHBsZSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEzMDEGA1UEAwwqQXBwbGUgaVR1bmVzIFN0b3JlIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTA5MDYxNTIyMDU1NloXDTE0MDYxNDIyMDU1NlowZDEjMCEGA1UEAwwaUHVyY2hhc2VSZWNlaXB0Q2VydGlmaWNhdGUxGzAZBgNVBAsMEkFwcGxlIGlUdW5lcyBTdG9yZTETMBEGA1UECgwKQXBwbGUgSW5jLjELMAkGA1UEBhMCVVMwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMrRjF2ct4IrSdiTChaI0g8pwv/cmHs8p/RwV/rt/91XKVhNl4XIBimKjQQNfgHsDs6yju++DrKJE7uKsphMddKYfFE5rGXsAdBEjBwRIxexTevx3HLEFGAt1moKx509dhxtiIdDgJv2YaVs49B0uJvNdy6SMqNNLHsDLzDS9oZHAgMBAAGjcjBwMAwGA1UdEwEB/wQCMAAwHwYDVR0jBBgwFoAUNh3o4p2C0gEYtTJrDtdDC5FYQzowDgYDVR0PAQH/BAQDAgeAMB0GA1UdDgQWBBSpg4PyGUjFPhJXCBTMzaN+mV8k9TAQBgoqhkiG92NkBgUBBAIFADANBgkqhkiG9w0BAQUFAAOCAQEAEaSbPjtmN4C/IB3QEpK32RxacCDXdVXAeVReS5FaZxc+t88pQP93BiAxvdW/3eTSMGY5FbeAYL3etqP5gm8wrFojX0ikyVRStQ+/AQ0KEjtqB07kLs9QUe8czR8UGfdM1EumV/UgvDd4NwNYxLQMg4WTQfgkQQVy8GXZwVHgbE/UC6Y7053pGXBk51NPM3woxhd3gSRLvXj+loHsStcTEqe9pBDpmG5+sk4tw+GK3GMeEN5/+e1QT9np/Kl1nj+aBw7C0xsy0bFnaAd1cSS6xdory/CUvM6gtKsmnOOdqTesbp0bs8sn6Wqs0C9dgcxRHuOMZ2tm8npLUm7argOSzQ==\";\n" +
			"\"purchase-info\" = \"ewoJInF1YW50aXR5IiA9ICIxIjsKCSJwdXJjaGFzZS1kYXRlIiA9ICIyMDExLTEwLTEyIDIwOjA1OjUwIEV0Yy9HTVQiOwoJIml0ZW0taWQiID0gIjQ3MjQxNTM1MyI7CgkiZXhwaXJlcy1kYXRlLWZvcm1hdHRlZCIgPSAiMjAxMS0xMC0xMiAyMDoxMDo1MCBFdGMvR01UIjsKCSJleHBpcmVzLWRhdGUiID0gIjEzMTg0NTAyNTAwMDAiOwoJInByb2R1Y3QtaWQiID0gImNvbS5kYWlseWJ1cm4ud29kMW1vbnRoIjsKCSJ0cmFuc2FjdGlvbi1pZCIgPSAiMTAwMDAwMDAwOTk1NzYwMiI7Cgkib3JpZ2luYWwtcHVyY2hhc2UtZGF0ZSIgPSAiMjAxMS0xMC0xMiAyMDowNTo1MiBFdGMvR01UIjsKCSJvcmlnaW5hbC10cmFuc2FjdGlvbi1pZCIgPSAiMTAwMDAwMDAwOTk1NzYwMiI7CgkiYmlkIiA9ICJjb20uZGFpbHlidXJuLndvZCI7CgkiYnZycyIgPSAiMC4wLjgiOwp9\";\n" +
			"\"environment\" = \"Sandbox\";\n" +
			"\"pod\" = \"100\";\n" +
			"\"signing-status\" = \"0\";\n" +
			"}\n";
	
		// build a sample transaction (only receipt is important for validation)
		Transaction transaction = new Transaction();
		transaction.setTransactionData(receipt);		
		if (verifier.isValid(transaction)) {
			System.out.println("Purchase is VALID!");
		}
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy