Skip to content
This repository was archived by the owner on Feb 22, 2023. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
review fixes
  • Loading branch information
Chris Yang committed Nov 25, 2019
commit a10b0ea33f8d107b5e830a147f4dd73d89246e47
2 changes: 1 addition & 1 deletion packages/in_app_purchase/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
## 0.3.2
## 0.3.0

* Migrate the `Google Play Library` to 2.0.3.
* Introduce a new class `BillingResultWrapper` which contains a detailed result of a BillingClient operation.
Expand Down
6 changes: 3 additions & 3 deletions packages/in_app_purchase/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,15 +114,15 @@ for (PurchaseDetails purchase in response.pastPurchases) {
}
```

Note that the App Store does not have any APIs for querying consummable
products, and Google Play considers consummable products to no longer be owned
Note that the App Store does not have any APIs for querying consumable
products, and Google Play considers consumable products to no longer be owned
once they're marked as consumed and fails to return them here. For restoring
these across devices you'll need to persist them on your own server and query
that as well.

### Making a purchase

Both storefronts handle consummable and non-consummable products differently. If
Both storefronts handle consumable and non-consumable products differently. If
you're using `InAppPurchaseConnection`, you need to make a distinction here and
call the right purchase method for each type.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -513,7 +513,7 @@ public void consumeAsync() {
}

@Test
public void acknowledgetPurchase() {
public void acknowledgePurchase() {
establishConnectedBillingClient(null, null);
ArgumentCaptor<BillingResult> resultCaptor = ArgumentCaptor.forClass(BillingResult.class);
BillingResult billingResult =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,16 @@ public void fromBillingResult() throws JSONException {
assertEquals(billingResultMap.get("debugMessage"), newBillingResult.getDebugMessage());
}

@Test
public void fromBillingResult_dubugMessageNull() throws JSONException {
Copy link
Contributor

Choose a reason for hiding this comment

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

nit:

Suggested change
public void fromBillingResult_dubugMessageNull() throws JSONException {
public void fromBillingResult_debugMessageNull() throws JSONException {

BillingResult newBillingResult =
BillingResult.newBuilder().setResponseCode(BillingClient.BillingResponseCode.OK).build();
Map<String, Object> billingResultMap = Translator.fromBillingResult(newBillingResult);

assertEquals(billingResultMap.get("responseCode"), newBillingResult.getResponseCode());
assertEquals(billingResultMap.get("debugMessage"), newBillingResult.getDebugMessage());
}

private void assertSerialized(SkuDetails expected, Map<String, Object> serialized) {
assertEquals(expected.getDescription(), serialized.get("description"));
assertEquals(expected.getFreeTrialPeriod(), serialized.get("freeTrialPeriod"));
Expand Down
2 changes: 2 additions & 0 deletions packages/in_app_purchase/example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -366,12 +366,14 @@ class _MyAppState extends State<MyApp> {
} else {
if (purchaseDetails.status == PurchaseStatus.error) {
handleError(purchaseDetails.error);
return;
Copy link
Contributor

@malsabbagh malsabbagh Jan 21, 2020

Choose a reason for hiding this comment

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

Also on iOS completePurchase() needs to be called for status == PurchaseStatus.error. With this change this will be broken.

Please refer to completePurchase documentation.

/// the purchase needs to be completed if the [PurchaseDetails.status] is [PurchaseStatus.error].

} else if (purchaseDetails.status == PurchaseStatus.purchased) {
bool valid = await _verifyPurchase(purchaseDetails);
if (valid) {
deliverProduct(purchaseDetails);
} else {
_handleInvalidPurchase(purchaseDetails);
return;
Copy link
Contributor

Choose a reason for hiding this comment

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

I am guessing this will have the same impact as line 374.

}
}
if (Platform.isAndroid) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -206,21 +206,23 @@ class BillingClient {
/// Consuming can only be done on an item that's owned, and as a result of consumption, the user will no longer own it.
/// Consumption is done asynchronously. The method returns a Future containing a [BillingResultWrapper].
///
/// The `params` must not be null.
/// The `purchaseToken` must not be null.
/// The `developerPayload` is the developer data associated with the purchase to be consumed, it defaults to null.
///
/// This wraps [`BillingClient#consumeAsync(String, ConsumeResponseListener)`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient.html#consumeAsync(java.lang.String,%20com.android.billingclient.api.ConsumeResponseListener))
Future<BillingResultWrapper> consumeAsync(ConsumeParams params) async {
assert(params != null);
Future<BillingResultWrapper> consumeAsync(String purchaseToken,
{String developerPayload}) async {
assert(purchaseToken != null);
return BillingResultWrapper.fromJson(await channel
.invokeMapMethod<String, dynamic>(
'BillingClient#consumeAsync(String, ConsumeResponseListener)',
<String, String>{
'purchaseToken': params.purchaseToken,
'developerPayload': params.developerPayload,
'purchaseToken': purchaseToken,
'developerPayload': developerPayload,
}));
}

/// Acknowledge an In-App purchase.
/// Acknowledge an in-app purchase.
///
/// The developer is required to acknowledge that they have granted entitlement for all in-app purchases.
///
Expand All @@ -235,18 +237,19 @@ class BillingClient {
/// Please refer to https://developer.android.com/google/play/billing/billing_library_overview#acknowledge for more
/// details.
///
/// The `params` must not be null.
/// The `purchaseToken` must not be null.
/// The `developerPayload` is the developer data associated with the purchase to be consumed, it defaults to null.
///
/// This wraps [`BillingClient#acknowledgePurchase(String, AcknowledgePurchaseResponseListener)`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient.html#acknowledgePurchase(com.android.billingclient.api.AcknowledgePurchaseParams,%20com.android.billingclient.api.AcknowledgePurchaseResponseListener))
Future<BillingResultWrapper> acknowledgePurchase(
AcknowledgeParams params) async {
assert(params != null);
Future<BillingResultWrapper> acknowledgePurchase(String purchaseToken,
{String developerPayload}) async {
assert(purchaseToken != null);
return BillingResultWrapper.fromJson(await channel.invokeMapMethod<String,
dynamic>(
'BillingClient#(AcknowledgePurchaseParams params, (AcknowledgePurchaseParams, AcknowledgePurchaseResponseListener)',
<String, String>{
'purchaseToken': params.purchaseToken,
'developerPayload': params.developerPayload,
'purchaseToken': purchaseToken,
'developerPayload': developerPayload,
}));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,8 @@ import 'package:json_annotation/json_annotation.dart';
part 'enum_converters.g.dart';

/// Serializer for [BillingResponse].
///
/// Use these in `@JsonSerializable()` classes by annotating them with
/// `@BillingResponseConverter()`.
// Use these in `@JsonSerializable()` classes by annotating them with
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: Why deleting the paragraph breaks here and below? I think they probably made sense as-is.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

// `@BillingResponseConverter()`.
class BillingResponseConverter implements JsonConverter<BillingResponse, int> {
const BillingResponseConverter();

Expand All @@ -24,9 +23,8 @@ class BillingResponseConverter implements JsonConverter<BillingResponse, int> {
}

/// Serializer for [SkuType].
///
/// Use these in `@JsonSerializable()` classes by annotating them with
/// `@SkuTypeConverter()`.
// Use these in `@JsonSerializable()` classes by annotating them with
// `@SkuTypeConverter()`.
class SkuTypeConverter implements JsonConverter<SkuType, String> {
const SkuTypeConverter();

Expand All @@ -47,9 +45,8 @@ class _SerializedEnums {
}

/// Serializer for [PurchaseStateWrapper].
///
/// Use these in `@JsonSerializable()` classes by annotating them with
/// `@PurchaseStateConverter()`.
// Use these in `@JsonSerializable()` classes by annotating them with
// `@PurchaseStateConverter()`.
class PurchaseStateConverter
implements JsonConverter<PurchaseStateWrapper, int> {
const PurchaseStateConverter();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,9 +109,15 @@ class PurchaseWrapper {
final String developerPayload;

/// Whether the purchase has been acknowledged.
///
/// A successful purchase has to be acknowledged within 3 days after the purchase via [BillingClient.acknowledgePurchase].
/// * See also [BillingClient.acknowledgePurchase] for more details on acknowledging purchases.
final bool isAcknowledged;

/// The state of purchase.
/// Determines the current state of the purchase.
///
/// [BillingClient.acknowledgePurchase] should only be called when the `purchaseState` is [PurchaseStateWrapper.purchased].
/// * See also [BillingClient.acknowledgePurchase] for more details on acknowledging purchases.
final PurchaseStateWrapper purchaseState;
}

Expand Down Expand Up @@ -264,57 +270,31 @@ class PurchasesHistoryResult {
final List<PurchaseHistoryRecordWrapper> purchaseHistoryRecordList;
}

/// The parameter object used when consuming a purchase.
///
/// See also [BillingClient.consumeAsync] for consuming a purchase.
class ConsumeParams {
/// Constructs the [ConsumeParams].
///
/// The `purchaseToken` must not be null.
/// The default value of `developerPayload` is null.
ConsumeParams({@required this.purchaseToken, this.developerPayload = null});

/// The developer data associated with the purchase to be consumed.
///
/// Defaults to null.
final String developerPayload;

/// The token that identifies the purchase to be consumed.
final String purchaseToken;
}

/// The parameter object used when acknowledge a purchase.
///
/// See also [BillingClient.acknowledgePurchase] for acknowledging a purchase.
class AcknowledgeParams {
/// Constructs the [AcknowledgeParams].
///
/// The `purchaseToken` must not be null.
/// The default value of `developerPayload` is null.
AcknowledgeParams(
{@required this.purchaseToken, this.developerPayload = null});

/// The developer data associated with the purchase to be acknowledged.
///
/// Defaults to null.
final String developerPayload;

/// The token that identifies the purchase to be acknowledged.
final String purchaseToken;
}

/// Possible state of a [PurchaseWrapper].
///
/// Wraps
/// [`BillingClient.api.Purchase.PurchaseState`](https://developer.android.com/reference/com/android/billingclient/api/Purchase.PurchaseState.html).
/// * See also: [PurchaseWrapper].
enum PurchaseStateWrapper {
/// The state is unspecified.
///
/// No actions on the [PurchaseWrapper] should be performed on this state.
@JsonValue(0)
unspecified_state,

/// The state is purchased.
///
/// The production should be delivered and the purchase should be acknowledged on this state.
/// * See also [BillingClient.acknowledgePurchase] for more details on acknowledging purchases.
@JsonValue(1)
purchased,

/// The state is pending.
///
/// No actions on the [PurchaseWrapper] should be performed on this state.
/// Wait for the state becomes [PurchaseStateWrapper.purchased] to perform further actions.
///
/// A UI can be also displayed on this state to indicate the purchase is in action.
@JsonValue(2)
pending,
}
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ class SkuDetailsResponseWrapper {
int get hashCode => hashValues(billingResult, skuDetailsList);
}

/// Params containing the response code and the debug message from In-app Billing API response.
/// Params containing the response code and the debug message from the Play Billing API response.
@JsonSerializable()
@BillingResponseConverter()
class BillingResultWrapper {
Expand All @@ -190,10 +190,10 @@ class BillingResultWrapper {
factory BillingResultWrapper.fromJson(Map map) =>
_$BillingResultWrapperFromJson(map);

/// Response code returned in In-app Billing API calls.
/// Response code returned in the Play Billing API calls.
final BillingResponse responseCode;

/// Debug message returned in In-app Billing API calls.
/// Debug message returned in the Play Billing API calls.
///
/// This message uses an en-US locale and should not be shown to users.
final String debugMessage;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,19 +70,17 @@ class GooglePlayConnection
@override
Future<BillingResultWrapper> completePurchase(PurchaseDetails purchase,
{String developerPayload}) {
AcknowledgeParams params = AcknowledgeParams(
purchaseToken: purchase.verificationData.serverVerificationData,
return billingClient.acknowledgePurchase(
purchase.verificationData.serverVerificationData,
developerPayload: developerPayload);
return billingClient.acknowledgePurchase(params);
}

@override
Future<BillingResultWrapper> consumePurchase(PurchaseDetails purchase,
{String developerPayload}) {
ConsumeParams params = ConsumeParams(
purchaseToken: purchase.verificationData.serverVerificationData,
return billingClient.consumeAsync(
purchase.verificationData.serverVerificationData,
developerPayload: developerPayload);
return billingClient.consumeAsync(params);
}

@override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -174,16 +174,16 @@ abstract class InAppPurchaseConnection {
/// user.
///
/// You are responsible for completing every [PurchaseDetails] whose
/// [PurchaseDetails.status] is [PurchaseStatus.purchased].
/// Additionally, the purchase needs to be completed if the [PurchaseDetails.status]
/// [[PurchaseStatus.error].
/// [PurchaseDetails.status] is [PurchaseStatus.purchased]. Additionally on iOS,
/// the purchase needs to be completed if the [PurchaseDetails.status] is [PurchaseStatus.error].
/// Completing a [PurchaseStatus.pending] purchase will cause an exception.
/// For convenience, [PurchaseDetails.pendingCompletePurchase] indicates if a purchase is pending for completion.
///
/// The method returns a [BillingResultWrapper] to indicate a detailed status of the complete process.
///
/// Warning!Fail to call this method within 3 days of the purchase will result a refund on Android.
/// Warning!Failure to call this method within 3 days of the purchase will result a refund on Android.
/// The [consumePurchase] acts as an implicit [completePurchase] on Android.
///
/// The optional parameter `developerPayload` only works on Android.
Future<BillingResultWrapper> completePurchase(PurchaseDetails purchase,
{String developerPayload = null});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,12 +131,12 @@ class PurchaseDetails {
if (_platform == _kPlatformIOS) {
if (status == PurchaseStatus.purchased ||
status == PurchaseStatus.error) {
pendingCompletePurchase = true;
_pendingCompletePurchase = true;
}
}
if (_platform == _kPlatformAndroid) {
if (status == PurchaseStatus.purchased) {
pendingCompletePurchase = true;
_pendingCompletePurchase = true;
}
}
_status = status;
Expand All @@ -157,11 +157,13 @@ class PurchaseDetails {
/// This is null on Android.
final PurchaseWrapper billingClientPurchase;

/// The developer has to call [InAppPurchaseConnection.completePurchase] if the value is `true`.
/// The developer has to call [InAppPurchaseConnection.completePurchase] if the value is `true`
/// and the product has been delivered to the user.
///
/// The initial value is `false`.
/// * See also [InAppPurchaseConnection.completePurchase] for more details on completing purchases.
bool pendingCompletePurchase = false;
bool get pendingCompletePurchase => _pendingCompletePurchase;
bool _pendingCompletePurchase = false;

// The platform that the object is created on.
//
Expand Down Expand Up @@ -190,9 +192,9 @@ class PurchaseDetails {
? (transaction.transactionTimeStamp * 1000).toInt().toString()
: null,
this.skPaymentTransaction = transaction,
this.billingClientPurchase = null {
this.status = SKTransactionStatusConverter()
.toPurchaseStatus(transaction.transactionState);
this.billingClientPurchase = null,
_status = SKTransactionStatusConverter()
.toPurchaseStatus(transaction.transactionState) {
_platform = _kPlatformIOS;
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: I think this can go outside of the method block with the other variables that are set above it. Same with the Android one below.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

Copy link
Contributor

Choose a reason for hiding this comment

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

nit: I think _platform can also be set the same way here and below.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

}

Expand All @@ -206,9 +208,9 @@ class PurchaseDetails {
source: IAPSource.GooglePlay),
this.transactionDate = purchase.purchaseTime.toString(),
this.skPaymentTransaction = null,
this.billingClientPurchase = purchase {
this.status =
PurchaseStateConverter().toPurchaseStatus(purchase.purchaseState);
this.billingClientPurchase = purchase,
_status =
PurchaseStateConverter().toPurchaseStatus(purchase.purchaseState) {
_platform = _kPlatformAndroid;
}
}
Expand Down
2 changes: 1 addition & 1 deletion packages/in_app_purchase/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: in_app_purchase
description: A Flutter plugin for in-app purchases. Exposes APIs for making in-app purchases through the App Store and Google Play.
author: Flutter Team <[email protected]>
homepage: https://github.com/flutter/plugins/tree/master/packages/in_app_purchase
version: 0.2.2+2
version: 0.3.0


dependencies:
Expand Down
Loading