diff --git a/CHANGELOG.md b/CHANGELOG.md index 595351f..d2898e2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +## 0.2.0 +New Feature +* Introduced new API `purchaseStoreProduct` to purchase product with customer information. (#68) ## 0.1.0 New Feature * Adds show manage subscriptions settings in app (#67) diff --git a/README.md b/README.md index c48aa53..6e639e6 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ To use Chargebee SDK in your Flutter app, follow these steps: ``` dart dependencies: - chargebee_flutter: ^0.1.0 + chargebee_flutter: ^0.2.0 ``` 2. Install dependency. @@ -91,15 +91,16 @@ try { You can present any of the above products to your users for them to purchase. #### Buy or Subscribe Product +Pass the `Product` and `CBCustomer` objects to the following function when the user chooses the product to purchase. -Pass the product and customer identifier to the following function when your customer chooses the product to purchase. +`CBCustomer` - **Optional object**. Although this is an optional object, we recommend passing the necessary customer details, such as `customerId`, `firstName`, `lastName`, and `email` if it is available before the user subscribes to your App. This ensures that the customer details in your database match the customer details in Chargebee. If the `customerId` is not passed in the customer's details, then the value of `customerId` will be the same as the `subscriptionId` created in Chargebee. -`customerId` - **Optional parameter**. Although this is an optional parameter, we recommend passing customerId if it is available before user subscribes on your App. Passing this parameter ensures that customerId in your database matches with the customerId in Chargebee. -In case this parameter is not passed, then the **customerId** will be the same as the **SubscriptionId** created in Chargebee. +**Note**: The `customer` parameter in the below code snippet is an instance of `CBCustomer` class that contains the details of the customer who wants to subscribe or buy the product. ``` dart try { - final result = await Chargebee.purchaseProduct(product, customerId); + final customer = CBCustomer('customerId','firstName','lastName','emailId'); + final result = await Chargebee.purchaseStoreProduct(product, customer: customer); print("subscription id : ${result.subscriptionId}"); print("subscription status : ${result.status}"); } on PlatformException catch (e) { @@ -184,14 +185,15 @@ Receipt validation is crucial to ensure that the purchases made by your users ar * Add a network listener, as shown in the example project. * Save the product identifier in the cache once the purchase is initiated and clear the cache once the purchase is successful. -* When the network connectivity is lost after the purchase is completed at Apple App Store/Google Play Store but not synced with Chargebee, retrieve the product from the cache once the network connection is back and initiate validateReceipt() by passing `productId` and `CBCustomer(optional)` as input. This will validate the receipt and sync the purchase in Chargebee as a subscription. For subscriptions, use the function to validateReceipt(). +* When the network connectivity is lost after the purchase is completed at Apple App Store/Google Play Store but not synced with Chargebee, retrieve the product from the cache once the network connection is back and initiate `validateReceipt() / validateReceiptForNonSubscriptions()` by passing `productId` and `CBCustomer(optional)` as input. This will validate the receipt and sync the purchase in Chargebee as a subscription or one-time purchase. For subscriptions, use the function to `validateReceipt()`;for one-time purchases, use the function `validateReceiptForNonSubscriptions()`. Use the function available for the retry mechanism. ##### Function for validating the Subscriptions receipt ``` dart try { - final result = await Chargebee.validateReceipt(productId); + final customer = CBCustomer('customerId','firstName','lastName','emailId'); + final result = await Chargebee.validateReceipt(productId, customer); print("subscription id : ${result.subscriptionId}"); print("subscription status : ${result.status}"); } on PlatformException catch (e) { diff --git a/android/build.gradle b/android/build.gradle index 83d3848..0a3e233 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -1,5 +1,5 @@ group 'com.chargebee.flutter.sdk' -version '0.1.0' +version '0.2.0' buildscript { ext.kotlin_version = '1.6.0' diff --git a/android/src/main/kotlin/com/chargebee/flutter/sdk/ChargebeeFlutterSdkPlugin.kt b/android/src/main/kotlin/com/chargebee/flutter/sdk/ChargebeeFlutterSdkPlugin.kt index 9ef20e3..41c44f2 100644 --- a/android/src/main/kotlin/com/chargebee/flutter/sdk/ChargebeeFlutterSdkPlugin.kt +++ b/android/src/main/kotlin/com/chargebee/flutter/sdk/ChargebeeFlutterSdkPlugin.kt @@ -181,7 +181,12 @@ class ChargebeeFlutterSdkPlugin : FlutterPlugin, MethodCallHandler, ActivityAwar } private fun purchaseProduct(args: Map, result: Result) { - val customerID = args["customerId"] as String + val customer = CBCustomer( + args["customerId"] as String, + args["firstName"] as String, + args["lastName"] as String, + args["email"] as String + ) val arrayList: ArrayList = ArrayList() arrayList.add(args["product"] as String) CBPurchase.retrieveProducts( @@ -198,7 +203,7 @@ class ChargebeeFlutterSdkPlugin : FlutterPlugin, MethodCallHandler, ActivityAwar } CBPurchase.purchaseProduct( productIDs.first(), - customerID, + customer, object : CBCallback.PurchaseCallback { override fun onSuccess( receiptDetail: ReceiptDetail, diff --git a/example/integration_test/chargebee_test.dart b/example/integration_test/chargebee_test.dart index 5e5b8cc..8fb4d17 100644 --- a/example/integration_test/chargebee_test.dart +++ b/example/integration_test/chargebee_test.dart @@ -19,7 +19,8 @@ void main() { await chargebeeTest.retrieveProductIdentifiersWithoutParam(); await chargebeeTest.retrieveProductIdentifiersWithParam(); await chargebeeTest.retrieveProducts(); - await chargebeeTest.purchaseProducts_withCustomerInfo(); + await chargebeeTest.purchaseProducts_withCustomerId(); + await chargebeeTest.purchaseProduct_withCustomerInfo(); await chargebeeTest.purchaseProducts_withoutCustomerInfo(); await chargebeeTest.retrieveSubscriptions(); await chargebeeTest.retrieveEntitlements(); @@ -111,8 +112,14 @@ class ChargebeeTest { 'currencyCode', SubscriptionPeriod.fromMap( {'periodUnit': 'month', 'numberOfUnits': 3},),); - - Future purchaseProducts_withCustomerInfo() async { + final customer = CBCustomer( + 'abc_flutter_test', + 'fn', + 'ln', + 'abc@gmail.com', + ); + + Future purchaseProducts_withCustomerId() async { tester.printToConsole('Starting to subscribe the product'); if (platformName == 'ios') { @@ -141,7 +148,24 @@ class ChargebeeTest { } try { - final result = await Chargebee.purchaseProduct(product, 'abc'); + final result = await Chargebee.purchaseProduct(product); + debugPrint('purchase result: $result'); + expect(result.status, 'true'); + tester.printToConsole('Product subscribed successfully!'); + } on PlatformException catch (e) { + fail('Error: ${e.message}'); + } + } + + Future purchaseProduct_withCustomerInfo() async { + tester.printToConsole('Starting to subscribe the product'); + if (platformName == 'ios') { + _getProduct(productIdForiOS); + } else { + _getProduct(productIdForAndroid); + } + try { + final result = await Chargebee.purchaseStoreProduct(product, customer: customer); debugPrint('purchase result: $result'); expect(result.status, 'true'); tester.printToConsole('Product subscribed successfully!'); diff --git a/example/lib/product_listview.dart b/example/lib/product_listview.dart index 669f6ed..76506a3 100644 --- a/example/lib/product_listview.dart +++ b/example/lib/product_listview.dart @@ -86,7 +86,8 @@ class ProductListViewState extends State { if (product.subscriptionPeriod.unit.isNotEmpty && product.subscriptionPeriod.numberOfUnits != 0) { mProgressBarUtil.showProgressDialog(); - purchaseProduct(product); + // purchaseProduct(product); + purchaseStoreProduct(product); } else { _showDialog(context, product); } @@ -97,7 +98,46 @@ class ProductListViewState extends State { Future purchaseProduct(Product product) async { try { - final result = await Chargebee.purchaseProduct(product, 'customerId'); + final result = await Chargebee.purchaseProduct(product, 'abc'); + debugPrint('subscription result : $result'); + debugPrint('subscription id : ${result.subscriptionId}'); + debugPrint('plan id : ${result.planId}'); + debugPrint('subscription status : ${result.status}'); + + mProgressBarUtil.hideProgressDialog(); + + if (result.status == 'true') { + _showSuccessDialog(context, 'Success'); + } else { + _showSuccessDialog(context, result.subscriptionId); + } + } on PlatformException catch (e) { + debugPrint( + 'Error Message: ${e.message}, Error Details: ${e.details}, Error Code: ${e.code}'); + mProgressBarUtil.hideProgressDialog(); + if (e.code.isNotEmpty) { + final responseCode = int.parse(e.code); + if (responseCode >= 500 && responseCode <= 599) { + /// Cache the productId in SharedPreferences if failed synching with Chargebee. + final prefs = await SharedPreferences.getInstance(); + prefs.setString('productId', product.id); + + /// validate the receipt + validateReceipt(product.id); + } + } + } + } + + Future purchaseStoreProduct(Product product) async { + try { + final customer = CBCustomer( + 'abc_flutter_test', + 'fn', + 'ln', + 'abc@gmail.com', + ); + final result = await Chargebee.purchaseStoreProduct(product, customer: customer); debugPrint('subscription result : $result'); debugPrint('subscription id : ${result.subscriptionId}'); debugPrint('plan id : ${result.planId}'); diff --git a/ios/Classes/SwiftChargebeeFlutterSdkPlugin.swift b/ios/Classes/SwiftChargebeeFlutterSdkPlugin.swift index 9a9e1ef..ce336c3 100644 --- a/ios/Classes/SwiftChargebeeFlutterSdkPlugin.swift +++ b/ios/Classes/SwiftChargebeeFlutterSdkPlugin.swift @@ -60,7 +60,7 @@ public class SwiftChargebeeFlutterSdkPlugin: NSObject, FlutterPlugin { return _result(FlutterError.noArgsError) } let productId = params["product"] - let customerId = params["customerId"] + let customer = CBCustomer(customerID: params["customerId"], firstName:params["firstName"], lastName: params["lastName"], email:params["email"]) var dict = [String:String]() CBPurchase.shared.retrieveProducts(withProductID: [productId!], completion: { result in DispatchQueue.main.async { @@ -68,7 +68,7 @@ public class SwiftChargebeeFlutterSdkPlugin: NSObject, FlutterPlugin { case let .success(products): debugPrint("products: \(products)"); let product: CBProduct = products.self.first!; - CBPurchase.shared.purchaseProduct(product: product, customerId: customerId) { result in + CBPurchase.shared.purchaseProduct(product: product, customer: customer) { result in switch result { case .success(let result): if let subscriptionId = result.subscriptionId, let planId = result.planId{ diff --git a/ios/chargebee_flutter.podspec b/ios/chargebee_flutter.podspec index 184c667..82f9ba0 100644 --- a/ios/chargebee_flutter.podspec +++ b/ios/chargebee_flutter.podspec @@ -1,7 +1,7 @@ Pod::Spec.new do |s| s.name = 'chargebee_flutter' - s.version = '0.1.0' + s.version = '0.2.0' s.summary = 'This is the official Software Development Kit (SDK) for Chargebee Flutter.' s.description = <<-DESC A new Flutter plugin. diff --git a/lib/src/chargebee.dart b/lib/src/chargebee.dart index 98a856c..350ed67 100644 --- a/lib/src/chargebee.dart +++ b/lib/src/chargebee.dart @@ -80,14 +80,39 @@ class Chargebee { /// /// If purchase success [PurchaseResult] object be returned. /// Throws an [PlatformException] in case of failure. + @Deprecated( + 'This method will be removed in upcoming release, Use purchaseStoreProduct API instead', + ) static Future purchaseProduct( Product product, [ String? customerId = '', ]) async { - customerId ??= ''; + final map = _convertToMap(product, customerId: customerId); + return _purchaseResult(map); + } + + /// Buy the product with/without customer data. + /// + /// [product] product object to be passed. + /// + /// [customer] it can be optional. + /// if passed, the subscription will be created by using customerId in chargebee. + /// if not passed, the value of customerId is same as SubscriptionId. + /// + /// If purchase success [PurchaseResult] object be returned. + /// Throws an [PlatformException] in case of failure. + static Future purchaseStoreProduct( + Product product, { + CBCustomer? customer, + }) async { + final map = _convertToMap(product, customer: customer); + return _purchaseResult(map); + } + + static Future _purchaseResult(Map params) async { final String purchaseResult = await platform.invokeMethod( Constants.mPurchaseProduct, - {Constants.product: product.id, Constants.customerId: customerId}, + params, ); if (purchaseResult.isNotEmpty) { return PurchaseResult.fromJson(jsonDecode(purchaseResult.toString())); @@ -387,4 +412,24 @@ class Chargebee { } return NonSubscriptionPurchaseResult.fromJson(jsonDecode(purchaseResult)); } + + static Map _convertToMap( + Product product, { + String? customerId = '', + CBCustomer? customer, + }) { + String? id = ''; + if (customerId?.isNotEmpty ?? false) { + id = customerId; + } else if (customer?.id?.isNotEmpty ?? false) { + id = customer?.id; + } + return { + Constants.product: product.id, + Constants.customerId: id, + Constants.firstName: customer?.firstName ?? '', + Constants.lastName: customer?.lastName ?? '', + Constants.email: customer?.email ?? '', + }; + } } diff --git a/pubspec.yaml b/pubspec.yaml index ea94559..f456124 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: chargebee_flutter description: This is the official Software Development Kit (SDK) for Chargebee Flutter. -version: 0.1.0 +version: 0.2.0 homepage: 'https://chargebee.com' repository: 'https://github.com/chargebee/chargebee-flutter' diff --git a/test/chargebee_test.dart b/test/chargebee_test.dart index 366e036..70250aa 100644 --- a/test/chargebee_test.dart +++ b/test/chargebee_test.dart @@ -179,15 +179,27 @@ void main() { ); const purchaseResult = '''{"subscriptionId":"cb-dsd", "planId":"test", "status":"active"}'''; - + final customer = CBCustomer( + 'abc', + '', + '', + '', + ); + final params = { + Constants.product: product.id, + Constants.customerId: customer.id ?? '', + Constants.firstName: customer.firstName ?? '', + Constants.lastName: customer.lastName ?? '', + Constants.email: customer.email ?? '', + }; test('returns subscription result for Android', () async { channelResponse = purchaseResult; debugDefaultTargetPlatformOverride = TargetPlatform.android; - final result = await Chargebee.purchaseProduct(product); + final result = await Chargebee.purchaseProduct(product, 'abc'); expect(callStack, [ isMethodCall( Constants.mPurchaseProduct, - arguments: {Constants.product: product.id, Constants.customerId: ''}, + arguments: params, ) ]); expect(result.status, 'active'); @@ -196,11 +208,37 @@ void main() { test('returns subscription result for iOS', () async { channelResponse = purchaseResult; debugDefaultTargetPlatformOverride = TargetPlatform.iOS; - final result = await Chargebee.purchaseProduct(product); + final result = await Chargebee.purchaseProduct(product, 'abc'); + expect(callStack, [ + isMethodCall( + Constants.mPurchaseProduct, + arguments: params, + ) + ]); + expect(result.status, 'active'); + }); + + test('subscribed with customer id for Android', () async { + channelResponse = purchaseResult; + debugDefaultTargetPlatformOverride = TargetPlatform.android; + final result = await Chargebee.purchaseProduct(product, 'abc'); + expect(callStack, [ + isMethodCall( + Constants.mPurchaseProduct, + arguments: params, + ) + ]); + expect(result.status, 'active'); + }); + + test('subscribed with customer id for iOS', () async { + channelResponse = purchaseResult; + debugDefaultTargetPlatformOverride = TargetPlatform.android; + final result = await Chargebee.purchaseProduct(product, 'abc'); expect(callStack, [ isMethodCall( Constants.mPurchaseProduct, - arguments: {Constants.product: product.id, Constants.customerId: ''}, + arguments: params, ) ]); expect(result.status, 'active'); @@ -209,13 +247,16 @@ void main() { test('subscribed with customer info for Android', () async { channelResponse = purchaseResult; debugDefaultTargetPlatformOverride = TargetPlatform.android; - final result = await Chargebee.purchaseProduct(product, 'asz'); + final result = await Chargebee.purchaseStoreProduct(product, customer: CBCustomer('abc_flutter_test', 'flutter', 'test', 'abc@gmail.com')); expect(callStack, [ isMethodCall( Constants.mPurchaseProduct, arguments: { Constants.product: product.id, - Constants.customerId: 'asz' + Constants.customerId: 'abc_flutter_test', + Constants.firstName: 'flutter', + Constants.lastName: 'test', + Constants.email: 'abc@gmail.com', }, ) ]); @@ -225,13 +266,16 @@ void main() { test('subscribed with customer info for iOS', () async { channelResponse = purchaseResult; debugDefaultTargetPlatformOverride = TargetPlatform.android; - final result = await Chargebee.purchaseProduct(product, 'ast'); + final result = await Chargebee.purchaseStoreProduct(product, customer: CBCustomer('abc_flutter_test', 'flutter', 'test', 'abc@gmail.com')); expect(callStack, [ isMethodCall( Constants.mPurchaseProduct, arguments: { Constants.product: product.id, - Constants.customerId: 'ast' + Constants.customerId: 'abc_flutter_test', + Constants.firstName: 'flutter', + Constants.lastName: 'test', + Constants.email: 'abc@gmail.com', }, ) ]);