Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
953338a
Enable alternitive billing only available check, add test and code to…
reidbaker Feb 2, 2024
8ab32cc
Enable alternative billing only during client creation and tests cove…
reidbaker Feb 2, 2024
4d06253
ShowAlternativeBillingDialog android native method added
reidbaker Feb 2, 2024
8d95e87
Add tests for null activity behavior
reidbaker Feb 2, 2024
3878b4f
Remove not needed lines of code
reidbaker Feb 2, 2024
e516655
Add showAlternativeBillingOnlyInformationDialog and isAlternativeBill…
reidbaker Feb 5, 2024
c8efd99
test showAlternativeBillingOnlyInformationDialog and isAlternativeBil…
reidbaker Feb 5, 2024
3bd6a95
add alternative billing only reporting details implementation and tests
reidbaker Feb 5, 2024
3e77c14
formatting and generated code
reidbaker Feb 5, 2024
b77152b
Changelog and version bump added
reidbaker Feb 5, 2024
8cacaef
Formatting
reidbaker Feb 5, 2024
d4c4454
Merge branch 'main' into i142618-alternitive-billing-api-intro
reidbaker Feb 6, 2024
2f91615
Update to api 34 (required to test end to end, update dependencies, f…
reidbaker Feb 6, 2024
fe7a677
Use 0.3.1 instead of 0+19
reidbaker Feb 6, 2024
95214de
Expose country code as labled button
reidbaker Feb 7, 2024
fd506c2
Expose other alternative apis as buttons in their own section
reidbaker Feb 7, 2024
6415bee
formatting
reidbaker Feb 7, 2024
448dd2c
Add test for BillingClientManager alternative billing only
reidbaker Feb 8, 2024
1524fe0
Add platform addition tests
reidbaker Feb 8, 2024
7507f02
Show the dialog result code
reidbaker Feb 8, 2024
2ae9119
Set alternative billing only to true in example app
reidbaker Feb 8, 2024
99b3baf
Speling mistake fix
reidbaker Feb 9, 2024
b72cd39
Merge branch 'main' into i142618-alternitive-billing-api-intro
reidbaker Feb 9, 2024
240df4a
revert dependencies since they conflict with integration_test
reidbaker Feb 9, 2024
0723af9
changelog copy change
reidbaker Feb 9, 2024
16ce293
Migrate to enum for alternative billing only and play billing
reidbaker Feb 9, 2024
dfbb6b7
java formatting
reidbaker Feb 9, 2024
d073552
Include generated code change
reidbaker Feb 9, 2024
8e041af
Use json conversion for sending object over wire
reidbaker Feb 9, 2024
fe9d77e
Change button text
reidbaker Feb 9, 2024
31788de
Merge branch 'main' into i142618-alternitive-billing-api-intro
reidbaker Feb 9, 2024
31008fa
billingChoiceMode instead of enableAlternatitveBillingOnly
reidbaker Feb 9, 2024
b59c645
billing choice converstion to java
reidbaker Feb 10, 2024
7b6b7a2
Code review feedback, documentation, formatting, spelling
reidbaker Feb 12, 2024
b144067
Add alternative billing only reporting details example code
reidbaker Feb 12, 2024
8450aaa
Changlog formatting
reidbaker Feb 12, 2024
9730db3
Merge branch 'main' into i142618-alternitive-billing-api-intro
reidbaker Feb 12, 2024
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
Migrate to enum for alternative billing only and play billing
  • Loading branch information
reidbaker committed Feb 9, 2024
commit 16ce29350a857030ada42f092b50f8b98a4d0e8a
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,11 @@ interface BillingClientFactory {
*
* @param context The context used to create the {@link BillingClient}.
* @param channel The method channel used to create the {@link BillingClient}.
* @param enableAlternativeBillingOnly Enables the ability to offer alternative billing without
* user choice to use Google Play billing.
* @param billingChoiceMode Enables the ability to offer alternative billing or Google Play billing.
* @return The {@link BillingClient} object that is created.
*/
BillingClient createBillingClient(
@NonNull Context context,
@NonNull MethodChannel channel,
boolean enableAlternativeBillingOnly);
int billingChoiceMode);
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import androidx.annotation.NonNull;
import com.android.billingclient.api.BillingClient;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugins.inapppurchase.MethodCallHandlerImpl.BillingChoiceMode;

/** The implementation for {@link BillingClientFactory} for the plugin. */
final class BillingClientFactoryImpl implements BillingClientFactory {
Expand All @@ -16,9 +17,9 @@ final class BillingClientFactoryImpl implements BillingClientFactory {
public BillingClient createBillingClient(
@NonNull Context context,
@NonNull MethodChannel channel,
boolean enableAlternativeBillingOnly) {
int billingChoiceMode) {
BillingClient.Builder builder = BillingClient.newBuilder(context).enablePendingPurchases();
if (enableAlternativeBillingOnly) {
if (billingChoiceMode == BillingChoiceMode.ALTERNATIVE_BILLING_ONLY) {
// https://developer.android.com/google/play/billing/alternative/alternative-billing-without-user-choice-in-app
builder.enableAlternativeBillingOnly();
Copy link
Member

@gmackall gmackall Feb 12, 2024

Choose a reason for hiding this comment

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

Not an actionable comment, but: Interesting that the billing client builder example they show at the link doesn't call enablePendingPurchases. That must be a mistake in their documentation, right? It is documented as required in all circumstances
https://developer.android.com/reference/com/android/billingclient/api/BillingClient.Builder#enablePendingPurchases()

Copy link
Contributor Author

Choose a reason for hiding this comment

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

TBH I am not sure. Organizationally I think it should be safe to include because if the caller was going to use alternative billing only then there are no pending purchases to enable or if there are all that happens on the callers side since they are providing the billing implementation.

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
import com.android.billingclient.api.QueryPurchasesParams;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
Expand Down Expand Up @@ -82,11 +83,16 @@ static final class MethodArgs {
// Key for an int argument passed into startConnection
static final String HANDLE = "handle";
// Key for a boolean argument passed into startConnection.
static final String ENABLE_ALTERNATIVE_BILLING_ONLY = "enableAlternativeBillingOnly";
static final String BILLING_CHOICE_MODE = "billingChoiceMode";

private MethodArgs() {}
}

static final class BillingChoiceMode {
static final int PLAY_BILLING_ONLY = 0;
static final int ALTERNATIVE_BILLING_ONLY = 1;
}

// TODO(gmackall): Replace uses of deprecated ProrationMode enum values with new
// ReplacementMode enum values.
// https://github.com/flutter/flutter/issues/128957.
Expand Down Expand Up @@ -167,12 +173,11 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result
break;
case MethodNames.START_CONNECTION:
final int handle = (int) call.argument(MethodArgs.HANDLE);
boolean enableAlternativeBillingOnly = false;
if (call.hasArgument(MethodArgs.ENABLE_ALTERNATIVE_BILLING_ONLY)
&& (boolean) call.argument(MethodArgs.ENABLE_ALTERNATIVE_BILLING_ONLY)) {
enableAlternativeBillingOnly = true;
int billingChoiceMode = BillingChoiceMode.PLAY_BILLING_ONLY;
if (call.hasArgument(MethodArgs.BILLING_CHOICE_MODE)) {
billingChoiceMode = call.argument(MethodArgs.BILLING_CHOICE_MODE);
}
startConnection(handle, result, enableAlternativeBillingOnly);
startConnection(handle, result, billingChoiceMode);
break;
case MethodNames.END_CONNECTION:
endConnection(result);
Expand Down Expand Up @@ -497,11 +502,11 @@ private void getConnectionState(final MethodChannel.Result result) {
}

private void startConnection(
final int handle, final MethodChannel.Result result, boolean enableAlternativeBillingOnly) {
final int handle, final MethodChannel.Result result, int billingChoiceMode) {
if (billingClient == null) {
billingClient =
billingClientFactory.createBillingClient(
applicationContext, methodChannel, enableAlternativeBillingOnly);
applicationContext, methodChannel, billingChoiceMode);
}

billingClient.startConnection(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugin.common.MethodChannel.Result;
import io.flutter.plugins.inapppurchase.MethodCallHandlerImpl.BillingChoiceMode;
import io.flutter.plugins.inapppurchase.MethodCallHandlerImpl.MethodArgs;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
Expand Down Expand Up @@ -110,9 +111,9 @@ public class MethodCallHandlerTest {
public void setUp() {
MockitoAnnotations.openMocks(this);
// Use the same client no matter if alternative billing is enabled or not.
when(factory.createBillingClient(context, mockMethodChannel, false))
when(factory.createBillingClient(context, mockMethodChannel, BillingChoiceMode.PLAY_BILLING_ONLY))
.thenReturn(mockBillingClient);
when(factory.createBillingClient(context, mockMethodChannel, true))
when(factory.createBillingClient(context, mockMethodChannel, BillingChoiceMode.ALTERNATIVE_BILLING_ONLY))
.thenReturn(mockBillingClient);
methodChannelHandler = new MethodCallHandlerImpl(activity, context, mockMethodChannel, factory);
when(mockActivityPluginBinding.getActivity()).thenReturn(activity);
Expand Down Expand Up @@ -157,9 +158,9 @@ public void isReady_clientDisconnected() {

@Test
public void startConnection() {
ArgumentCaptor<BillingClientStateListener> captor = mockStartConnection(false);
ArgumentCaptor<BillingClientStateListener> captor = mockStartConnection(BillingChoiceMode.PLAY_BILLING_ONLY);
verify(result, never()).success(any());
verify(factory, times(1)).createBillingClient(context, mockMethodChannel, false);
verify(factory, times(1)).createBillingClient(context, mockMethodChannel, BillingChoiceMode.PLAY_BILLING_ONLY);

BillingResult billingResult =
BillingResult.newBuilder()
Expand All @@ -173,9 +174,9 @@ public void startConnection() {

@Test
public void startConnectionAlternativeBillingOnly() {
ArgumentCaptor<BillingClientStateListener> captor = mockStartConnection(true);
ArgumentCaptor<BillingClientStateListener> captor = mockStartConnection(BillingChoiceMode.ALTERNATIVE_BILLING_ONLY);
verify(result, never()).success(any());
verify(factory, times(1)).createBillingClient(context, mockMethodChannel, true);
verify(factory, times(1)).createBillingClient(context, mockMethodChannel, BillingChoiceMode.ALTERNATIVE_BILLING_ONLY);

BillingResult billingResult =
BillingResult.newBuilder()
Expand All @@ -200,7 +201,7 @@ public void startConnectionAlternativeBillingUnset() {

methodChannelHandler.onMethodCall(call, result);
verify(result, never()).success(any());
verify(factory, times(1)).createBillingClient(context, mockMethodChannel, false);
verify(factory, times(1)).createBillingClient(context, mockMethodChannel, BillingChoiceMode.PLAY_BILLING_ONLY);

BillingResult billingResult =
BillingResult.newBuilder()
Expand Down Expand Up @@ -1049,14 +1050,14 @@ public void isFutureSupported_false() {
}

private ArgumentCaptor<BillingClientStateListener> mockStartConnection() {
return mockStartConnection(false);
return mockStartConnection(BillingChoiceMode.PLAY_BILLING_ONLY);
}

private ArgumentCaptor<BillingClientStateListener> mockStartConnection(
boolean enableAlternativeBillingOnly) {
int billingChoiceMode) {
Map<String, Object> arguments = new HashMap<>();
arguments.put(MethodArgs.HANDLE, 1);
arguments.put(MethodArgs.ENABLE_ALTERNATIVE_BILLING_ONLY, enableAlternativeBillingOnly);
arguments.put(MethodArgs.BILLING_CHOICE_MODE, billingChoiceMode);
MethodCall call = new MethodCall(START_CONNECTION, arguments);
ArgumentCaptor<BillingClientStateListener> captor =
ArgumentCaptor.forClass(BillingClientStateListener.class);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -286,7 +286,7 @@ class _MyAppState extends State<_MyApp> {
final InAppPurchaseAndroidPlatformAddition addition =
InAppPurchasePlatformAddition.instance!
as InAppPurchaseAndroidPlatformAddition;
unawaited(addition.setAlternativeBillingOnlyState(true));
unawaited(addition.setBillingChoice(BillingChoiceMode.alternativeBillingOnly));
},
child: const Text('setAlternativeBillingOnlyState true'),
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,8 @@ class BillingClientManager {
/// Creates the [BillingClientManager].
///
/// Immediately initializes connection to the underlying [BillingClient].
/// [enableAlternativeBillingOnly] if customers should only see alternitive billing.
///
/// Callers need to check if AlternativeBillingOnly is available by calling
/// [BillingClientWrapper.isAlternativeBillingOnlyAvailable] first.
BillingClientManager() : _enableAlternativeBillingOnly = false {
BillingClientManager()
: _billingChoiceMode = BillingChoiceMode.playBillingOnly {
_connect();
}

Expand All @@ -57,7 +54,7 @@ class BillingClientManager {
final StreamController<PurchasesResultWrapper> _purchasesUpdatedController =
StreamController<PurchasesResultWrapper>.broadcast();

bool _enableAlternativeBillingOnly;
BillingChoiceMode _billingChoiceMode;
bool _isConnecting = false;
bool _isDisposed = false;

Expand Down Expand Up @@ -124,10 +121,14 @@ class BillingClientManager {
_purchasesUpdatedController.close();
}

/// Ends connection to [BillingClient] and reconnects with [alternativeBillingState].
Future<void> reconnectWithAlternativeBillingOnlyState(
bool alternativeBillingOnlyState) async {
_enableAlternativeBillingOnly = alternativeBillingOnlyState;
/// Ends connection to [BillingClient] and reconnects with [billingChoiceMode].
///
/// Callers need to check if [BillingChoiceMode.alternativeBillingOnly] is
/// available by calling [BillingClientWrapper.isAlternativeBillingOnlyAvailable]
/// first.
Future<void> reconnectWithBillingChoiceMode(
BillingChoiceMode billingChoiceMode) async {
_billingChoiceMode = billingChoiceMode;
// Ends connection and triggers OnBillingServiceDisconnected, which causes reconnect;
await client.endConnection();
await _connect();
Expand All @@ -147,7 +148,7 @@ class BillingClientManager {
_readyFuture = Future<void>.sync(() async {
await client.startConnection(
onBillingServiceDisconnected: _connect,
enableAlternativeBillingOnly: _enableAlternativeBillingOnly);
billingChoiceMode: _billingChoiceMode);
_isConnecting = false;
});
return _readyFuture;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,8 @@ class BillingClient {
/// one doesn't already exist.
Future<BillingResultWrapper> startConnection(
{required OnBillingServiceDisconnected onBillingServiceDisconnected,
bool enableAlternativeBillingOnly = false}) async {
BillingChoiceMode billingChoiceMode =
BillingChoiceMode.playBillingOnly}) async {
final List<Function> disconnectCallbacks =
_callbacks[_kOnBillingServiceDisconnected] ??= <Function>[];
disconnectCallbacks.add(onBillingServiceDisconnected);
Expand All @@ -120,7 +121,7 @@ class BillingClient {
'BillingClient#startConnection(BillingClientStateListener)',
<String, dynamic>{
'handle': disconnectCallbacks.length - 1,
'enableAlternativeBillingOnly': enableAlternativeBillingOnly,
'billingChoiceMode': billingChoiceMode,
})) ??
<String, dynamic>{});
}
Expand Down Expand Up @@ -487,6 +488,21 @@ enum BillingResponse {
networkError,
}

/// Plugin concept to cover billing modes.
///
/// [playBillingOnly] (google play billing only).
/// [alternativeBillingOnly] (app provided billing with reporting to play).
@JsonEnum(alwaysCreate: true)
enum BillingChoiceMode {
/// Billing through google play. Default state.
@JsonValue(0)
playBillingOnly,

/// Billing through app provided flow.
@JsonValue(1)
alternativeBillingOnly,
}

/// Serializer for [BillingResponse].
///
/// Use these in `@JsonSerializable()` classes by annotating them with
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -183,13 +183,13 @@ class InAppPurchaseAndroidPlatformAddition
/// Disconnects, Sets AlternativeBillingOnly to true, and recoonects to
/// the [BillingClient].
///
/// [alternativeBillingOnlyState] true will enable alternative billing only.
/// [BillingChoiceMode.playBillingOnly] is the default state used.
/// [BillingChoiceMode.alternativeBillingOnly] will enable alternative billing only.
///
/// Play apis have requirements for when this method can be called.
/// See: https://developer.android.com/google/play/billing/alternative/alternative-billing-without-user-choice-in-app
Future<void> setAlternativeBillingOnlyState(
bool alternativeBillingOnlyState) {
Future<void> setBillingChoice(BillingChoiceMode billingChoiceMode) {
return _billingClientManager
.reconnectWithAlternativeBillingOnlyState(alternativeBillingOnlyState);
.reconnectWithBillingChoiceMode(billingChoiceMode);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -81,13 +81,14 @@ void main() {
expect(stubPlatform.countPreviousCalls(startConnectionCall), equals(2));
});

test('re-connects when host calls reconnectWithAlternativeBillingOnlyState',
test('re-connects when host calls reconnectWithBillingChoiceMode',
() async {
connectedCompleter.complete();
// Ensures all asynchronous connected code finishes.
await manager.runWithClientNonRetryable((_) async {});

await manager.reconnectWithAlternativeBillingOnlyState(true);
await manager.reconnectWithBillingChoiceMode(
BillingChoiceMode.alternativeBillingOnly);
// Verify that connection was ended.
expect(stubPlatform.countPreviousCalls(endConnectionCall), equals(1));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ void main() {
);
await billingClient.startConnection(
onBillingServiceDisconnected: () {},
enableAlternativeBillingOnly: true);
billingChoiceMode: BillingChoiceMode.alternativeBillingOnly);
final MethodCall call = stubPlatform.previousCallMatching(methodName);
expect(
call.arguments,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,15 +87,15 @@ void main() {

group('setAlternativeBillingOnlyState', () {
late Map<Object?, Object?> arguments;
test('setAlternativeBillingOnlyState true', () async {
test('setAlternativeBillingOnlyState', () async {
stubPlatform.reset();
stubPlatform.addResponse(
name: startConnectionCall,
additionalStepBeforeReturn: (dynamic value) =>
arguments = value as Map<dynamic, dynamic>,
);
stubPlatform.addResponse(name: endConnectionCall);
await iapAndroidPlatformAddition.setAlternativeBillingOnlyState(true);
await iapAndroidPlatformAddition.setBillingChoice(BillingChoiceMode.alternativeBillingOnly);

/// Fake the disconnect that we would expect from a endConnectionCall.
await manager.client.callHandler(
Expand All @@ -104,18 +104,18 @@ void main() {
);
// Verify that after connection ended reconnect was called.
expect(stubPlatform.countPreviousCalls(startConnectionCall), equals(2));
expect(arguments['enableAlternativeBillingOnly'], isTrue);
expect(arguments['billingChoiceMode'], BillingChoiceMode.alternativeBillingOnly);
});

test('setAlternativeBillingOnlyState false', () async {
test('setPlayBillingState', () async {
stubPlatform.reset();
stubPlatform.addResponse(
name: startConnectionCall,
additionalStepBeforeReturn: (dynamic value) =>
arguments = value as Map<dynamic, dynamic>,
);
stubPlatform.addResponse(name: endConnectionCall);
await iapAndroidPlatformAddition.setAlternativeBillingOnlyState(false);
await iapAndroidPlatformAddition.setBillingChoice(BillingChoiceMode.playBillingOnly);

/// Fake the disconnect that we would expect from a endConnectionCall.
await manager.client.callHandler(
Expand All @@ -124,7 +124,7 @@ void main() {
);
// Verify that after connection ended reconnect was called.
expect(stubPlatform.countPreviousCalls(startConnectionCall), equals(2));
expect(arguments['enableAlternativeBillingOnly'], isFalse);
expect(arguments['billingChoiceMode'], BillingChoiceMode.playBillingOnly);
});
});

Expand Down