Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Code refactor & more proper null safety update #301

Closed
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 4 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
## 5.5.0
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR: NOT FINISHED YET

+ DEPRECATED: removed dataAndroid and originalJsonAndroid from PurchasedItem. Use transactionReceipt instead as it is the same object.

## 5.0.2
+ Replaced obfuscatedAccountIdAndroid with obfuscatedAccountId in request purchase method [#299](https://github.com/dooboolab/flutter_inapp_purchase/pull/299)

Expand All @@ -8,7 +11,7 @@
+ Support null safety [#275](https://github.com/dooboolab/flutter_inapp_purchase/pull/275)

## 4.0.2
+ The dart side requires "introductoryPriceCyclesAndroid" to be a int [#268](https://github.com/dooboolab/flutter_inapp_purchase/pull/268)
+ The dart side requires "introductoryPriceCyclesAndroid" to be an int [#268](https://github.com/dooboolab/flutter_inapp_purchase/pull/268)

## 4.0.1
+ `platform` dep version `>=2.0.0 <4.0.0`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,32 +4,28 @@
import android.content.Context;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@hyochan What is the status on amazon IAP? Is it a working feature? I tried to unify Amazon IAPs maps to be the same as Android apps, but it is better to kill amazon IAPs in case it is not a working feature and nobody uses it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bohdan1krokhmaliuk Honestly I've not tried Amazon at all because I am not using it. Also, this was developed by the community by @Rockvole.

How about separating the two variants as done in dooboolab-community/react-native-iap#1336?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No no, I was wondering cause it would make two more variables not nullable

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi, Sorry I cant help much, I have not done any Android java development since this Amazon IAP work (2018 I think). The Amazon store may become more important soon since it will be used for Android apps in Windows 11.
https://www.theverge.com/2021/6/24/22548428/microsoft-windows-11-android-apps-support-amazon-store

import android.util.Log;

import androidx.annotation.NonNull;

import com.amazon.device.iap.PurchasingListener;
import com.amazon.device.iap.PurchasingService;
import com.amazon.device.iap.model.FulfillmentResult;
import com.amazon.device.iap.model.Product;
import com.amazon.device.iap.model.ProductDataResponse;
import com.amazon.device.iap.model.ProductType;
import com.amazon.device.iap.model.PurchaseResponse;
import com.amazon.device.iap.model.PurchaseUpdatesResponse;
import com.amazon.device.iap.model.Receipt;
import com.amazon.device.iap.model.RequestId;
import com.amazon.device.iap.model.UserDataResponse;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.text.NumberFormat;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import io.flutter.plugin.common.BinaryMessenger;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugin.common.MethodChannel.MethodCallHandler;
Expand Down Expand Up @@ -57,14 +53,14 @@ public void setChannel(MethodChannel channel) {
}

@Override
public void onMethodCall(final MethodCall call, final Result result) {
public void onMethodCall(final @NonNull MethodCall call, final @NonNull Result result) {
this.result=result;
try {
PurchasingService.registerListener(context, purchasesUpdatedListener);

} catch (Exception e) {
result.error(call.method, "Call endConnection method if you want to start over.", e.getMessage());
}

if (call.method.equals("getPlatformVersion")) {
try {
result.success("Android " + android.os.Build.VERSION.RELEASE);
Expand All @@ -78,7 +74,7 @@ public void onMethodCall(final MethodCall call, final Result result) {
result.success("Billing client has ended.");
} else if (call.method.equals("consumeAllItems")) {
// consumable is a separate type in amazon
result.success("no-ops in amazon");
result.error("E_NO_OP_IN_AMAZON","no-ops in amazon",null);
} else if (call.method.equals("getItemsByType")) {
Log.d(TAG, "getItemsByType");
String type = call.argument("type");
Expand All @@ -91,34 +87,43 @@ public void onMethodCall(final MethodCall call, final Result result) {
}
PurchasingService.getProductData(productSkus);

final ArrayList<HashMap<String, Object>> list = new ArrayList<>();
result.success(list);
} else if (call.method.equals("getAvailableItemsByType")) {
String type = call.argument("type");
Log.d(TAG, "gaibt="+type);

final ArrayList<HashMap<String, Object>> list = new ArrayList<>();
// NOTE: getPurchaseUpdates doesnt return Consumables which are FULFILLED
if(type.equals("inapp")) {
PurchasingService.getPurchaseUpdates(true);
result.success(list);
} else if(type.equals("subs")) {
// Subscriptions are retrieved during inapp, so we just return empty list
result.success("[]");
result.success(list);
} else {
result.notImplemented();
}
} else if (call.method.equals("getPurchaseHistoryByType")) {
final ArrayList<HashMap<String, Object>> list = new ArrayList<>();
// No equivalent
result.success("[]");
result.success(list);
} else if (call.method.equals("buyItemByType")) {
final String type = call.argument("type");
final String obfuscatedAccountId = call.argument("obfuscatedAccountId");
final String obfuscatedProfileId = call.argument("obfuscatedProfileId");
final String sku = call.argument("sku");
final String oldSku = call.argument("oldSku");
final int prorationMode = call.argument("prorationMode");

Log.d(TAG, "type="+type+"||sku="+sku+"||oldsku="+oldSku);
final RequestId requestId = PurchasingService.purchase(sku);
Log.d(TAG, "resid="+requestId.toString());

result.success(null);
} else if (call.method.equals("consumeProduct")) {
// consumable is a separate type in amazon
result.success("no-ops in amazon");
result.error("E_NO_OP_IN_AMAZON","no-ops in amazon",null);
} else {
result.notImplemented();
}
Expand Down Expand Up @@ -147,49 +152,27 @@ public void onProductDataResponse(ProductDataResponse response) {
final Set<String> unavailableSkus = response.getUnavailableSkus();
Log.d(TAG, "onProductDataResponse: " + unavailableSkus.size() + " unavailable skus");
Log.d(TAG, "unavailableSkus="+unavailableSkus.toString());
JSONArray items = new JSONArray();
try {
for (Map.Entry<String, Product> skuDetails : productData.entrySet()) {
Product product=skuDetails.getValue();
NumberFormat format = NumberFormat.getCurrencyInstance();

Number number;
try {
number = format.parse(product.getPrice());
} catch (ParseException e) {
result.error(TAG, "Price Parsing error", e.getMessage());
return;
}
JSONObject item = new JSONObject();
item.put("productId", product.getSku());
item.put("price", number.toString());
item.put("currency", null);
ProductType productType = product.getProductType();
switch (productType) {
case ENTITLED:
case CONSUMABLE:
item.put("type", "inapp");
break;
case SUBSCRIPTION:
item.put("type", "subs");
break;
}
item.put("localizedPrice", product.getPrice());
item.put("title", product.getTitle());
item.put("description", product.getDescription());
item.put("introductoryPrice", "");
item.put("subscriptionPeriodAndroid", "");
item.put("freeTrialPeriodAndroid", "");
item.put("introductoryPriceCyclesAndroid", 0);
item.put("introductoryPricePeriodAndroid", "");
Log.d(TAG, "opdr Putting "+item.toString());
items.put(item);
ArrayList<HashMap<String,Object>> items = new ArrayList<>();

for (Map.Entry<String, Product> skuDetails : productData.entrySet()) {
Product product=skuDetails.getValue();
NumberFormat format = NumberFormat.getCurrencyInstance();

Number number;
try {
number = format.parse(product.getPrice());
} catch (ParseException e) {
result.error(TAG, "Price Parsing error", e.getMessage());
return;
}
//System.err.println("Sending "+items.toString());
result.success(items.toString());
} catch (JSONException e) {
result.error(TAG, "E_BILLING_RESPONSE_JSON_PARSE_ERROR", e.getMessage());

final HashMap<String,Object> item = FlutterEntitiesBuilder.buildSkuDetailsMap(product);
Log.d(TAG, "opdr Putting "+item.toString());
items.add(item);
}
//System.err.println("Sending "+items.toString());
result.success(items);

break;
case FAILED:
result.error(TAG,"FAILED",null);
Expand All @@ -209,19 +192,12 @@ public void onPurchaseResponse(PurchaseResponse response) {
case SUCCESSFUL:
Receipt receipt = response.getReceipt();
PurchasingService.notifyFulfillment(receipt.getReceiptId(), FulfillmentResult.FULFILLED);
Date date = receipt.getPurchaseDate();
Long transactionDate=date.getTime();
try {
JSONObject item = getPurchaseData(receipt.getSku(),
receipt.getReceiptId(),
receipt.getReceiptId(),
transactionDate.doubleValue());
Log.d(TAG, "opr Putting "+item.toString());
result.success(item.toString());
channel.invokeMethod("purchase-updated", item.toString());
} catch (JSONException e) {
result.error(TAG, "E_BILLING_RESPONSE_JSON_PARSE_ERROR", e.getMessage());
}

final HashMap<String,Object> item = FlutterEntitiesBuilder.buildPurchaseMap(receipt);
Log.d(TAG, "opr Putting "+item.toString());
result.success(item);
channel.invokeMethod("purchase-updated", item);

break;
case FAILED:
result.error(TAG, "buyItemByType", "billingResponse is not ok: " + status);
Expand All @@ -237,24 +213,16 @@ public void onPurchaseUpdatesResponse(PurchaseUpdatesResponse response) {

switch(status) {
case SUCCESSFUL:
JSONArray items = new JSONArray();
try {
List<Receipt> receipts = response.getReceipts();
for(Receipt receipt : receipts) {
Date date = receipt.getPurchaseDate();
Long transactionDate=date.getTime();
JSONObject item = getPurchaseData(receipt.getSku(),
receipt.getReceiptId(),
receipt.getReceiptId(),
transactionDate.doubleValue());

Log.d(TAG, "opudr Putting "+item.toString());
items.put(item);
}
result.success(items.toString());
} catch (JSONException e) {
result.error(TAG, "E_BILLING_RESPONSE_JSON_PARSE_ERROR", e.getMessage());
ArrayList<HashMap<String,Object>> items = new ArrayList<>();

List<Receipt> receipts = response.getReceipts();
for(Receipt receipt : receipts) {
final HashMap<String,Object> item = FlutterEntitiesBuilder.buildPurchaseMap(receipt);
Log.d(TAG, "opudr Putting "+item.toString());
items.add(item);
}
result.success(items);

break;
case FAILED:
result.error(TAG,"FAILED",null);
Expand All @@ -267,16 +235,4 @@ public void onPurchaseUpdatesResponse(PurchaseUpdatesResponse response) {
}
};

JSONObject getPurchaseData(String productId, String transactionId, String transactionReceipt,
Double transactionDate) throws JSONException {
JSONObject item = new JSONObject();
item.put("productId", productId);
item.put("transactionId", transactionId);
item.put("transactionReceipt", transactionReceipt);
item.put("transactionDate", Double.toString(transactionDate));
item.put("dataAndroid",null);
item.put("signatureAndroid",null);
item.put("purchaseToken",null);
return item;
}
}