我已经在我的应用程序中实现了应用程序购买模块,我希望我的应用程序为用户执行自动续订订阅,因为我的代码很好。
问题在于,当我购买订阅时,google play会向我发送notifyid,通过该notifyid我可以获得购买历史记录,但在购买历史记录中我的购买响应为“null”。是否有办法获得订阅的购买响应以及让我知道google play后如何通知我们 续订。
我附上代码,我在JSON格式下获得购买回复。
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import com.surfwise.inapppurchase.Constans.PurchaseState;
import com.surfwise.inapppurchase.util.Base64;
import com.surfwise.inapppurchase.util.Base64DecoderException;
import android.text.TextUtils;
import android.util.Log;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.Signature;
import java.security.SignatureException;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.X509EncodedKeySpec;
import java.util.ArrayList;
import java.util.HashSet;
/**
* Security-related methods. For a secure implementation, all of this code
* should be implemented on a server that communicates with the application on
* the device. For the sake of simplicity and clarity of this example, this code
* is included here and is executed on the device. If you must verify the
* purchases on the phone, you should obfuscate this code to make it harder for
* an attacker to replace the code with stubs that treat all purchases as
* verified.
*/
public class Security {
private static final String TAG = "Security";
private static final String KEY_FACTORY_ALGORITHM = "RSA";
private static final String SIGNATURE_ALGORITHM = "SHA1withRSA";
private static final SecureRandom RANDOM = new SecureRandom();
/**
* This keeps track of the nonces that we generated and sent to the server.
* We need to keep track of these until we get back the purchase state and
* send a confirmation message back to Android Market. If we are killed and
* lose this list of nonces, it is not fatal. Android Market will send us a
* new "notify" message and we will re-generate a new nonce. This has to be
* "static" so that the {@link BillingReceiver} can check if a nonce exists.
*/
private static HashSet<Long> sKnownNonces = new HashSet<Long>();
/**
* A class to hold the verified purchase information.
*/
public static class VerifiedPurchase {
public PurchaseState purchaseState;
public String notificationId;
public String productId;
public String orderId;
public long purchaseTime;
public String developerPayload;
public String purchaseToken;
public VerifiedPurchase(PurchaseState purchaseState,
String notificationId, String productId, String orderId,
long purchaseTime, String developerPayload,String purchaseToken) {
this.purchaseState = purchaseState;
this.notificationId = notificationId;
this.productId = productId;
this.orderId = orderId;
this.purchaseTime = purchaseTime;
this.developerPayload = developerPayload;
this.purchaseToken=purchaseToken;
}
}
/** Generates a nonce (a random number used once). */
public static long generateNonce() {
long nonce = RANDOM.nextLong();
sKnownNonces.add(nonce);
return nonce;
}
public static void removeNonce(long nonce) {
sKnownNonces.remove(nonce);
}
public static boolean isNonceKnown(long nonce) {
return sKnownNonces.contains(nonce);
}
/**
* 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;
}
if (Constans.DEBUG) {
//Log.i(TAG, "signedData: " + signedData);
}
boolean verified = false;
if (!TextUtils.isEmpty(signature)) {
/**
* Compute your public key (that you got from the Android Market
* publisher site).
*
* Instead of just storing the entire literal string here embedded
* in the program, construct the key at runtime from pieces or use
* bit manipulation (for example, XOR with some other string) to
* hide the actual key. The key itself is not secret information,
* but we don't want to make it easy for an adversary to replace the
* public key with one of their own and then fake messages from the
* server.
*
* Generally, encryption keys / passwords should only be kept in
* memory long enough to perform the operation they need to perform.
*/
String base64EncodedPublicKey = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuKgldGQPL/xV9WKLmY62UVgEm7gsPI/T/nQxRKpYN17m8Sq3gO9nWD17wXew4oNaHmMAmArS7s7eFi3Z+XiyWil1iZvEOdBOdZD502BzujPoBa4Fu9eITPBO9tzBEdvNLXf8amnsRj53TA4bcxB2O6OcXrQIv3t3n5Dg5Nn+rJpoKSNUv7NEzJagG/2NhyjIysAObbvQ5SBQ5NgRtZlvhsTeQJPMLhRAoRcTK/+47VkhrxM3PppeGjoNRryn6d+RhMjs/nydvoQtP2V76UcUu4m+daDnK3PxOnwLt50hNtQhNf3VgixVrSKfHUWp240uEz9MHstjj8BWPH9BFF/TewIDAQAB";
PublicKey key = Security.generatePublicKey(base64EncodedPublicKey);
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);
System.out.println("response--------------->"+jElement);
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;
String purchaseToken=jElement.optString("purchaseToken",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,purchaseToken));
}
} catch (JSONException e) {
//Log.e(TAG, "JSON exception: ", e);
return null;
}
removeNonce(nonce);
return purchases;
}
/**
* Generates a PublicKey instance from a string containing the
* Base64-encoded public key.
*
* @param encodedPublicKey
* Base64-encoded public key
* @throws IllegalArgumentException
* if encodedPublicKey is invalid
*/
public static PublicKey generatePublicKey(String encodedPublicKey) {
try {
byte[] decodedKey = Base64.decode(encodedPublicKey);
KeyFactory keyFactory = KeyFactory
.getInstance(KEY_FACTORY_ALGORITHM);
return keyFactory
.generatePublic(new X509EncodedKeySpec(decodedKey));
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
} catch (InvalidKeySpecException e) {
//Log.e(TAG, "Invalid key specification.");
throw new IllegalArgumentException(e);
} catch (Base64DecoderException e) {
//Log.e(TAG, "Base64 decoding failed.");
throw new IllegalArgumentException(e);
}
}
/**
* Verifies that the signature from the server matches the computed
* signature on the data. Returns true if the data is correctly signed.
*
* @param publicKey
* public key associated with the developer account
* @param signedData
* signed data from server
* @param signature
* server signature
* @return true if the data and signature match
*/
public static boolean verify(PublicKey publicKey, String signedData,
String signature) {
if (Constans.DEBUG) {
//Log.i(TAG, "signature: " + signature);
}
Signature sig;
try {
sig = Signature.getInstance(SIGNATURE_ALGORITHM);
sig.initVerify(publicKey);
sig.update(signedData.getBytes());
if (!sig.verify(Base64.decode(signature))) {
//Log.e(TAG, "Signature verification failed.");
return false;
}
return true;
} catch (NoSuchAlgorithmException e) {
//Log.e(TAG, "NoSuchAlgorithmException.");
} catch (InvalidKeyException e) {
//Log.e(TAG, "Invalid key specification.");
} catch (SignatureException e) {
//Log.e(TAG, "Signature exception.");
} catch (Base64DecoderException e) {
//Log.e(TAG, "Base64 decoding failed.");
}
return false;
}
}
答案 0 :(得分:3)
在RELEASE模式下运行您的应用程序,并在Manifest文件中将调试模式设置为false。
答案 1 :(得分:0)
当您尝试购买产品时,您购买的产品流程应如下所示:
mHelper.launchPurchaseFlow(this, sku, 1001,mPurchaseFinishedListener,finalBase64);
当您尝试购买订阅时,您的购买流程应该是这样的
mHelper.launchSubscriptionPurchaseFlow(this, sku, 1002,
mPurchaseFinishedListener,finalBase64);