最近项目中需要接入海外SDK,进行支付,故选择Google Play支付。
1.首先要注意的是,Google官方控制台的项目创建和配置,配置出现问题的话会导致,支付调不起来。(配置教程,网上很多暂时就不搞了)
2.创建好项目不要忘了设置测试账号,测试账号很重要,测试账号最好是绑定国外的卡。如果是绑定中国大陆的信用卡,可能测试的时候你会发现,你这个账号好像并不能使用,因为谷歌在中国是不可以进行支付的。(原因是你的卡会有地址信息展示,国内的不可以,你可以试着改到国外)
3.谷歌账号的地区,一定要是国外的,中国不允许谷歌支付,会出现 code码为3 的情况(也可以切换地区)
4.目前谷歌官方推荐的是google play结算 方法,(网上很多都是之前的那种老方法)
下边是调用支付时返回的code码,看这个code码对比自己的错误。2020-12-04说下 code码为6 的情况,一般是由于网络原因。先检测网络是否翻墙,再Google Play商店是否能正常打开
//BILLING_RESPONSE_RESULT_OK 0 成功
//BILLING_RESPONSE_RESULT_USER_CANCELED 1 用户按上一步或取消对话框
//BILLING_RESPONSE_RESULT_SERVICE_UNAVAILABLE 2 网络连接断开
//BILLING_RESPONSE_RESULT_BILLING_UNAVAILABLE 3 所请求的类型不支持 Google Play 结算服务 AIDL 版本
//BILLING_RESPONSE_RESULT_ITEM_UNAVAILABLE 4 请求的商品已不再出售
//BILLING_RESPONSE_RESULT_DEVELOPER_ERROR 5 提供给 API 的参数无效。此错误也可能说明应用未针对 Google Play 结算服务正确签名或设置,或者在其清单中缺少必要的权限。
//BILLING_RESPONSE_RESULT_ERROR 6 API 操作期间出现严重错误
//BILLING_RESPONSE_RESULT_ITEM_ALREADY_OWNED 7 未能购买,因为已经拥有此商品
//BILLING_RESPONSE_RESULT_ITEM_NOT_OWNED 8 未能消费,因为尚未拥有此商品
5.弹出 “无法购买您想要的商品” ,“此版本的应用未配置为通过google play结算” 这种错误提示,原因有:一、由于 versionCode版本 与Google后台上传的安装包的版本不一致(这个千万要注意,上传的包和你本地测试的那个包不一致就会出现问题),二、签名不一致(部分人喜欢让谷歌来管理自己的证书,好多就会导致本地打包测试出现错误,将自己修改后的东西重新上传下包,以后再进行测试就不需要了)
6.支付验证时(支付 验证 json文件和谷歌离线推送的json文件是两个不同的json文件),需要将 Server account 所对应的邮件添加到测试组上
下面我就贴下支付管理类的代码,
public class AppGooglePay implements PurchasesUpdatedListener {
private static String TAG = "AppGooglePay";
//支付权限 key
private static final String BASE64_PUBLIC_KEY = "改为你自己的值BASE64";
//未初始化标记
public static final int BILLING_MANAGER_NOT_INIT = -1;
/*客户端*/
private BillingClient billingClient;
private final Activity mActivity;
//监听
private final BillingUpdatesListener mBillingUpdatesListener;
//是否连接成功
private boolean mIsServiceConnected;
//客户端当前状态
private @BillingClient.BillingResponseCode
int curBillingClientResponseCode = BillingClient.BillingResponseCode.SERVICE_DISCONNECTED;
//商品列表
private final List<Purchase> PurchaseList = new ArrayList<>();
//消耗令牌
private Set<String> mTokensToBeConsumed;
//监听 接口
public interface BillingUpdatesListener {
void onBillingClientSetupFinished();
void onConsumeFinished(String token, @BillingClient.BillingResponseCode int result);
void onPurchasesUpdated(List<Purchase> purchases);
void onFailedHandle(@BillingClient.BillingResponseCode int result);
}
/*
* 初始化
* */
public AppGooglePay(Activity activity, final BillingUpdatesListener updatesListener){
mActivity = activity;
mBillingUpdatesListener = updatesListener;
billingClient = BillingClient.newBuilder(mActivity).enablePendingPurchases().setListener(this).build();
Log.d(TAG, "开始设置信息");
startServiceConnection(new Runnable() { //连接 GooglePlay服务器
@Override
public void run() {
mBillingUpdatesListener.onBillingClientSetupFinished();
Log.d(TAG, "设置客户端成功,开始请求商品库存");
OnQueryPurchases();
}
});
}
//连接GooglePlay服务器
public void startServiceConnection(final Runnable executeOnSuccess){
billingClient.startConnection(new BillingClientStateListener() {
@Override
public void onBillingSetupFinished(BillingResult billingResult) {
Log.d(TAG, "Setup finished. Response code: " + billingResult.getResponseCode());
if(billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK){
mIsServiceConnected = true;
if(executeOnSuccess != null){
executeOnSuccess.run();
}
}
curBillingClientResponseCode = billingResult.getResponseCode();
}
@Override
public void onBillingServiceDisconnected() {
mIsServiceConnected = false;
}
});
}
//请求查询商品库存
public void OnQueryPurchases() {
Runnable queryToExecute = new Runnable() {
@Override
public void run() {
//系统当前时间
long time = System.currentTimeMillis();
//请求内购商品
Purchase.PurchasesResult purchasesResult = billingClient.queryPurchases(BillingClient.SkuType.INAPP);
Log.i(TAG, "请求请内购商品花费时间:" + (System.currentTimeMillis() - time) + "ms");
//支持订阅
if(areSubscriptionsSupported()){
Purchase.PurchasesResult subscriptionResult = billingClient.queryPurchases(BillingClient.SkuType.SUBS);
Log.i(TAG, "请求订阅商品后花费的时间: "
+ (System.currentTimeMillis() - time) + "ms");
if(subscriptionResult.getResponseCode() == BillingClient.BillingResponseCode.OK){
Log.i(TAG, "请求订阅消息返回 Code: "
+ subscriptionResult.getResponseCode()
+ " res: " + subscriptionResult.getPurchasesList().size());
purchasesResult.getPurchasesList().addAll(subscriptionResult.getPurchasesList());
}
else {
Log.e(TAG, "获取订阅商品失败请见Code");
}
}
else if (purchasesResult.getResponseCode() == BillingClient.BillingResponseCode.OK){
Log.i(TAG, "跳过请求订阅商品,因为设备不支持");
}
else{
Log.w(TAG, "请求商品失败返回: "
+ purchasesResult.getResponseCode());
}
onQueryPurchasesFinished(purchasesResult); //请求商品信息
}
};
executeServiceRequest(queryToExecute);
}
/*是否支持订阅*/
public boolean areSubscriptionsSupported(){
int responseCode = billingClient.isFeatureSupported(BillingClient.FeatureType.SUBSCRIPTIONS).getResponseCode();
if(responseCode != BillingClient.BillingResponseCode.OK)
{
Log.w(TAG, "areSubscriptionsSupported() got an error response: " + responseCode);
}
return responseCode == BillingClient.BillingResponseCode.OK;
}
//开始执行服务请求(不要在主线程中搞)
private void executeServiceRequest(Runnable runnable) {
if(mIsServiceConnected){
runnable.run();
}
else{
startServiceConnection(runnable);
}
}
//请求商品信息完成
private void onQueryPurchasesFinished(Purchase.PurchasesResult result){
if(billingClient == null || result.getResponseCode() != BillingClient.BillingResponseCode.OK){
Log.w(TAG, "billingClient is null or result code (" + result.getResponseCode()
+ ") was bad - quitting");
return;
}
Log.d(TAG, "请求商品信息完成");
PurchaseList.clear();
onPurchasesUpdated(result.getBillingResult(),result.getPurchasesList());
}
// 更新商品内容
@Override
public void onPurchasesUpdated(BillingResult billingResult, @Nullable List<Purchase> purchases) {
if(billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK && purchases != null ){
for (Purchase purchase : purchases){
Log.i(TAG, "onPurchasesUpdated: -->"+ purchase.getOrderId() + ",--->"+purchase.getPurchaseToken() + ",-->" + purchase.getOriginalJson());
HandlePurchase(purchase);
}
mBillingUpdatesListener.onPurchasesUpdated(PurchaseList);
}
else{
if(billingResult.getResponseCode() == BillingClient.BillingResponseCode.USER_CANCELED){
Log.i(TAG, "onPurchasesUpdated() - 用户取消购买当前商品");
}
else{
Log.w(TAG, "onPurchasesUpdatedconsumeAsync() got unknown resultCode: " + billingResult.getResponseCode());
}
mBillingUpdatesListener.onFailedHandle(billingResult.getResponseCode());
}
}
/*商品处理*/
private void HandlePurchase(Purchase purchase){
//验证签名数据
Log.i(TAG,"getSignature => "+ purchase.getSignature());
if(!VerifyValidSignature(purchase.getOriginalJson(),purchase.getSignature())){
Log.i(TAG, "Got a purchase: " + purchase + "; but signature is bad. Skipping...");
return;
}
Log.d(TAG, "Got a verified purchase: " + purchase);
PurchaseList.add(purchase);
}
//验证签名
private boolean VerifyValidSignature(String signedData,String signature){
try{
return AppGooglePaySec.verifyPurchase(BASE64_PUBLIC_KEY,signedData,signature);
}
catch (IOException e){
Log.e(TAG, "Got an exception trying to validate a purchase: " + e);
return false;
}
}
//商品消耗
public void consumeAsync(final String purchaseToken) {
if (mTokensToBeConsumed == null) {
mTokensToBeConsumed = new HashSet<>();
} else if (mTokensToBeConsumed.contains(purchaseToken)) {
Log.i(TAG, "Token was already scheduled to be consumed - skipping...");
return;
}
mTokensToBeConsumed.add(purchaseToken);
//消耗监听
final ConsumeResponseListener onConsumeListener = new ConsumeResponseListener() {
@Override
public void onConsumeResponse(BillingResult responseCode, String purchaseToken) {
// If billing service was disconnected, we try to reconnect 1 time
// (feel free to introduce your retry policy here).
mBillingUpdatesListener.onConsumeFinished(purchaseToken, responseCode.getResponseCode());
Log.i("luaAppGG", "消耗监听 --- >>> " + responseCode.getResponseCode());
}
};
final ConsumeParams consumeParams = ConsumeParams.newBuilder().setPurchaseToken(purchaseToken).build();
Runnable consumeRequest = new Runnable() {
@Override
public void run() {
// 进行消耗
billingClient.consumeAsync(consumeParams, onConsumeListener);
}
};
executeServiceRequest(consumeRequest);
}
//查询内购商品详情
public void querySkuDetailsAsync(@BillingClient.SkuType final String itemType, final List<String> skuList,
final SkuDetailsResponseListener listener) {
Runnable queryRequest = new Runnable(){
@Override
public void run() {
SkuDetailsParams.Builder params = SkuDetailsParams.newBuilder();
params.setSkusList(skuList).setType(itemType);
billingClient.querySkuDetailsAsync(params.build(),
new SkuDetailsResponseListener() {
@Override
public void onSkuDetailsResponse(BillingResult billingResult,
List<SkuDetails> skuDetailsList) {
listener.onSkuDetailsResponse(billingResult, skuDetailsList);
}
});
}
};
executeServiceRequest(queryRequest);
}
// 商品确认
public void acknowledgePurchase(AcknowledgePurchaseParams acknowledgePurchaseParams, AcknowledgePurchaseResponseListener Listener){
billingClient.acknowledgePurchase(acknowledgePurchaseParams,Listener);
}
//开始 购买,
public void initiatePurchaseFlow(final SkuDetails skuDetails) {
Runnable purchaseFlowRequest = new Runnable() {
@Override
public void run() {
BillingFlowParams purchaseParams = BillingFlowParams.newBuilder()
.setSkuDetails(skuDetails).build();
billingClient.launchBillingFlow(mActivity, purchaseParams);
}
};
executeServiceRequest(purchaseFlowRequest);
}
/*断开、 释放连接*/
public void destroy(){
Log.d(TAG, "Destroying the manager.");
if (billingClient != null && billingClient.isReady()) {
billingClient.endConnection();
billingClient = null;
}
}
}
还有一个管理类,这样写确实比较方便以后的改变(比如说,我要在其他项目中使用这个谷歌支付,就可以直接将我这些类,拷贝到相应的项目下,调用相应的方法即可,耦合性很低)
管理类
public class AppGooglePaySec {
private static final String TAG = "AppGooglePaySec";
private static final String KEY_FACTORY_ALGORITHM = "RSA";
private static final String SIGNATURE_ALGORITHM = "SHA1withRSA";
//商品 签名 验证
public static boolean verifyPurchase(String base64PublicKey, String signedData,
String signature) throws IOException {
if (TextUtils.isEmpty(signedData) || TextUtils.isEmpty(base64PublicKey)
|| TextUtils.isEmpty(signature)) {
Log.w(TAG,"购买验证失败,数据丢失"+base64PublicKey);
return false;
}
PublicKey key = generatePublicKey(base64PublicKey);
return verify(key, signedData, signature);
}
public static PublicKey generatePublicKey(String encodedPublicKey) throws IOException {
try {
byte[] decodedKey = Base64.decode(encodedPublicKey, Base64.DEFAULT);
KeyFactory keyFactory = KeyFactory.getInstance(KEY_FACTORY_ALGORITHM);
return keyFactory.generatePublic(new X509EncodedKeySpec(decodedKey));
} catch (NoSuchAlgorithmException e) {
// "RSA" is guaranteed to be available.
throw new RuntimeException(e);
} catch (InvalidKeySpecException e) {
String msg = "Invalid key specification: " + e;
BillingHelper.logWarn(TAG, msg);
throw new IOException(msg);
}
}
public static boolean verify(PublicKey publicKey, String signedData, String signature) {
byte[] signatureBytes;
try {
signatureBytes = Base64.decode(signature, Base64.DEFAULT);
} catch (IllegalArgumentException e) {
BillingHelper.logWarn(TAG, "Base64 decoding failed.");
return false;
}
try {
Signature signatureAlgorithm = Signature.getInstance(SIGNATURE_ALGORITHM);
signatureAlgorithm.initVerify(publicKey);
signatureAlgorithm.update(signedData.getBytes());
if (!signatureAlgorithm.verify(signatureBytes)) {
BillingHelper.logWarn(TAG, "Signature verification failed.");
return false;
}
return true;
} catch (NoSuchAlgorithmException e) {
// "RSA" is guaranteed to be available.
throw new RuntimeException(e);
} catch (InvalidKeyException e) {
BillingHelper.logWarn(TAG, "Invalid key specification.");
} catch (SignatureException e) {
BillingHelper.logWarn(TAG, "Signature exception.");
}
return false;
}
}
其实也可以跟着谷歌支付官方走,相对来说比较详细和真实,具有参考性。(官方文档看着就是有点乱)
再记录一下,商品消耗,个人认为,谷歌支付有点麻烦,购买后还需要先进行对商品的确认,如果不确认的话,谷歌平台会在三天后,将充值的返还给玩家,切记一定要确认订单;再一个就是必须要进行对商品消耗,若是不进行消耗,则会造成玩家再次购买不成功(本人项目中跟后端订单确认过之后,进行立即消耗)。对了,在每次购买之前都需要先将商品列表进行初始化一次,也就是将商品Id添加到谷歌平台,相当于做了一次校验一样。
还有一点:谷歌支付确认订单针对非消耗型的,像游戏中的金币解锁关卡之类的属于消耗型 的直接使用消耗订单即可
注意:这里面的方法有的是需要在 主Activity里的生命周期方法中调用 的 哟!!!莫要忘记!!!
以上大概就是我遇到的问题,特此记录!!!
记住,我不定时更新!!!
参考链接:https://blog.csdn.net/qq_35431588/article/details/105514168