Google Play Billing V6集成报错DF-DFERH-01求助
Google Play Billing Library订阅唤起支付窗口出现DF-DFERH-01错误的解决方案
问题描述
我在Android Studio的Fragment中集成Google Play Billing Library实现订阅售卖功能,已在Google Play Console完成内部测试应用上传及订阅基础套餐配置,代码逻辑严格遵循文档步骤,能正常获取订阅产品信息,但唤起Google Play支付窗口时出现DF-DFERH-01错误。已尝试以下操作均未解决:
- 清除Google Play商店与服务的缓存数据
- 重启设备
- 从内部测试渠道下载应用
- 将Fragment替换为Activity
- 降级库版本
其他带订阅功能的应用可正常唤起支付窗口,排除Google Play商店自身问题,附上相关代码:
@Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { // Inflate the layout for this fragment view = inflater.inflate(R.layout.fragment_sin_anuncios, container, false); PurchasesUpdatedListener purchasesUpdatedListener = new PurchasesUpdatedListener() { @Override public void onPurchasesUpdated(BillingResult billingResult, List<Purchase> purchases) { // To be implemented in a later section. } }; billingClient = BillingClient.newBuilder(getContext()) .setListener(purchasesUpdatedListener) .enablePendingPurchases() .build(); connectToGooglePlayBilling(); return view; } private void connectToGooglePlayBilling(){ billingClient.startConnection(new BillingClientStateListener() { @Override public void onBillingSetupFinished(BillingResult billingResult) { if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) { // The BillingClient is ready. You can query purchases here. getProductDeatils(); } } @Override public void onBillingServiceDisconnected() { // Try to restart the connection on the next request to // Google Play by calling the startConnection() method. Toast.makeText(getContext(), "No se pudo conectar a GooglePlay, revisa tu conexion a internet.\nIntentando conectarse nuevamente.", Toast.LENGTH_SHORT).show(); connectToGooglePlayBilling(); } }); } private void getProductDeatils(){ QueryProductDetailsParams queryProductDetailsParams = QueryProductDetailsParams.newBuilder() .setProductList( ImmutableList.of( QueryProductDetailsParams.Product.newBuilder() .setProductId("version_sin_anuncios") .setProductType(BillingClient.ProductType.SUBS) .build())) .build(); Activity activity = getActivity(); billingClient.queryProductDetailsAsync( queryProductDetailsParams, new ProductDetailsResponseListener() { public void onProductDetailsResponse(BillingResult billingResult, List<ProductDetails> productDetailsList) { // check billingResult // process returned productDetailsList if(billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK && productDetailsList != null){ TextView descripcion = view.findViewById(R.id.textViewSinAnuncios); descripcion.setText(productDetailsList.get(0).getDescription()); Button BotonSinAnuncios = (Button) view.findViewById(R.id.buttonSinAnuncios); BotonSinAnuncios.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Log.d("GoogleBillingService", "onBillingSetupFinished: billingResult: " + billingResult); Log.d("GoogleBillingService", "Product detatils: " + productDetailsList.get(0)); ImmutableList productDetailsParamsList = ImmutableList.of( BillingFlowParams.ProductDetailsParams.newBuilder() // retrieve a value for "productDetails" by calling queryProductDetailsAsync() .setProductDetails(productDetailsList.get(0)) // to get an offer token, call ProductDetails.getSubscriptionOfferDetails() // for a list of offers that are available to the user .setOfferToken(String.valueOf(productDetailsList.get(0).getSubscriptionOfferDetails())) .build() ); BillingFlowParams billingFlowParams = BillingFlowParams.newBuilder() .setProductDetailsParamsList(productDetailsParamsList) .build(); // Launch the billing flow billingClient.launchBillingFlow(activity, billingFlowParams); } }); } } } ); } }
解决方案
1. 修复Offer Token获取逻辑(核心问题)
代码中setOfferToken的参数错误:String.valueOf(productDetailsList.get(0).getSubscriptionOfferDetails())是把整个List<SubscriptionOfferDetails>对象转成字符串,而非有效的优惠token。正确做法是从订阅方案列表中取出具体方案的token:
// 先判断方案列表不为空 List<ProductDetails.SubscriptionOfferDetails> offerDetails = productDetailsList.get(0).getSubscriptionOfferDetails(); if (offerDetails != null && !offerDetails.isEmpty()) { String validOfferToken = offerDetails.get(0).getOfferToken(); // 使用该token构建参数 }
2. 完善空值判断逻辑
增加多层空值校验,避免异常同时确保参数有效性:
BotonSinAnuncios.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Log.d("GoogleBillingService", "onBillingSetupFinished: billingResult: " + billingResult); Log.d("GoogleBillingService", "Product details: " + productDetailsList.get(0)); // 校验产品列表 if (productDetailsList == null || productDetailsList.isEmpty()) { Toast.makeText(getContext(), "产品信息异常", Toast.LENGTH_SHORT).show(); return; } ProductDetails productDetails = productDetailsList.get(0); // 校验订阅方案列表 List<ProductDetails.SubscriptionOfferDetails> offerDetails = productDetails.getSubscriptionOfferDetails(); if (offerDetails == null || offerDetails.isEmpty()) { Toast.makeText(getContext(), "无可用订阅方案", Toast.LENGTH_SHORT).show(); return; } // 获取有效offer token String offerToken = offerDetails.get(0).getOfferToken(); ImmutableList<BillingFlowParams.ProductDetailsParams> productDetailsParamsList = ImmutableList.of( BillingFlowParams.ProductDetailsParams.newBuilder() .setProductDetails(productDetails) .setOfferToken(offerToken) .build() ); BillingFlowParams billingFlowParams = BillingFlowParams.newBuilder() .setProductDetailsParamsList(productDetailsParamsList) .build(); // 校验Activity有效性 if (getActivity() == null) { return; } billingClient.launchBillingFlow(getActivity(), billingFlowParams); } });
3. 优化BillingClient生命周期管理
在Fragment销毁时关闭BillingClient连接,避免内存泄漏:
@Override public void onDestroy() { super.onDestroy(); if (billingClient != null && billingClient.isReady()) { billingClient.endConnection(); } }
4. 验证测试账号与签名一致性
- 确认测试账号已加入Google Play Console内部测试人员列表,且已接受测试邀请
- 确保本地打包测试应用的签名文件,与Google Play Console上传应用的签名完全一致
内容的提问来源于stack exchange,提问作者Jorge Barajas
相关产品推荐
相关产品推荐

