@capgo/native-purchases 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,17 @@
1
+ require 'json'
2
+
3
+ package = JSON.parse(File.read(File.join(__dir__, 'package.json')))
4
+
5
+ Pod::Spec.new do |s|
6
+ s.name = 'NativePurchases'
7
+ s.version = package['version']
8
+ s.summary = package['description']
9
+ s.license = package['license']
10
+ s.homepage = package['repository']['url']
11
+ s.author = package['author']
12
+ s.source = { :git => package['repository']['url'], :tag => s.version.to_s }
13
+ s.source_files = 'ios/Plugin/**/*.{swift,h,m,c,cc,mm,cpp}'
14
+ s.ios.deployment_target = '13.0'
15
+ s.dependency 'Capacitor'
16
+ s.swift_version = '5.1'
17
+ end
package/README.md ADDED
@@ -0,0 +1,137 @@
1
+ # native-purchases
2
+
3
+ In-app Subscriptions Made Easy
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install native-purchases
9
+ npx cap sync
10
+ ```
11
+
12
+ ## API
13
+
14
+ <docgen-index>
15
+
16
+ * [`restorePurchases()`](#restorepurchases)
17
+ * [`purchaseProduct(...)`](#purchaseproduct)
18
+ * [`getProducts(...)`](#getproducts)
19
+ * [Interfaces](#interfaces)
20
+
21
+ </docgen-index>
22
+
23
+ <docgen-api>
24
+ <!--Update the source file JSDoc comments and rerun docgen to update the docs below-->
25
+
26
+ ### restorePurchases()
27
+
28
+ ```typescript
29
+ restorePurchases() => any
30
+ ```
31
+
32
+ Restores a user's previous and links their appUserIDs to any user's also using those .
33
+
34
+ **Returns:** <code>any</code>
35
+
36
+ --------------------
37
+
38
+
39
+ ### purchaseProduct(...)
40
+
41
+ ```typescript
42
+ purchaseProduct(options: { productIdentifier: string; quantity: number; }) => any
43
+ ```
44
+
45
+ | Param | Type |
46
+ | ------------- | ------------------------------------------------------------- |
47
+ | **`options`** | <code>{ productIdentifier: string; quantity: number; }</code> |
48
+
49
+ **Returns:** <code>any</code>
50
+
51
+ --------------------
52
+
53
+
54
+ ### getProducts(...)
55
+
56
+ ```typescript
57
+ getProducts(options: { productIdentifiers: string[]; }) => any
58
+ ```
59
+
60
+ | Param | Type |
61
+ | ------------- | ---------------------------------------- |
62
+ | **`options`** | <code>{ productIdentifiers: {}; }</code> |
63
+
64
+ **Returns:** <code>any</code>
65
+
66
+ --------------------
67
+
68
+
69
+ ### Interfaces
70
+
71
+
72
+ #### CustomerInfo
73
+
74
+ | Prop | Type | Description |
75
+ | ------------------------------------ | --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
76
+ | **`activeSubscriptions`** | <code>[string]</code> | Set of active subscription skus |
77
+ | **`allPurchasedProductIdentifiers`** | <code>[string]</code> | Set of purchased skus, active and inactive |
78
+ | **`nonSubscriptionTransactions`** | <code>{}</code> | Returns all the non-subscription a user has made. The are ordered by purchase date in ascending order. |
79
+ | **`latestExpirationDate`** | <code>string \| null</code> | The latest expiration date of all purchased skus |
80
+ | **`firstSeen`** | <code>string</code> | The date this user was first seen in RevenueCat. |
81
+ | **`originalAppUserId`** | <code>string</code> | The original App User Id recorded for this user. |
82
+ | **`requestDate`** | <code>string</code> | Date when this info was requested |
83
+ | **`originalApplicationVersion`** | <code>string \| null</code> | Returns the version number for the version of the application when the user bought the app. Use this for grandfathering users when migrating to subscriptions. This corresponds to the value of CFBundleVersion (in iOS) in the Info.plist file when the purchase was originally made. This is always null in Android |
84
+ | **`originalPurchaseDate`** | <code>string \| null</code> | Returns the purchase date for the version of the application when the user bought the app. Use this for grandfathering users when migrating to subscriptions. |
85
+ | **`managementURL`** | <code>string \| null</code> | URL to manage the active subscription of the user. If this user has an active iOS subscription, this will point to the App Store, if the user has an active Play Store subscription it will point there. If there are no active subscriptions it will be null. If there are multiple for different platforms, it will point to the device store. |
86
+
87
+
88
+ #### Transaction
89
+
90
+ | Prop | Type | Description |
91
+ | --------------------------- | ------------------- | ------------------------------------------------------------------ |
92
+ | **`transactionIdentifier`** | <code>string</code> | RevenueCat Id associated to the transaction. |
93
+ | **`productIdentifier`** | <code>string</code> | <a href="#product">Product</a> Id associated with the transaction. |
94
+ | **`purchaseDate`** | <code>string</code> | Purchase date of the transaction in ISO 8601 format. |
95
+
96
+
97
+ #### Product
98
+
99
+ | Prop | Type | Description |
100
+ | --------------------------------- | ----------------------------------------------------------------------- | ------------------------------------------------------------------------ |
101
+ | **`identifier`** | <code>string</code> | <a href="#product">Product</a> Id. |
102
+ | **`description`** | <code>string</code> | Description of the product. |
103
+ | **`title`** | <code>string</code> | Title of the product. |
104
+ | **`price`** | <code>number</code> | Price of the product in the local currency. |
105
+ | **`priceString`** | <code>string</code> | Formatted price of the item, including its currency sign, such as €3.99. |
106
+ | **`currencyCode`** | <code>string</code> | Currency code for price and original price. |
107
+ | **`currencySymbol`** | <code>string</code> | Currency symbol for price and original price. |
108
+ | **`isFamilyShareable`** | <code>boolean</code> | Boolean indicating if the product is sharable with family |
109
+ | **`subscriptionGroupIdentifier`** | <code>string</code> | Group identifier for the product. |
110
+ | **`subscriptionPeriod`** | <code><a href="#subscriptionperiod">SubscriptionPeriod</a></code> | The <a href="#product">Product</a> subcription group identifier. |
111
+ | **`introductoryPrice`** | <code><a href="#skproductdiscount">SKProductDiscount</a> \| null</code> | The <a href="#product">Product</a> introductory Price. |
112
+ | **`discounts`** | <code>{}</code> | The <a href="#product">Product</a> discounts list. |
113
+
114
+
115
+ #### SubscriptionPeriod
116
+
117
+ | Prop | Type | Description |
118
+ | ------------------- | ------------------- | --------------------------------------- |
119
+ | **`numberOfUnits`** | <code>number</code> | The Subscription Period number of unit. |
120
+ | **`unit`** | <code>number</code> | The Subscription Period unit. |
121
+
122
+
123
+ #### SKProductDiscount
124
+
125
+ | Prop | Type | Description |
126
+ | ------------------------ | ----------------------------------------------------------------- | ------------------------------------------------------------------------ |
127
+ | **`identifier`** | <code>string</code> | The <a href="#product">Product</a> discount identifier. |
128
+ | **`type`** | <code>number</code> | The <a href="#product">Product</a> discount type. |
129
+ | **`price`** | <code>number</code> | The <a href="#product">Product</a> discount price. |
130
+ | **`priceString`** | <code>string</code> | Formatted price of the item, including its currency sign, such as €3.99. |
131
+ | **`currencySymbol`** | <code>string</code> | The <a href="#product">Product</a> discount currency symbol. |
132
+ | **`currencyCode`** | <code>string</code> | The <a href="#product">Product</a> discount currency code. |
133
+ | **`paymentMode`** | <code>number</code> | The <a href="#product">Product</a> discount paymentMode. |
134
+ | **`numberOfPeriods`** | <code>number</code> | The <a href="#product">Product</a> discount number Of Periods. |
135
+ | **`subscriptionPeriod`** | <code><a href="#subscriptionperiod">SubscriptionPeriod</a></code> | The <a href="#product">Product</a> discount subscription period. |
136
+
137
+ </docgen-api>
@@ -0,0 +1,61 @@
1
+ ext {
2
+ junitVersion = project.hasProperty('junitVersion') ? rootProject.ext.junitVersion : '4.13.2'
3
+ androidxAppCompatVersion = project.hasProperty('androidxAppCompatVersion') ? rootProject.ext.androidxAppCompatVersion : '1.6.1'
4
+ androidxJunitVersion = project.hasProperty('androidxJunitVersion') ? rootProject.ext.androidxJunitVersion : '1.1.5'
5
+ androidxEspressoCoreVersion = project.hasProperty('androidxEspressoCoreVersion') ? rootProject.ext.androidxEspressoCoreVersion : '3.5.1'
6
+ }
7
+
8
+ buildscript {
9
+ repositories {
10
+ google()
11
+ mavenCentral()
12
+ }
13
+ dependencies {
14
+ classpath 'com.android.tools.build:gradle:8.0.0'
15
+ }
16
+ }
17
+
18
+ apply plugin: 'com.android.library'
19
+
20
+ android {
21
+ namespace "ee.forgr.nativepurchases"
22
+ compileSdkVersion project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion : 33
23
+ defaultConfig {
24
+ minSdkVersion project.hasProperty('minSdkVersion') ? rootProject.ext.minSdkVersion : 22
25
+ targetSdkVersion project.hasProperty('targetSdkVersion') ? rootProject.ext.targetSdkVersion : 33
26
+ versionCode 1
27
+ versionName "1.0"
28
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
29
+ }
30
+ buildTypes {
31
+ release {
32
+ minifyEnabled false
33
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
34
+ }
35
+ }
36
+ lintOptions {
37
+ abortOnError false
38
+ }
39
+ compileOptions {
40
+ sourceCompatibility JavaVersion.VERSION_17
41
+ targetCompatibility JavaVersion.VERSION_17
42
+ }
43
+ }
44
+
45
+ repositories {
46
+ google()
47
+ mavenCentral()
48
+ }
49
+
50
+
51
+ dependencies {
52
+ implementation "com.google.guava:guava:31.1-android"
53
+ def billing_version = "6.0.1"
54
+ implementation "com.android.billingclient:billing:$billing_version"
55
+ implementation fileTree(dir: 'libs', include: ['*.jar'])
56
+ implementation project(':capacitor-android')
57
+ implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion"
58
+ testImplementation "junit:junit:$junitVersion"
59
+ androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion"
60
+ androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion"
61
+ }
@@ -0,0 +1,2 @@
1
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android">
2
+ </manifest>
@@ -0,0 +1,195 @@
1
+ package ee.forgr.nativepurchases;
2
+
3
+ import android.util.Log;
4
+ import androidx.annotation.NonNull;
5
+
6
+ import com.android.billingclient.api.AcknowledgePurchaseParams;
7
+ import com.android.billingclient.api.AcknowledgePurchaseResponseListener;
8
+ import com.android.billingclient.api.BillingClient;
9
+ import com.android.billingclient.api.BillingFlowParams;
10
+ import com.android.billingclient.api.BillingResult;
11
+ import com.android.billingclient.api.Purchase;
12
+ import com.android.billingclient.api.PurchasesUpdatedListener;
13
+ import com.android.billingclient.api.SkuDetails;
14
+ import com.android.billingclient.api.SkuDetailsParams;
15
+ import com.android.billingclient.api.SkuDetailsResponseListener;
16
+ import com.getcapacitor.JSObject;
17
+ import com.getcapacitor.Plugin;
18
+ import com.getcapacitor.PluginCall;
19
+ import com.getcapacitor.PluginMethod;
20
+ import com.getcapacitor.annotation.CapacitorPlugin;
21
+ import com.google.common.base.CaseFormat;
22
+ import java.util.ArrayList;
23
+ import java.util.HashMap;
24
+ import java.util.Iterator;
25
+ import java.util.List;
26
+ import java.util.Map;
27
+ import org.json.JSONArray;
28
+ import org.json.JSONException;
29
+ import org.json.JSONObject;
30
+
31
+ @CapacitorPlugin(name = "NativePurchases")
32
+ public class NativePurchasesPlugin extends Plugin {
33
+
34
+ public final String PLUGIN_VERSION = "2.0.13";
35
+
36
+ private BillingClient billingClient;
37
+ private PluginCall purchaseCall;
38
+
39
+ @Override
40
+ public void load() {
41
+ super.load();
42
+ billingClient = BillingClient.newBuilder(getContext())
43
+ .setListener(new PurchasesUpdatedListener() {
44
+ @Override
45
+ public void onPurchasesUpdated(BillingResult billingResult, List<Purchase> purchases) {
46
+ if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK && purchases != null) {
47
+ for (Purchase purchase : purchases) {
48
+ handlePurchase(purchase);
49
+ }
50
+ } else {
51
+ // Handle any other error codes.
52
+ }
53
+ }
54
+ })
55
+ .enablePendingPurchases()
56
+ .build();
57
+ }
58
+
59
+ private void handlePurchase(Purchase purchase) {
60
+ if (purchase.getPurchaseState() == Purchase.PurchaseState.PURCHASED) {
61
+ // Grant entitlement to the user, then acknowledge the purchase
62
+ acknowledgePurchase(purchase.getPurchaseToken());
63
+
64
+ JSObject ret = new JSObject();
65
+ ret.put("transactionId", purchase.getPurchaseToken());
66
+ purchaseCall.resolve(ret);
67
+ } else if (purchase.getPurchaseState() == Purchase.PurchaseState.PENDING) {
68
+ // Here you can confirm to the user that they've started the pending
69
+ // purchase, and to complete it, they should follow instructions that are
70
+ // given to them. You can also choose to remind the user to complete the
71
+ // purchase if you detect that it is still pending.
72
+ }
73
+ }
74
+
75
+ private void acknowledgePurchase(String purchaseToken) {
76
+ AcknowledgePurchaseParams acknowledgePurchaseParams =
77
+ AcknowledgePurchaseParams.newBuilder()
78
+ .setPurchaseToken(purchaseToken)
79
+ .build();
80
+ billingClient.acknowledgePurchase(acknowledgePurchaseParams, new AcknowledgePurchaseResponseListener() {
81
+ @Override
82
+ public void onAcknowledgePurchaseResponse(BillingResult billingResult) {
83
+ // Handle the result of the acknowledge purchase
84
+ }
85
+ });
86
+ }
87
+
88
+ @PluginMethod
89
+ public void purchaseProduct(PluginCall call) {
90
+ String productIdentifier = call.getString("productIdentifier");
91
+ Log.d("CapacitorPurchases", "purchaseProduct: " + productIdentifier);
92
+ purchaseCall = call;
93
+
94
+ if (billingClient.isReady()) {
95
+ List<String> skuList = new ArrayList<>();
96
+ skuList.add(productIdentifier);
97
+ SkuDetailsParams.Builder params = SkuDetailsParams.newBuilder();
98
+ params.setSkusList(skuList).setType(BillingClient.SkuType.INAPP);
99
+
100
+ billingClient.querySkuDetailsAsync(params.build(), new SkuDetailsResponseListener() {
101
+ @Override
102
+ public void onSkuDetailsResponse(BillingResult billingResult, List<SkuDetails> skuDetailsList) {
103
+ if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK && skuDetailsList != null) {
104
+ for (SkuDetails skuDetails : skuDetailsList) {
105
+ BillingFlowParams flowParams = BillingFlowParams.newBuilder()
106
+ .setSkuDetails(skuDetails)
107
+ .build();
108
+ billingClient.launchBillingFlow(getActivity(), flowParams);
109
+ }
110
+ }
111
+ }
112
+ });
113
+ }
114
+ }
115
+
116
+ @PluginMethod
117
+ public void restorePurchases(PluginCall call) {
118
+ Log.i("NativePurchases", "restorePurchases");
119
+ call.resolve();
120
+ }
121
+
122
+ @PluginMethod
123
+ public void getProducts(PluginCall call) {
124
+ List<String> productIdentifiers = new ArrayList<String>();
125
+ JSONArray productIdentifiersArray = call.getArray("productIdentifiers");
126
+ Log.i("NativePurchases", "getProducts: " + productIdentifiersArray);
127
+ call.resolve();
128
+ }
129
+
130
+ //================================================================================
131
+ // Private methods
132
+ //================================================================================
133
+
134
+ private static JSObject convertMapToJson(Map<String, ?> readableMap) {
135
+ JSObject object = new JSObject();
136
+
137
+ for (Map.Entry<String, ?> entry : readableMap.entrySet()) {
138
+ String camelKey = entry.getKey().contains("_")
139
+ ? CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL, entry.getKey())
140
+ : entry.getKey();
141
+ if (entry.getValue() == null) {
142
+ object.put(camelKey, JSONObject.NULL);
143
+ } else if (entry.getValue() instanceof Map) {
144
+ object.put(
145
+ camelKey,
146
+ convertMapToJson((Map<String, Object>) entry.getValue())
147
+ );
148
+ } else if (entry.getValue() instanceof Object[]) {
149
+ object.put(
150
+ camelKey,
151
+ convertArrayToJsonArray((Object[]) entry.getValue())
152
+ );
153
+ } else if (entry.getValue() instanceof List) {
154
+ object.put(
155
+ camelKey,
156
+ convertArrayToJsonArray(((List) entry.getValue()).toArray())
157
+ );
158
+ } else if (entry.getValue() != null) {
159
+ Object value = entry.getValue();
160
+ if (camelKey == "priceString") {
161
+ String currency_symbol =
162
+ ((String) value).replaceAll("\\d", "")
163
+ .replace(".", "")
164
+ .replace(",", "");
165
+ object.put("currencySymbol", currency_symbol);
166
+ }
167
+ if (camelKey == "title") {
168
+ // value = ((String) value).replace("(" + AppName + ")", "");
169
+ value = ((String) value).replaceAll("\\((.*?)\\)", ""); // TODO find better implementation
170
+ }
171
+ object.put(camelKey, value);
172
+ }
173
+ }
174
+
175
+ return object;
176
+ }
177
+
178
+ private static JSONArray convertArrayToJsonArray(Object[] array) {
179
+ JSONArray writableArray = new JSONArray();
180
+ for (Object item : array) {
181
+ if (item == null) {
182
+ writableArray.put(JSONObject.NULL);
183
+ } else if (item instanceof Map) {
184
+ writableArray.put(convertMapToJson((Map<String, Object>) item));
185
+ } else if (item instanceof Object[]) {
186
+ writableArray.put(convertArrayToJsonArray((Object[]) item));
187
+ } else if (item instanceof List) {
188
+ writableArray.put(convertArrayToJsonArray(((List) item).toArray()));
189
+ } else {
190
+ writableArray.put(item);
191
+ }
192
+ }
193
+ return writableArray;
194
+ }
195
+ }
File without changes