@virex-tech/paywallo-sdk 1.0.0 → 1.1.0

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.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@virex-tech/paywallo-sdk",
3
- "version": "1.0.0",
4
- "description": "SDK para integração com Monety - Analytics, Feature Flags, Paywalls e Campaigns",
3
+ "version": "1.1.0",
4
+ "description": "SDK React Native para integração com Paywallo - Paywalls, Subscriptions e Analytics",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
7
7
  "types": "./dist/index.d.ts",
@@ -17,7 +17,7 @@
17
17
  "dist",
18
18
  "ios",
19
19
  "android",
20
- "MonetySdk.podspec",
20
+ "PaywalloSdk.podspec",
21
21
  "react-native.config.js"
22
22
  ],
23
23
  "react-native": "./dist/index.mjs",
@@ -41,13 +41,21 @@
41
41
  "react-native",
42
42
  "mobile",
43
43
  "subscription",
44
- "monety"
44
+ "paywallo"
45
45
  ],
46
- "author": "Monety",
46
+ "author": "Virex Tech",
47
47
  "license": "MIT",
48
48
  "publishConfig": {
49
49
  "access": "public"
50
50
  },
51
+ "repository": {
52
+ "type": "git",
53
+ "url": "https://github.com/paywallo/sdk-react-native.git"
54
+ },
55
+ "homepage": "https://github.com/paywallo/sdk-react-native#readme",
56
+ "bugs": {
57
+ "url": "https://github.com/paywallo/sdk-react-native/issues"
58
+ },
51
59
  "sideEffects": false,
52
60
  "engines": {
53
61
  "node": ">=18.0.0"
@@ -2,12 +2,12 @@ module.exports = {
2
2
  dependency: {
3
3
  platforms: {
4
4
  ios: {
5
- podspecPath: './MonetySdk.podspec',
5
+ podspecPath: './PaywalloSdk.podspec',
6
6
  },
7
7
  android: {
8
8
  sourceDir: './android',
9
- packageImportPath: 'import com.monety.sdk.MonetySdkPackage;',
10
- packageInstance: 'new MonetySdkPackage()',
9
+ packageImportPath: 'import com.paywallo.sdk.PaywalloSdkPackage;',
10
+ packageInstance: 'new PaywalloSdkPackage()',
11
11
  },
12
12
  },
13
13
  },
@@ -1,16 +0,0 @@
1
- package com.monety.sdk
2
-
3
- import com.facebook.react.ReactPackage
4
- import com.facebook.react.bridge.NativeModule
5
- import com.facebook.react.bridge.ReactApplicationContext
6
- import com.facebook.react.uimanager.ViewManager
7
-
8
- class MonetySdkPackage : ReactPackage {
9
- override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
10
- return listOf(PanelStoreKitModule(reactContext))
11
- }
12
-
13
- override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
14
- return listOf(PanelWebViewManager(reactContext))
15
- }
16
- }
@@ -1,315 +0,0 @@
1
- package com.monety.sdk
2
-
3
- import android.app.Activity
4
- import com.android.billingclient.api.*
5
- import com.facebook.react.bridge.*
6
- import kotlinx.coroutines.*
7
-
8
- class PanelStoreKitModule(private val reactContext: ReactApplicationContext) :
9
- ReactContextBaseJavaModule(reactContext), PurchasesUpdatedListener {
10
-
11
- private var billingClient: BillingClient? = null
12
- private var purchasePromise: Promise? = null
13
- private val scope = CoroutineScope(Dispatchers.Main + SupervisorJob())
14
-
15
- override fun getName() = "PanelStoreKit"
16
-
17
- private fun ensureBillingClient(): BillingClient {
18
- if (billingClient == null || billingClient?.isReady != true) {
19
- billingClient = BillingClient.newBuilder(reactContext)
20
- .setListener(this)
21
- .enablePendingPurchases()
22
- .build()
23
- }
24
- return billingClient!!
25
- }
26
-
27
- private suspend fun connectBilling(): Boolean {
28
- val client = ensureBillingClient()
29
- if (client.isReady) return true
30
-
31
- return suspendCancellableCoroutine { cont ->
32
- client.startConnection(object : BillingClientStateListener {
33
- override fun onBillingSetupFinished(result: BillingResult) {
34
- if (cont.isActive) {
35
- cont.resume(result.responseCode == BillingClient.BillingResponseCode.OK) {}
36
- }
37
- }
38
- override fun onBillingServiceDisconnected() {
39
- if (cont.isActive) {
40
- cont.resume(false) {}
41
- }
42
- }
43
- })
44
- }
45
- }
46
-
47
- @ReactMethod
48
- fun getProducts(productIds: ReadableArray, promise: Promise) {
49
- scope.launch {
50
- try {
51
- if (!connectBilling()) {
52
- promise.reject("BILLING_ERROR", "Failed to connect to billing service")
53
- return@launch
54
- }
55
-
56
- val ids = mutableListOf<String>()
57
- for (i in 0 until productIds.size()) {
58
- productIds.getString(i)?.let { ids.add(it) }
59
- }
60
-
61
- // Try subscriptions first, then one-time
62
- val subsProducts = queryProducts(ids, BillingClient.ProductType.SUBS)
63
- val inappProducts = queryProducts(ids, BillingClient.ProductType.INAPP)
64
-
65
- val result = WritableNativeArray()
66
-
67
- for (details in subsProducts + inappProducts) {
68
- val map = WritableNativeMap()
69
- map.putString("productId", details.productId)
70
- map.putString("title", details.title)
71
- map.putString("description", details.description)
72
-
73
- val subDetails = details.subscriptionOfferDetails
74
- if (subDetails != null && subDetails.isNotEmpty()) {
75
- val offer = subDetails[0]
76
- val pricingPhases = offer.pricingPhases.pricingPhaseList
77
-
78
- // Find the main pricing phase (not trial)
79
- val mainPhase = if (pricingPhases.size > 1) {
80
- pricingPhases.last()
81
- } else {
82
- pricingPhases.first()
83
- }
84
-
85
- map.putString("price", (mainPhase.priceAmountMicros / 1_000_000.0).toString())
86
- map.putDouble("priceValue", mainPhase.priceAmountMicros / 1_000_000.0)
87
- map.putString("currency", mainPhase.priceCurrencyCode)
88
- map.putString("localizedPrice", mainPhase.formattedPrice)
89
- map.putString("type", "subscription")
90
- map.putString("subscriptionPeriod", mainPhase.billingPeriod)
91
-
92
- // Check for free trial
93
- if (pricingPhases.size > 1) {
94
- val trialPhase = pricingPhases.first()
95
- if (trialPhase.priceAmountMicros == 0L) {
96
- map.putString("freeTrialPeriod", trialPhase.billingPeriod)
97
- } else {
98
- map.putString("introductoryPrice", trialPhase.formattedPrice)
99
- map.putDouble("introductoryPriceValue", trialPhase.priceAmountMicros / 1_000_000.0)
100
- }
101
- }
102
- } else {
103
- val oneTime = details.oneTimePurchaseOfferDetails
104
- if (oneTime != null) {
105
- map.putString("price", (oneTime.priceAmountMicros / 1_000_000.0).toString())
106
- map.putDouble("priceValue", oneTime.priceAmountMicros / 1_000_000.0)
107
- map.putString("currency", oneTime.priceCurrencyCode)
108
- map.putString("localizedPrice", oneTime.formattedPrice)
109
- map.putString("type", "non_consumable")
110
- }
111
- }
112
-
113
- result.pushMap(map)
114
- }
115
-
116
- promise.resolve(result)
117
- } catch (e: Exception) {
118
- promise.reject("PRODUCTS_ERROR", "Failed to load products: ${e.message}", e)
119
- }
120
- }
121
- }
122
-
123
- private suspend fun queryProducts(ids: List<String>, type: String): List<ProductDetails> {
124
- val client = billingClient ?: return emptyList()
125
-
126
- val productList = ids.map { id ->
127
- QueryProductDetailsParams.Product.newBuilder()
128
- .setProductId(id)
129
- .setProductType(type)
130
- .build()
131
- }
132
-
133
- val params = QueryProductDetailsParams.newBuilder()
134
- .setProductList(productList)
135
- .build()
136
-
137
- return suspendCancellableCoroutine { cont ->
138
- client.queryProductDetailsAsync(params) { result, detailsList ->
139
- if (cont.isActive) {
140
- if (result.responseCode == BillingClient.BillingResponseCode.OK) {
141
- cont.resume(detailsList ?: emptyList()) {}
142
- } else {
143
- cont.resume(emptyList()) {}
144
- }
145
- }
146
- }
147
- }
148
- }
149
-
150
- @ReactMethod
151
- fun purchase(productId: String, promise: Promise) {
152
- scope.launch {
153
- try {
154
- if (!connectBilling()) {
155
- promise.reject("BILLING_ERROR", "Failed to connect to billing service")
156
- return@launch
157
- }
158
-
159
- val client = billingClient!!
160
- val activity = currentActivity
161
-
162
- if (activity == null) {
163
- promise.reject("NO_ACTIVITY", "No current activity")
164
- return@launch
165
- }
166
-
167
- // Query product details
168
- val subsProducts = queryProducts(listOf(productId), BillingClient.ProductType.SUBS)
169
- val inappProducts = queryProducts(listOf(productId), BillingClient.ProductType.INAPP)
170
- val details = (subsProducts + inappProducts).firstOrNull()
171
-
172
- if (details == null) {
173
- promise.reject("PRODUCT_NOT_FOUND", "Product $productId not found")
174
- return@launch
175
- }
176
-
177
- purchasePromise = promise
178
-
179
- val flowParamsBuilder = BillingFlowParams.newBuilder()
180
-
181
- val productDetailsParams = BillingFlowParams.ProductDetailsParams.newBuilder()
182
- .setProductDetails(details)
183
-
184
- // For subscriptions, set the offer token
185
- details.subscriptionOfferDetails?.firstOrNull()?.let { offer ->
186
- productDetailsParams.setOfferToken(offer.offerToken)
187
- }
188
-
189
- flowParamsBuilder.setProductDetailsParamsList(
190
- listOf(productDetailsParams.build())
191
- )
192
-
193
- client.launchBillingFlow(activity, flowParamsBuilder.build())
194
- } catch (e: Exception) {
195
- purchasePromise = null
196
- promise.reject("PURCHASE_ERROR", "Purchase failed: ${e.message}", e)
197
- }
198
- }
199
- }
200
-
201
- override fun onPurchasesUpdated(result: BillingResult, purchases: List<Purchase>?) {
202
- val promise = purchasePromise ?: return
203
- purchasePromise = null
204
-
205
- when (result.responseCode) {
206
- BillingClient.BillingResponseCode.OK -> {
207
- val purchase = purchases?.firstOrNull()
208
- if (purchase != null) {
209
- val map = WritableNativeMap()
210
- map.putString("transactionId", purchase.orderId ?: purchase.purchaseToken)
211
- map.putString("productId", purchase.products.firstOrNull() ?: "")
212
- map.putString("receipt", purchase.purchaseToken)
213
- map.putDouble("transactionDate", purchase.purchaseTime.toDouble())
214
- promise.resolve(map)
215
- } else {
216
- promise.resolve(null)
217
- }
218
- }
219
- BillingClient.BillingResponseCode.USER_CANCELED -> {
220
- promise.resolve(null)
221
- }
222
- else -> {
223
- promise.reject("PURCHASE_ERROR", "Purchase failed: ${result.debugMessage}")
224
- }
225
- }
226
- }
227
-
228
- @ReactMethod
229
- fun finishTransaction(transactionId: String, promise: Promise) {
230
- scope.launch {
231
- try {
232
- if (!connectBilling()) {
233
- promise.resolve(null)
234
- return@launch
235
- }
236
-
237
- val client = billingClient!!
238
-
239
- // Query purchases to find the one to acknowledge
240
- val subsResult = client.queryPurchasesAsync(
241
- QueryPurchasesParams.newBuilder()
242
- .setProductType(BillingClient.ProductType.SUBS)
243
- .build()
244
- )
245
-
246
- val allPurchases = subsResult.purchasesList
247
-
248
- val purchase = allPurchases.firstOrNull {
249
- it.orderId == transactionId || it.purchaseToken == transactionId
250
- }
251
-
252
- if (purchase != null && !purchase.isAcknowledged) {
253
- val ackParams = AcknowledgePurchaseParams.newBuilder()
254
- .setPurchaseToken(purchase.purchaseToken)
255
- .build()
256
-
257
- client.acknowledgePurchase(ackParams) { ackResult ->
258
- if (ackResult.responseCode == BillingClient.BillingResponseCode.OK) {
259
- promise.resolve(null)
260
- } else {
261
- promise.reject("ACK_ERROR", "Failed to acknowledge: ${ackResult.debugMessage}")
262
- }
263
- }
264
- } else {
265
- promise.resolve(null)
266
- }
267
- } catch (e: Exception) {
268
- promise.resolve(null)
269
- }
270
- }
271
- }
272
-
273
- @ReactMethod
274
- fun getActiveTransactions(promise: Promise) {
275
- scope.launch {
276
- try {
277
- if (!connectBilling()) {
278
- promise.resolve(WritableNativeArray())
279
- return@launch
280
- }
281
-
282
- val client = billingClient!!
283
- val result = WritableNativeArray()
284
-
285
- val subsResult = client.queryPurchasesAsync(
286
- QueryPurchasesParams.newBuilder()
287
- .setProductType(BillingClient.ProductType.SUBS)
288
- .build()
289
- )
290
-
291
- for (purchase in subsResult.purchasesList) {
292
- if (purchase.purchaseState == Purchase.PurchaseState.PURCHASED) {
293
- val map = WritableNativeMap()
294
- map.putString("transactionId", purchase.orderId ?: purchase.purchaseToken)
295
- map.putString("productId", purchase.products.firstOrNull() ?: "")
296
- map.putString("receipt", purchase.purchaseToken)
297
- map.putDouble("transactionDate", purchase.purchaseTime.toDouble())
298
- result.pushMap(map)
299
- }
300
- }
301
-
302
- promise.resolve(result)
303
- } catch (e: Exception) {
304
- promise.resolve(WritableNativeArray())
305
- }
306
- }
307
- }
308
-
309
- override fun onCatalystInstanceDestroy() {
310
- scope.cancel()
311
- billingClient?.endConnection()
312
- billingClient = null
313
- super.onCatalystInstanceDestroy()
314
- }
315
- }
@@ -1,74 +0,0 @@
1
- package com.monety.sdk
2
-
3
- import android.annotation.SuppressLint
4
- import android.webkit.JavascriptInterface
5
- import android.webkit.WebChromeClient
6
- import android.webkit.WebSettings
7
- import android.webkit.WebView
8
- import android.webkit.WebViewClient
9
- import com.facebook.react.bridge.Arguments
10
- import com.facebook.react.bridge.ReactContext
11
- import com.facebook.react.uimanager.events.RCTEventEmitter
12
-
13
- @SuppressLint("SetJavaScriptEnabled", "ViewConstructor")
14
- class PanelWebView(context: ReactContext) : WebView(context) {
15
-
16
- private val reactContext = context
17
-
18
- init {
19
- setupWebView()
20
- }
21
-
22
- private fun setupWebView() {
23
- settings.apply {
24
- javaScriptEnabled = true
25
- domStorageEnabled = true
26
- mediaPlaybackRequiresUserGesture = false
27
- mixedContentMode = WebSettings.MIXED_CONTENT_ALWAYS_ALLOW
28
- cacheMode = WebSettings.LOAD_DEFAULT
29
- }
30
-
31
- webViewClient = object : WebViewClient() {
32
- override fun onPageFinished(view: WebView?, url: String?) {
33
- super.onPageFinished(view, url)
34
- dispatchEvent("onLoadEnd", null)
35
- }
36
- }
37
-
38
- webChromeClient = WebChromeClient()
39
-
40
- addJavascriptInterface(WebAppInterface(), "ReactNativeWebView")
41
- }
42
-
43
- private var pendingHtml: String? = null
44
-
45
- fun setSourceUrl(url: String?) {
46
- if (pendingHtml.isNullOrEmpty()) {
47
- url?.let { loadUrl(it) }
48
- }
49
- }
50
-
51
- fun setSourceHtml(html: String?) {
52
- pendingHtml = html
53
- html?.let { loadDataWithBaseURL("https://app.monety.io", it, "text/html", "UTF-8", null) }
54
- }
55
-
56
- fun injectJavaScript(script: String) {
57
- evaluateJavascript(script, null)
58
- }
59
-
60
- private fun dispatchEvent(eventName: String, data: String?) {
61
- val event = Arguments.createMap().apply {
62
- data?.let { putString("data", it) }
63
- }
64
- reactContext.getJSModule(RCTEventEmitter::class.java)
65
- .receiveEvent(id, eventName, event)
66
- }
67
-
68
- inner class WebAppInterface {
69
- @JavascriptInterface
70
- fun postMessage(message: String) {
71
- post { dispatchEvent("onMessage", message) }
72
- }
73
- }
74
- }
@@ -1,52 +0,0 @@
1
- package com.monety.sdk
2
-
3
- import com.facebook.react.bridge.ReactApplicationContext
4
- import com.facebook.react.bridge.ReadableArray
5
- import com.facebook.react.common.MapBuilder
6
- import com.facebook.react.uimanager.SimpleViewManager
7
- import com.facebook.react.uimanager.ThemedReactContext
8
- import com.facebook.react.uimanager.annotations.ReactProp
9
-
10
- class PanelWebViewManager(private val reactContext: ReactApplicationContext) : SimpleViewManager<PanelWebView>() {
11
-
12
- companion object {
13
- const val REACT_CLASS = "PanelWebView"
14
- const val COMMAND_INJECT_JS = 1
15
- }
16
-
17
- override fun getName(): String = REACT_CLASS
18
-
19
- override fun createViewInstance(context: ThemedReactContext): PanelWebView {
20
- return PanelWebView(context)
21
- }
22
-
23
- @ReactProp(name = "sourceUrl")
24
- fun setSourceUrl(view: PanelWebView, url: String?) {
25
- view.setSourceUrl(url)
26
- }
27
-
28
- @ReactProp(name = "sourceHtml")
29
- fun setSourceHtml(view: PanelWebView, html: String?) {
30
- view.setSourceHtml(html)
31
- }
32
-
33
- override fun getExportedCustomDirectEventTypeConstants(): Map<String, Any>? {
34
- return MapBuilder.builder<String, Any>()
35
- .put("onMessage", MapBuilder.of("registrationName", "onMessage"))
36
- .put("onLoadEnd", MapBuilder.of("registrationName", "onLoadEnd"))
37
- .build()
38
- }
39
-
40
- override fun getCommandsMap(): Map<String, Int>? {
41
- return MapBuilder.of("injectJavaScript", COMMAND_INJECT_JS)
42
- }
43
-
44
- override fun receiveCommand(view: PanelWebView, commandId: String?, args: ReadableArray?) {
45
- when (commandId) {
46
- "injectJavaScript" -> {
47
- val script = args?.getString(0) ?: return
48
- view.injectJavaScript(script)
49
- }
50
- }
51
- }
52
- }
@@ -1,21 +0,0 @@
1
- #import <React/RCTBridgeModule.h>
2
-
3
- @interface RCT_EXTERN_MODULE(PanelStoreKit, NSObject)
4
-
5
- RCT_EXTERN_METHOD(getProducts:(NSArray *)productIds
6
- resolve:(RCTPromiseResolveBlock)resolve
7
- reject:(RCTPromiseRejectBlock)reject)
8
-
9
- RCT_EXTERN_METHOD(purchase:(NSString *)productId
10
- appAccountToken:(NSString *)appAccountToken
11
- resolve:(RCTPromiseResolveBlock)resolve
12
- reject:(RCTPromiseRejectBlock)reject)
13
-
14
- RCT_EXTERN_METHOD(finishTransaction:(NSString *)transactionId
15
- resolve:(RCTPromiseResolveBlock)resolve
16
- reject:(RCTPromiseRejectBlock)reject)
17
-
18
- RCT_EXTERN_METHOD(getActiveTransactions:(RCTPromiseResolveBlock)resolve
19
- reject:(RCTPromiseRejectBlock)reject)
20
-
21
- @end