/** * Verifies that the data was signed with the given signature, and returns the list of verified * purchases. The data is in JSON format and contains a nonce (number used once) that we generated * and that was signed (as part of the whole data string) with a private key. The data also * contains the {@link PurchaseState} and product ID of the purchase. In the general case, there * can be an array of purchase transactions because there may be delays in processing the purchase * on the backend and then several purchases can be batched together. * * @param signedData the signed JSON string (signed, not encrypted) * @param signature the signature for the data, signed with the private key */ public static ArrayList<VerifiedPurchase> verifyPurchase(String signedData, String signature) { if (signedData == null) { Log.e(TAG, "data is null"); return null; } else if (StoreConfig.debug) { Log.d(TAG, "signedData: " + signedData); } boolean verified = false; // TODO: check this out... we might not want to verify when debugging. // if (!StoreConfig.debug) { if (TextUtils.isEmpty(signature)) { Log.w(TAG, "Empty signature. Stopping verification."); return null; } PublicKey key = Security.generatePublicKey(StoreConfig.publicKey); verified = Security.verify(key, signedData, signature); if (!verified) { Log.w(TAG, "signature does not match data."); return null; } // } JSONObject jObject; JSONArray jTransactionsArray = null; int numTransactions = 0; long nonce = 0L; try { jObject = new JSONObject(signedData); // The nonce might be null if the user backed out of the buy page. nonce = jObject.optLong("nonce"); jTransactionsArray = jObject.optJSONArray("orders"); if (jTransactionsArray != null) { numTransactions = jTransactionsArray.length(); } } catch (JSONException e) { return null; } if (!Security.isNonceKnown(nonce)) { Log.w(TAG, "Nonce not found: " + nonce); return null; } ArrayList<VerifiedPurchase> purchases = new ArrayList<VerifiedPurchase>(); try { for (int i = 0; i < numTransactions; i++) { JSONObject jElement = jTransactionsArray.getJSONObject(i); int response = jElement.getInt("purchaseState"); PurchaseState purchaseState = PurchaseState.valueOf(response); String productId = jElement.getString("productId"); String packageName = jElement.getString("packageName"); long purchaseTime = jElement.getLong("purchaseTime"); String orderId = jElement.optString("orderId", ""); String notifyId = null; if (jElement.has("notificationId")) { notifyId = jElement.getString("notificationId"); } String developerPayload = jElement.optString("developerPayload", null); // If the purchase state is PURCHASED, then we require a // verified nonce. if (purchaseState == PurchaseState.PURCHASED && !verified) { continue; } purchases.add( new VerifiedPurchase( purchaseState, notifyId, productId, orderId, purchaseTime, developerPayload)); } } catch (JSONException e) { Log.e(TAG, "JSON exception: ", e); return null; } removeNonce(nonce); return purchases; }