react-native-iap 12.14.1 → 12.15.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/android/gradle.properties +1 -1
- package/android/src/amazon/java/com/dooboolab/rniap/PurchasingServiceProxy.kt +8 -2
- package/android/src/amazon/java/com/dooboolab/rniap/PurchasingServiceProxyAmazonImpl.kt +12 -18
- package/android/src/amazon/java/com/dooboolab/rniap/RNIapAmazonListener.kt +4 -2
- package/android/src/amazon/java/com/dooboolab/rniap/RNIapAmazonModule.kt +45 -29
- package/android/src/amazon/java/com/dooboolab/rniap/RNIapPackage.kt +1 -4
- package/android/src/main/java/com/dooboolab/rniap/PromiseUtils.kt +9 -2
- package/android/src/main/java/com/dooboolab/rniap/PromiseUtlis.kt +15 -6
- package/android/src/play/java/com/dooboolab/rniap/PlayUtils.kt +86 -70
- package/android/src/play/java/com/dooboolab/rniap/RNIapModule.kt +196 -209
- package/android/src/play/java/com/dooboolab/rniap/RNIapPackage.kt +1 -4
- package/android/src/testAmazon/java/com/dooboolab/rniap/RNIapAmazonModuleTest.kt +23 -19
- package/package.json +2 -1
|
@@ -6,7 +6,10 @@ import com.amazon.device.iap.model.FulfillmentResult
|
|
|
6
6
|
import com.amazon.device.iap.model.RequestId
|
|
7
7
|
|
|
8
8
|
interface PurchasingServiceProxy {
|
|
9
|
-
fun registerListener(
|
|
9
|
+
fun registerListener(
|
|
10
|
+
var0: Context?,
|
|
11
|
+
var1: PurchasingListener?,
|
|
12
|
+
)
|
|
10
13
|
|
|
11
14
|
fun getUserData(): RequestId
|
|
12
15
|
|
|
@@ -16,5 +19,8 @@ interface PurchasingServiceProxy {
|
|
|
16
19
|
|
|
17
20
|
fun getPurchaseUpdates(var0: Boolean): RequestId
|
|
18
21
|
|
|
19
|
-
fun notifyFulfillment(
|
|
22
|
+
fun notifyFulfillment(
|
|
23
|
+
var0: String?,
|
|
24
|
+
var1: FulfillmentResult?,
|
|
25
|
+
)
|
|
20
26
|
}
|
|
@@ -7,27 +7,21 @@ import com.amazon.device.iap.model.FulfillmentResult
|
|
|
7
7
|
import com.amazon.device.iap.model.RequestId
|
|
8
8
|
|
|
9
9
|
class PurchasingServiceProxyAmazonImpl : PurchasingServiceProxy {
|
|
10
|
-
override fun registerListener(
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
override fun registerListener(
|
|
11
|
+
var0: Context?,
|
|
12
|
+
var1: PurchasingListener?,
|
|
13
|
+
) = PurchasingService.registerListener(var0, var1)
|
|
13
14
|
|
|
14
|
-
override fun getUserData(): RequestId
|
|
15
|
-
return PurchasingService.getUserData()
|
|
16
|
-
}
|
|
15
|
+
override fun getUserData(): RequestId = PurchasingService.getUserData()
|
|
17
16
|
|
|
18
|
-
override fun purchase(var0: String?): RequestId
|
|
19
|
-
return PurchasingService.purchase(var0)
|
|
20
|
-
}
|
|
17
|
+
override fun purchase(var0: String?): RequestId = PurchasingService.purchase(var0)
|
|
21
18
|
|
|
22
|
-
override fun getProductData(var0: Set<String?>?): RequestId
|
|
23
|
-
return PurchasingService.getProductData(var0)
|
|
24
|
-
}
|
|
19
|
+
override fun getProductData(var0: Set<String?>?): RequestId = PurchasingService.getProductData(var0)
|
|
25
20
|
|
|
26
|
-
override fun getPurchaseUpdates(var0: Boolean): RequestId
|
|
27
|
-
return PurchasingService.getPurchaseUpdates(var0)
|
|
28
|
-
}
|
|
21
|
+
override fun getPurchaseUpdates(var0: Boolean): RequestId = PurchasingService.getPurchaseUpdates(var0)
|
|
29
22
|
|
|
30
|
-
override fun notifyFulfillment(
|
|
31
|
-
|
|
32
|
-
|
|
23
|
+
override fun notifyFulfillment(
|
|
24
|
+
var0: String?,
|
|
25
|
+
var1: FulfillmentResult?,
|
|
26
|
+
) = PurchasingService.notifyFulfillment(var0, var1)
|
|
33
27
|
}
|
|
@@ -22,7 +22,6 @@ class RNIapAmazonListener(
|
|
|
22
22
|
var eventSender: EventSender?,
|
|
23
23
|
var purchasingService: PurchasingServiceProxy?,
|
|
24
24
|
) : PurchasingListener {
|
|
25
|
-
|
|
26
25
|
override fun onProductDataResponse(response: ProductDataResponse) {
|
|
27
26
|
when (response.requestStatus) {
|
|
28
27
|
ProductDataResponse.RequestStatus.SUCCESSFUL -> {
|
|
@@ -170,7 +169,10 @@ class RNIapAmazonListener(
|
|
|
170
169
|
}
|
|
171
170
|
}
|
|
172
171
|
|
|
173
|
-
private fun receiptToMap(
|
|
172
|
+
private fun receiptToMap(
|
|
173
|
+
userData: UserData,
|
|
174
|
+
receipt: Receipt,
|
|
175
|
+
): WritableMap {
|
|
174
176
|
val item = Arguments.createMap()
|
|
175
177
|
item.putString("productId", receipt.sku)
|
|
176
178
|
item.putDouble("transactionDate", receipt.purchaseDate.time.toDouble())
|
|
@@ -22,28 +22,33 @@ class RNIapAmazonModule(
|
|
|
22
22
|
private val reactContext: ReactApplicationContext,
|
|
23
23
|
private val purchasingService: PurchasingServiceProxy = PurchasingServiceProxyAmazonImpl(),
|
|
24
24
|
private var eventSender: EventSender? = null,
|
|
25
|
-
) :
|
|
26
|
-
|
|
27
|
-
override fun getName(): String {
|
|
28
|
-
return TAG
|
|
29
|
-
}
|
|
25
|
+
) : ReactContextBaseJavaModule(reactContext) {
|
|
26
|
+
override fun getName(): String = TAG
|
|
30
27
|
|
|
31
28
|
@ReactMethod
|
|
32
29
|
fun initConnection(promise: Promise) {
|
|
33
30
|
if (RNIapActivityListener.amazonListener == null) {
|
|
34
|
-
promise.safeReject(
|
|
31
|
+
promise.safeReject(
|
|
32
|
+
PromiseUtils.E_DEVELOPER_ERROR,
|
|
33
|
+
Exception("RNIapActivityListener is not registered in your MainActivity.onCreate"),
|
|
34
|
+
)
|
|
35
35
|
return
|
|
36
36
|
}
|
|
37
37
|
if (eventSender == null) {
|
|
38
|
-
eventSender =
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
38
|
+
eventSender =
|
|
39
|
+
object : EventSender {
|
|
40
|
+
private val rctDeviceEventEmitter =
|
|
41
|
+
reactContext
|
|
42
|
+
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
|
|
43
|
+
|
|
44
|
+
override fun sendEvent(
|
|
45
|
+
eventName: String,
|
|
46
|
+
params: WritableMap?,
|
|
47
|
+
) {
|
|
48
|
+
rctDeviceEventEmitter
|
|
49
|
+
.emit(eventName, params)
|
|
50
|
+
}
|
|
45
51
|
}
|
|
46
|
-
}
|
|
47
52
|
}
|
|
48
53
|
RNIapActivityListener.amazonListener?.eventSender = eventSender
|
|
49
54
|
RNIapActivityListener.amazonListener?.purchasingService = purchasingService
|
|
@@ -109,7 +114,11 @@ class RNIapAmazonModule(
|
|
|
109
114
|
}
|
|
110
115
|
|
|
111
116
|
@ReactMethod
|
|
112
|
-
fun getItemsByType(
|
|
117
|
+
fun getItemsByType(
|
|
118
|
+
type: String?,
|
|
119
|
+
skuArr: ReadableArray,
|
|
120
|
+
promise: Promise,
|
|
121
|
+
) {
|
|
113
122
|
val productSkus: MutableSet<String> = HashSet()
|
|
114
123
|
var ii = 0
|
|
115
124
|
val skuSize = skuArr.size()
|
|
@@ -171,7 +180,10 @@ class RNIapAmazonModule(
|
|
|
171
180
|
* From https://amazon.developer.forums.answerhub.com/questions/175720/how-to-open-store-subscription-screen-directly-use.html?childToView=179402#answer-179402
|
|
172
181
|
*/
|
|
173
182
|
@ReactMethod
|
|
174
|
-
fun deepLinkToSubscriptions(
|
|
183
|
+
fun deepLinkToSubscriptions(
|
|
184
|
+
isAmazonDevice: Boolean,
|
|
185
|
+
promise: Promise,
|
|
186
|
+
) {
|
|
175
187
|
if (isAmazonDevice) {
|
|
176
188
|
val intent =
|
|
177
189
|
Intent("android.intent.action.VIEW", Uri.parse("amzn://apps/library/subscriptions"))
|
|
@@ -210,22 +222,26 @@ class RNIapAmazonModule(
|
|
|
210
222
|
|
|
211
223
|
const val TAG = "RNIapAmazonModule"
|
|
212
224
|
}
|
|
225
|
+
|
|
213
226
|
init {
|
|
214
|
-
val lifecycleEventListener: LifecycleEventListener =
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
227
|
+
val lifecycleEventListener: LifecycleEventListener =
|
|
228
|
+
object : LifecycleEventListener {
|
|
229
|
+
/**
|
|
230
|
+
* From https://developer.amazon.com/docs/in-app-purchasing/iap-implement-iap.html#getpurchaseupdates-responses
|
|
231
|
+
* We should fetch updates on resume
|
|
232
|
+
*/
|
|
233
|
+
override fun onHostResume() {
|
|
234
|
+
if (RNIapActivityListener.hasListener) {
|
|
235
|
+
purchasingService.getUserData()
|
|
236
|
+
purchasingService.getPurchaseUpdates(false)
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
override fun onHostPause() {}
|
|
241
|
+
|
|
242
|
+
override fun onHostDestroy() {
|
|
223
243
|
}
|
|
224
244
|
}
|
|
225
|
-
override fun onHostPause() {}
|
|
226
|
-
override fun onHostDestroy() {
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
245
|
reactContext.addLifecycleEventListener(lifecycleEventListener)
|
|
230
246
|
}
|
|
231
247
|
}
|
|
@@ -7,10 +7,7 @@ import com.facebook.react.uimanager.ViewManager
|
|
|
7
7
|
import java.util.ArrayList
|
|
8
8
|
|
|
9
9
|
class RNIapPackage : ReactPackage {
|
|
10
|
-
|
|
11
|
-
override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
|
|
12
|
-
return emptyList()
|
|
13
|
-
}
|
|
10
|
+
override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> = emptyList()
|
|
14
11
|
|
|
15
12
|
override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
|
|
16
13
|
val modules: MutableList<NativeModule> = ArrayList()
|
|
@@ -6,11 +6,18 @@ import java.util.HashMap
|
|
|
6
6
|
|
|
7
7
|
object PromiseUtils {
|
|
8
8
|
private val promises = HashMap<String, MutableList<Promise>>()
|
|
9
|
-
|
|
9
|
+
|
|
10
|
+
fun addPromiseForKey(
|
|
11
|
+
key: String,
|
|
12
|
+
promise: Promise,
|
|
13
|
+
) {
|
|
10
14
|
promises.getOrPut(key) { mutableListOf() }.add(promise)
|
|
11
15
|
}
|
|
12
16
|
|
|
13
|
-
fun resolvePromisesForKey(
|
|
17
|
+
fun resolvePromisesForKey(
|
|
18
|
+
key: String,
|
|
19
|
+
value: Any?,
|
|
20
|
+
) {
|
|
14
21
|
promises[key]?.forEach { promise ->
|
|
15
22
|
promise.safeResolve(value)
|
|
16
23
|
}
|
|
@@ -21,12 +21,21 @@ fun Promise.safeResolve(value: Any?) {
|
|
|
21
21
|
|
|
22
22
|
fun Promise.safeReject(message: String) = this.safeReject(message, null, null)
|
|
23
23
|
|
|
24
|
-
fun Promise.safeReject(
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
fun Promise.safeReject(
|
|
24
|
+
fun Promise.safeReject(
|
|
25
|
+
code: String?,
|
|
26
|
+
message: String?,
|
|
27
|
+
) = this.safeReject(code, message, null)
|
|
28
|
+
|
|
29
|
+
fun Promise.safeReject(
|
|
30
|
+
code: String?,
|
|
31
|
+
throwable: Throwable?,
|
|
32
|
+
) = this.safeReject(code, null, throwable)
|
|
33
|
+
|
|
34
|
+
fun Promise.safeReject(
|
|
35
|
+
code: String?,
|
|
36
|
+
message: String?,
|
|
37
|
+
throwable: Throwable?,
|
|
38
|
+
) {
|
|
30
39
|
try {
|
|
31
40
|
this.reject(code, message, throwable)
|
|
32
41
|
} catch (oce: ObjectAlreadyConsumedException) {
|
|
@@ -4,88 +4,104 @@ import android.util.Log
|
|
|
4
4
|
import com.android.billingclient.api.BillingClient
|
|
5
5
|
import com.facebook.react.bridge.Promise
|
|
6
6
|
|
|
7
|
-
data class BillingResponse(
|
|
7
|
+
data class BillingResponse(
|
|
8
|
+
val code: String,
|
|
9
|
+
val message: String,
|
|
10
|
+
)
|
|
8
11
|
|
|
9
12
|
object PlayUtils {
|
|
10
|
-
fun rejectPromiseWithBillingError(
|
|
13
|
+
fun rejectPromiseWithBillingError(
|
|
14
|
+
promise: Promise,
|
|
15
|
+
responseCode: Int,
|
|
16
|
+
) {
|
|
11
17
|
val errorData = getBillingResponseData(responseCode)
|
|
12
18
|
promise.safeReject(errorData.code, errorData.message)
|
|
13
19
|
}
|
|
14
20
|
|
|
15
21
|
fun getBillingResponseData(responseCode: Int): BillingResponse {
|
|
16
|
-
val errorData =
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
+
val errorData =
|
|
23
|
+
when (responseCode) {
|
|
24
|
+
BillingClient.BillingResponseCode.FEATURE_NOT_SUPPORTED -> {
|
|
25
|
+
BillingResponse(
|
|
26
|
+
PromiseUtils.E_SERVICE_ERROR,
|
|
27
|
+
"This feature is not available on your device.",
|
|
28
|
+
)
|
|
29
|
+
}
|
|
30
|
+
BillingClient.BillingResponseCode.SERVICE_DISCONNECTED -> {
|
|
31
|
+
BillingResponse(
|
|
32
|
+
PromiseUtils.E_NETWORK_ERROR,
|
|
33
|
+
"The service is disconnected (check your internet connection.)",
|
|
34
|
+
)
|
|
35
|
+
}
|
|
36
|
+
BillingClient.BillingResponseCode.NETWORK_ERROR -> {
|
|
37
|
+
BillingResponse(
|
|
38
|
+
PromiseUtils.E_NETWORK_ERROR,
|
|
39
|
+
"You have problem with network connection.",
|
|
40
|
+
)
|
|
41
|
+
}
|
|
42
|
+
BillingClient.BillingResponseCode.OK -> {
|
|
43
|
+
BillingResponse(
|
|
44
|
+
"OK",
|
|
45
|
+
"",
|
|
46
|
+
)
|
|
47
|
+
}
|
|
48
|
+
BillingClient.BillingResponseCode.USER_CANCELED -> {
|
|
49
|
+
BillingResponse(
|
|
50
|
+
PromiseUtils.E_USER_CANCELLED,
|
|
51
|
+
"Payment is Cancelled.",
|
|
52
|
+
)
|
|
53
|
+
}
|
|
54
|
+
BillingClient.BillingResponseCode.SERVICE_UNAVAILABLE -> {
|
|
55
|
+
BillingResponse(
|
|
56
|
+
PromiseUtils.E_SERVICE_ERROR,
|
|
57
|
+
"The service is unreachable. This may be your internet connection, or the Play Store may be down.",
|
|
58
|
+
)
|
|
59
|
+
}
|
|
60
|
+
BillingClient.BillingResponseCode.BILLING_UNAVAILABLE -> {
|
|
61
|
+
BillingResponse(
|
|
62
|
+
PromiseUtils.E_SERVICE_ERROR,
|
|
63
|
+
"Billing is unavailable. This may be a problem with your device, or the Play Store may be down.",
|
|
64
|
+
)
|
|
65
|
+
}
|
|
66
|
+
BillingClient.BillingResponseCode.ITEM_UNAVAILABLE -> {
|
|
67
|
+
BillingResponse(
|
|
68
|
+
PromiseUtils.E_ITEM_UNAVAILABLE,
|
|
69
|
+
"That item is unavailable.",
|
|
70
|
+
)
|
|
71
|
+
}
|
|
72
|
+
BillingClient.BillingResponseCode.DEVELOPER_ERROR -> {
|
|
73
|
+
BillingResponse(
|
|
74
|
+
PromiseUtils.E_DEVELOPER_ERROR,
|
|
75
|
+
"Google is indicating that we have some issue connecting to payment.",
|
|
76
|
+
)
|
|
77
|
+
}
|
|
78
|
+
BillingClient.BillingResponseCode.ERROR -> {
|
|
79
|
+
BillingResponse(
|
|
80
|
+
PromiseUtils.E_UNKNOWN,
|
|
81
|
+
"An unknown or unexpected error has occurred. Please try again later.",
|
|
82
|
+
)
|
|
83
|
+
}
|
|
84
|
+
BillingClient.BillingResponseCode.ITEM_ALREADY_OWNED -> {
|
|
85
|
+
BillingResponse(
|
|
86
|
+
PromiseUtils.E_ALREADY_OWNED,
|
|
87
|
+
"You already own this item.",
|
|
88
|
+
)
|
|
89
|
+
}
|
|
90
|
+
else -> {
|
|
91
|
+
BillingResponse(
|
|
92
|
+
PromiseUtils.E_UNKNOWN,
|
|
93
|
+
"Purchase failed with code: $responseCode",
|
|
94
|
+
)
|
|
95
|
+
}
|
|
22
96
|
}
|
|
23
|
-
BillingClient.BillingResponseCode.SERVICE_DISCONNECTED -> {
|
|
24
|
-
BillingResponse(
|
|
25
|
-
PromiseUtils.E_NETWORK_ERROR,
|
|
26
|
-
"The service is disconnected (check your internet connection.)",
|
|
27
|
-
)
|
|
28
|
-
}
|
|
29
|
-
BillingClient.BillingResponseCode.OK -> {
|
|
30
|
-
BillingResponse(
|
|
31
|
-
"OK",
|
|
32
|
-
"",
|
|
33
|
-
)
|
|
34
|
-
}
|
|
35
|
-
BillingClient.BillingResponseCode.USER_CANCELED -> {
|
|
36
|
-
BillingResponse(
|
|
37
|
-
PromiseUtils.E_USER_CANCELLED,
|
|
38
|
-
"Payment is Cancelled.",
|
|
39
|
-
)
|
|
40
|
-
}
|
|
41
|
-
BillingClient.BillingResponseCode.SERVICE_UNAVAILABLE -> {
|
|
42
|
-
BillingResponse(
|
|
43
|
-
PromiseUtils.E_SERVICE_ERROR,
|
|
44
|
-
"The service is unreachable. This may be your internet connection, or the Play Store may be down.",
|
|
45
|
-
)
|
|
46
|
-
}
|
|
47
|
-
BillingClient.BillingResponseCode.BILLING_UNAVAILABLE -> {
|
|
48
|
-
BillingResponse(
|
|
49
|
-
PromiseUtils.E_SERVICE_ERROR,
|
|
50
|
-
"Billing is unavailable. This may be a problem with your device, or the Play Store may be down.",
|
|
51
|
-
)
|
|
52
|
-
}
|
|
53
|
-
BillingClient.BillingResponseCode.ITEM_UNAVAILABLE -> {
|
|
54
|
-
BillingResponse(
|
|
55
|
-
PromiseUtils.E_ITEM_UNAVAILABLE,
|
|
56
|
-
"That item is unavailable.",
|
|
57
|
-
)
|
|
58
|
-
}
|
|
59
|
-
BillingClient.BillingResponseCode.DEVELOPER_ERROR -> {
|
|
60
|
-
BillingResponse(
|
|
61
|
-
PromiseUtils.E_DEVELOPER_ERROR,
|
|
62
|
-
"Google is indicating that we have some issue connecting to payment.",
|
|
63
|
-
)
|
|
64
|
-
}
|
|
65
|
-
BillingClient.BillingResponseCode.ERROR -> {
|
|
66
|
-
BillingResponse(
|
|
67
|
-
PromiseUtils.E_UNKNOWN,
|
|
68
|
-
"An unknown or unexpected error has occurred. Please try again later.",
|
|
69
|
-
)
|
|
70
|
-
}
|
|
71
|
-
BillingClient.BillingResponseCode.ITEM_ALREADY_OWNED -> {
|
|
72
|
-
BillingResponse(
|
|
73
|
-
PromiseUtils.E_ALREADY_OWNED,
|
|
74
|
-
"You already own this item.",
|
|
75
|
-
)
|
|
76
|
-
}
|
|
77
|
-
else -> {
|
|
78
|
-
BillingResponse(
|
|
79
|
-
PromiseUtils.E_UNKNOWN,
|
|
80
|
-
"Purchase failed with code: $responseCode",
|
|
81
|
-
)
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
97
|
Log.e(TAG, "Error Code : $responseCode")
|
|
85
98
|
return errorData
|
|
86
99
|
}
|
|
87
100
|
|
|
88
|
-
fun rejectPromisesWithBillingError(
|
|
101
|
+
fun rejectPromisesWithBillingError(
|
|
102
|
+
key: String,
|
|
103
|
+
responseCode: Int,
|
|
104
|
+
) {
|
|
89
105
|
val errorData = getBillingResponseData(responseCode)
|
|
90
106
|
PromiseUtils.rejectPromisesForKey(key, errorData.code, errorData.message, null)
|
|
91
107
|
}
|
|
@@ -34,21 +34,18 @@ import com.facebook.react.module.annotations.ReactModule
|
|
|
34
34
|
import com.facebook.react.modules.core.DeviceEventManagerModule.RCTDeviceEventEmitter
|
|
35
35
|
import com.google.android.gms.common.ConnectionResult
|
|
36
36
|
import com.google.android.gms.common.GoogleApiAvailability
|
|
37
|
-
|
|
37
|
+
|
|
38
38
|
@ReactModule(name = RNIapModule.TAG)
|
|
39
39
|
class RNIapModule(
|
|
40
40
|
private val reactContext: ReactApplicationContext,
|
|
41
41
|
private val builder: BillingClient.Builder = BillingClient.newBuilder(reactContext).enablePendingPurchases(),
|
|
42
42
|
private val googleApiAvailability: GoogleApiAvailability = GoogleApiAvailability.getInstance(),
|
|
43
|
-
) :
|
|
44
|
-
ReactContextBaseJavaModule(reactContext),
|
|
43
|
+
) : ReactContextBaseJavaModule(reactContext),
|
|
45
44
|
PurchasesUpdatedListener {
|
|
46
|
-
|
|
47
45
|
private var billingClientCache: BillingClient? = null
|
|
48
46
|
private val skus: MutableMap<String, ProductDetails> = mutableMapOf()
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
}
|
|
47
|
+
|
|
48
|
+
override fun getName(): String = TAG
|
|
52
49
|
|
|
53
50
|
fun ensureConnection(
|
|
54
51
|
promise: Promise,
|
|
@@ -59,68 +56,73 @@ class RNIapModule(
|
|
|
59
56
|
callback(billingClient)
|
|
60
57
|
return
|
|
61
58
|
} else {
|
|
62
|
-
val nested =
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
59
|
+
val nested =
|
|
60
|
+
PromiseImpl(
|
|
61
|
+
{
|
|
62
|
+
if (it.isNotEmpty() && it[0] is Boolean && it[0] as Boolean) {
|
|
63
|
+
val connectedBillingClient = billingClientCache
|
|
64
|
+
if (connectedBillingClient?.isReady == true) {
|
|
65
|
+
callback(connectedBillingClient)
|
|
66
|
+
} else {
|
|
67
|
+
promise.safeReject(PromiseUtils.E_NOT_PREPARED, "Unable to auto-initialize connection")
|
|
68
|
+
}
|
|
68
69
|
} else {
|
|
69
|
-
promise.safeReject(PromiseUtils.
|
|
70
|
+
promise.safeReject(PromiseUtils.E_UNKNOWN, "ensureConnection - incorrect parameter in resolve")
|
|
71
|
+
Log.i(TAG, "Incorrect parameter in resolve")
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
var errorCode: String? = null
|
|
76
|
+
var errorMessage: String? = null
|
|
77
|
+
if (it.size > 1 && it[0] is String && it[1] is String) {
|
|
78
|
+
errorCode = it[0] as String
|
|
79
|
+
errorMessage = it[1] as String
|
|
80
|
+
} else if (it.isNotEmpty() && it[0] is WritableNativeMap) {
|
|
81
|
+
val errorMap = it[0] as WritableNativeMap
|
|
82
|
+
errorCode = errorMap.getString("code")
|
|
83
|
+
errorMessage = errorMap.getString("message")
|
|
70
84
|
}
|
|
71
|
-
} else {
|
|
72
|
-
promise.safeReject(PromiseUtils.E_UNKNOWN, "ensureConnection - incorrect parameter in resolve")
|
|
73
|
-
Log.i(TAG, "Incorrect parameter in resolve")
|
|
74
|
-
}
|
|
75
|
-
},
|
|
76
|
-
{
|
|
77
|
-
var errorCode: String? = null
|
|
78
|
-
var errorMessage: String? = null
|
|
79
|
-
if (it.size > 1 && it[0] is String && it[1] is String) {
|
|
80
|
-
errorCode = it[0] as String
|
|
81
|
-
errorMessage = it[1] as String
|
|
82
|
-
} else if (it.isNotEmpty() && it[0] is WritableNativeMap) {
|
|
83
|
-
val errorMap = it[0] as WritableNativeMap
|
|
84
|
-
errorCode = errorMap.getString("code")
|
|
85
|
-
errorMessage = errorMap.getString("message")
|
|
86
|
-
}
|
|
87
85
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
86
|
+
if (errorCode is String && errorMessage is String) {
|
|
87
|
+
promise.safeReject(
|
|
88
|
+
errorCode,
|
|
89
|
+
errorMessage,
|
|
90
|
+
)
|
|
91
|
+
} else {
|
|
92
|
+
promise.safeReject(PromiseUtils.E_UNKNOWN, "ensureConnection - incorrect parameter in reject")
|
|
93
|
+
Log.i(TAG, "Incorrect parameters in reject")
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
)
|
|
99
97
|
initConnection(nested)
|
|
100
98
|
}
|
|
101
99
|
}
|
|
102
100
|
|
|
103
101
|
@ReactMethod
|
|
104
|
-
fun isFeatureSupported(
|
|
102
|
+
fun isFeatureSupported(
|
|
103
|
+
feature: String,
|
|
104
|
+
promise: Promise,
|
|
105
|
+
) {
|
|
105
106
|
ensureConnection(
|
|
106
107
|
promise,
|
|
107
108
|
) { billingClient ->
|
|
108
|
-
val f =
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
109
|
+
val f =
|
|
110
|
+
when (feature) {
|
|
111
|
+
"IN_APP_MESSAGING" ->
|
|
112
|
+
BillingClient.FeatureType.IN_APP_MESSAGING
|
|
113
|
+
"PRICE_CHANGE_CONFIRMATION" ->
|
|
114
|
+
BillingClient.FeatureType.PRICE_CHANGE_CONFIRMATION
|
|
115
|
+
"PRODUCT_DETAILS" ->
|
|
116
|
+
BillingClient.FeatureType.PRODUCT_DETAILS
|
|
117
|
+
"SUBSCRIPTIONS" ->
|
|
118
|
+
BillingClient.FeatureType.SUBSCRIPTIONS
|
|
119
|
+
"SUBSCRIPTIONS_UPDATE" ->
|
|
120
|
+
BillingClient.FeatureType.SUBSCRIPTIONS_UPDATE
|
|
121
|
+
else -> {
|
|
122
|
+
promise.safeReject("Invalid Feature name")
|
|
123
|
+
return@ensureConnection
|
|
124
|
+
}
|
|
122
125
|
}
|
|
123
|
-
}
|
|
124
126
|
promise.safeResolve(billingClient.isFeatureSupported(f))
|
|
125
127
|
}
|
|
126
128
|
}
|
|
@@ -180,7 +182,9 @@ class RNIapModule(
|
|
|
180
182
|
promise,
|
|
181
183
|
) { billingClient ->
|
|
182
184
|
val consumeParams =
|
|
183
|
-
ConsumeParams
|
|
185
|
+
ConsumeParams
|
|
186
|
+
.newBuilder()
|
|
187
|
+
.setPurchaseToken(purchase.purchaseToken)
|
|
184
188
|
.build()
|
|
185
189
|
val listener =
|
|
186
190
|
ConsumeResponseListener { billingResult: BillingResult, outToken: String? ->
|
|
@@ -205,9 +209,11 @@ class RNIapModule(
|
|
|
205
209
|
promise,
|
|
206
210
|
) { billingClient ->
|
|
207
211
|
billingClient.queryPurchasesAsync(
|
|
208
|
-
QueryPurchasesParams
|
|
209
|
-
|
|
210
|
-
|
|
212
|
+
QueryPurchasesParams
|
|
213
|
+
.newBuilder()
|
|
214
|
+
.setProductType(
|
|
215
|
+
BillingClient.ProductType.INAPP,
|
|
216
|
+
).build(),
|
|
211
217
|
) { billingResult: BillingResult, list: List<Purchase>? ->
|
|
212
218
|
if (!isValidResult(billingResult, promise)) return@queryPurchasesAsync
|
|
213
219
|
if (list == null) {
|
|
@@ -233,25 +239,39 @@ class RNIapModule(
|
|
|
233
239
|
}
|
|
234
240
|
|
|
235
241
|
@ReactMethod
|
|
236
|
-
fun getItemsByType(
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
242
|
+
fun getItemsByType(
|
|
243
|
+
type: String,
|
|
244
|
+
skuArr: ReadableArray,
|
|
245
|
+
promise: Promise,
|
|
246
|
+
) {
|
|
247
|
+
ensureConnection(promise) { billingClient ->
|
|
248
|
+
val skuList = mutableListOf<QueryProductDetailsParams.Product>()
|
|
241
249
|
for (i in 0 until skuArr.size()) {
|
|
242
250
|
if (skuArr.getType(i) == ReadableType.String) {
|
|
243
|
-
skuArr.getString(i)?.let { sku ->
|
|
251
|
+
skuArr.getString(i)?.let { sku ->
|
|
244
252
|
skuList.add(
|
|
245
|
-
QueryProductDetailsParams.Product
|
|
246
|
-
.
|
|
253
|
+
QueryProductDetailsParams.Product
|
|
254
|
+
.newBuilder()
|
|
255
|
+
.setProductId(sku)
|
|
256
|
+
.setProductType(type)
|
|
257
|
+
.build(),
|
|
247
258
|
)
|
|
248
259
|
}
|
|
249
260
|
}
|
|
250
261
|
}
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
262
|
+
|
|
263
|
+
if (skuList.isEmpty()) {
|
|
264
|
+
promise.safeReject("EMPTY_SKU_LIST", "The SKU list is empty.")
|
|
265
|
+
return@ensureConnection
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
val params =
|
|
269
|
+
QueryProductDetailsParams
|
|
270
|
+
.newBuilder()
|
|
271
|
+
.setProductList(skuList)
|
|
272
|
+
.build()
|
|
273
|
+
|
|
274
|
+
billingClient.queryProductDetailsAsync(params) { billingResult, skuDetailsList ->
|
|
255
275
|
if (!isValidResult(billingResult, promise)) return@queryProductDetailsAsync
|
|
256
276
|
|
|
257
277
|
val items = Arguments.createArray()
|
|
@@ -264,68 +284,51 @@ class RNIapModule(
|
|
|
264
284
|
item.putString("description", skuDetails.description)
|
|
265
285
|
item.putString("productType", skuDetails.productType)
|
|
266
286
|
item.putString("name", skuDetails.name)
|
|
267
|
-
|
|
287
|
+
|
|
268
288
|
skuDetails.oneTimePurchaseOfferDetails?.let {
|
|
269
|
-
oneTimePurchaseOfferDetails
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
"priceAmountMicros",
|
|
276
|
-
it.priceAmountMicros.toString(),
|
|
277
|
-
)
|
|
289
|
+
val oneTimePurchaseOfferDetails =
|
|
290
|
+
Arguments.createMap().apply {
|
|
291
|
+
putString("priceCurrencyCode", it.priceCurrencyCode)
|
|
292
|
+
putString("formattedPrice", it.formattedPrice)
|
|
293
|
+
putString("priceAmountMicros", it.priceAmountMicros.toString())
|
|
294
|
+
}
|
|
278
295
|
item.putMap("oneTimePurchaseOfferDetails", oneTimePurchaseOfferDetails)
|
|
279
296
|
}
|
|
297
|
+
|
|
280
298
|
skuDetails.subscriptionOfferDetails?.let {
|
|
281
299
|
val subscriptionOfferDetails = Arguments.createArray()
|
|
282
300
|
it.forEach { subscriptionOfferDetailsItem ->
|
|
283
|
-
val offerDetails =
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
pricingPhase.putInt(
|
|
315
|
-
"billingCycleCount",
|
|
316
|
-
pricingPhaseItem.billingCycleCount,
|
|
317
|
-
)
|
|
318
|
-
pricingPhase.putString(
|
|
319
|
-
"priceAmountMicros",
|
|
320
|
-
pricingPhaseItem.priceAmountMicros.toString(),
|
|
321
|
-
)
|
|
322
|
-
pricingPhase.putInt("recurrenceMode", pricingPhaseItem.recurrenceMode)
|
|
323
|
-
|
|
324
|
-
pricingPhasesList.pushMap(pricingPhase)
|
|
325
|
-
}
|
|
326
|
-
val pricingPhases = Arguments.createMap()
|
|
327
|
-
pricingPhases.putArray("pricingPhaseList", pricingPhasesList)
|
|
328
|
-
offerDetails.putMap("pricingPhases", pricingPhases)
|
|
301
|
+
val offerDetails =
|
|
302
|
+
Arguments.createMap().apply {
|
|
303
|
+
putString("basePlanId", subscriptionOfferDetailsItem.basePlanId)
|
|
304
|
+
putString("offerId", subscriptionOfferDetailsItem.offerId)
|
|
305
|
+
putString("offerToken", subscriptionOfferDetailsItem.offerToken)
|
|
306
|
+
|
|
307
|
+
val offerTags = Arguments.createArray()
|
|
308
|
+
subscriptionOfferDetailsItem.offerTags.forEach { offerTag ->
|
|
309
|
+
offerTags.pushString(offerTag)
|
|
310
|
+
}
|
|
311
|
+
putArray("offerTags", offerTags)
|
|
312
|
+
|
|
313
|
+
val pricingPhasesList = Arguments.createArray()
|
|
314
|
+
subscriptionOfferDetailsItem.pricingPhases.pricingPhaseList.forEach { pricingPhaseItem ->
|
|
315
|
+
val pricingPhase =
|
|
316
|
+
Arguments.createMap().apply {
|
|
317
|
+
putString("formattedPrice", pricingPhaseItem.formattedPrice)
|
|
318
|
+
putString("priceCurrencyCode", pricingPhaseItem.priceCurrencyCode)
|
|
319
|
+
putString("billingPeriod", pricingPhaseItem.billingPeriod)
|
|
320
|
+
putInt("billingCycleCount", pricingPhaseItem.billingCycleCount)
|
|
321
|
+
putString("priceAmountMicros", pricingPhaseItem.priceAmountMicros.toString())
|
|
322
|
+
putInt("recurrenceMode", pricingPhaseItem.recurrenceMode)
|
|
323
|
+
}
|
|
324
|
+
pricingPhasesList.pushMap(pricingPhase)
|
|
325
|
+
}
|
|
326
|
+
val pricingPhases =
|
|
327
|
+
Arguments.createMap().apply {
|
|
328
|
+
putArray("pricingPhaseList", pricingPhasesList)
|
|
329
|
+
}
|
|
330
|
+
putMap("pricingPhases", pricingPhases)
|
|
331
|
+
}
|
|
329
332
|
subscriptionOfferDetails.pushMap(offerDetails)
|
|
330
333
|
}
|
|
331
334
|
item.putArray("subscriptionOfferDetails", subscriptionOfferDetails)
|
|
@@ -353,15 +356,20 @@ class RNIapModule(
|
|
|
353
356
|
}
|
|
354
357
|
|
|
355
358
|
@ReactMethod
|
|
356
|
-
fun getAvailableItemsByType(
|
|
359
|
+
fun getAvailableItemsByType(
|
|
360
|
+
type: String,
|
|
361
|
+
promise: Promise,
|
|
362
|
+
) {
|
|
357
363
|
ensureConnection(
|
|
358
364
|
promise,
|
|
359
365
|
) { billingClient ->
|
|
360
366
|
val items = WritableNativeArray()
|
|
361
367
|
billingClient.queryPurchasesAsync(
|
|
362
|
-
QueryPurchasesParams
|
|
363
|
-
|
|
364
|
-
|
|
368
|
+
QueryPurchasesParams
|
|
369
|
+
.newBuilder()
|
|
370
|
+
.setProductType(
|
|
371
|
+
if (type == "subs") BillingClient.ProductType.SUBS else BillingClient.ProductType.INAPP,
|
|
372
|
+
).build(),
|
|
365
373
|
) { billingResult: BillingResult, purchases: List<Purchase>? ->
|
|
366
374
|
if (!isValidResult(billingResult, promise)) return@queryPurchasesAsync
|
|
367
375
|
purchases?.forEach { purchase ->
|
|
@@ -399,16 +407,20 @@ class RNIapModule(
|
|
|
399
407
|
}
|
|
400
408
|
|
|
401
409
|
@ReactMethod
|
|
402
|
-
fun getPurchaseHistoryByType(
|
|
410
|
+
fun getPurchaseHistoryByType(
|
|
411
|
+
type: String,
|
|
412
|
+
promise: Promise,
|
|
413
|
+
) {
|
|
403
414
|
ensureConnection(
|
|
404
415
|
promise,
|
|
405
416
|
) { billingClient ->
|
|
406
417
|
billingClient.queryPurchaseHistoryAsync(
|
|
407
|
-
QueryPurchaseHistoryParams
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
418
|
+
QueryPurchaseHistoryParams
|
|
419
|
+
.newBuilder()
|
|
420
|
+
.setProductType(
|
|
421
|
+
if (type == "subs") BillingClient.ProductType.SUBS else BillingClient.ProductType.INAPP,
|
|
422
|
+
).build(),
|
|
423
|
+
) { billingResult: BillingResult, purchaseHistoryRecordList: MutableList<PurchaseHistoryRecord>? ->
|
|
412
424
|
|
|
413
425
|
if (!isValidResult(billingResult, promise)) return@queryPurchaseHistoryAsync
|
|
414
426
|
|
|
@@ -438,7 +450,7 @@ class RNIapModule(
|
|
|
438
450
|
type: String,
|
|
439
451
|
skuArr: ReadableArray,
|
|
440
452
|
purchaseToken: String?,
|
|
441
|
-
|
|
453
|
+
replacementMode: Int,
|
|
442
454
|
obfuscatedAccountId: String?,
|
|
443
455
|
obfuscatedProfileId: String?,
|
|
444
456
|
offerTokenArr: ReadableArray, // New parameter in V5
|
|
@@ -485,7 +497,8 @@ class RNIapModule(
|
|
|
485
497
|
}
|
|
486
498
|
var productDetailParams = BillingFlowParams.ProductDetailsParams.newBuilder().setProductDetails(selectedSku)
|
|
487
499
|
if (type == BillingClient.ProductType.SUBS) {
|
|
488
|
-
offerTokenArr.getString(index)?.let { offerToken ->
|
|
500
|
+
offerTokenArr.getString(index)?.let { offerToken ->
|
|
501
|
+
// null check for older versions of RN
|
|
489
502
|
productDetailParams = productDetailParams.setOfferToken(offerToken)
|
|
490
503
|
}
|
|
491
504
|
}
|
|
@@ -497,6 +510,23 @@ class RNIapModule(
|
|
|
497
510
|
val subscriptionUpdateParamsBuilder = SubscriptionUpdateParams.newBuilder()
|
|
498
511
|
if (purchaseToken != null) {
|
|
499
512
|
subscriptionUpdateParamsBuilder.setOldPurchaseToken(purchaseToken)
|
|
513
|
+
|
|
514
|
+
if (type == BillingClient.ProductType.SUBS && replacementMode != -1) {
|
|
515
|
+
val replacementMode =
|
|
516
|
+
when (replacementMode) {
|
|
517
|
+
BillingFlowParams.SubscriptionUpdateParams.ReplacementMode.CHARGE_PRORATED_PRICE -> BillingFlowParams.SubscriptionUpdateParams.ReplacementMode.CHARGE_PRORATED_PRICE
|
|
518
|
+
BillingFlowParams.SubscriptionUpdateParams.ReplacementMode.WITHOUT_PRORATION -> BillingFlowParams.SubscriptionUpdateParams.ReplacementMode.WITHOUT_PRORATION
|
|
519
|
+
BillingFlowParams.SubscriptionUpdateParams.ReplacementMode.DEFERRED -> BillingFlowParams.SubscriptionUpdateParams.ReplacementMode.DEFERRED
|
|
520
|
+
BillingFlowParams.SubscriptionUpdateParams.ReplacementMode.WITH_TIME_PRORATION -> BillingFlowParams.SubscriptionUpdateParams.ReplacementMode.WITH_TIME_PRORATION
|
|
521
|
+
BillingFlowParams.SubscriptionUpdateParams.ReplacementMode.CHARGE_FULL_PRICE -> BillingFlowParams.SubscriptionUpdateParams.ReplacementMode.CHARGE_FULL_PRICE
|
|
522
|
+
else -> BillingFlowParams.SubscriptionUpdateParams.ReplacementMode.UNKNOWN_REPLACEMENT_MODE
|
|
523
|
+
}
|
|
524
|
+
subscriptionUpdateParamsBuilder.setSubscriptionReplacementMode(replacementMode)
|
|
525
|
+
}
|
|
526
|
+
if (purchaseToken != null) {
|
|
527
|
+
val subscriptionUpdateParams = subscriptionUpdateParamsBuilder.build()
|
|
528
|
+
builder.setSubscriptionUpdateParams(subscriptionUpdateParams)
|
|
529
|
+
}
|
|
500
530
|
}
|
|
501
531
|
if (obfuscatedAccountId != null) {
|
|
502
532
|
builder.setObfuscatedAccountId(obfuscatedAccountId)
|
|
@@ -504,60 +534,7 @@ class RNIapModule(
|
|
|
504
534
|
if (obfuscatedProfileId != null) {
|
|
505
535
|
builder.setObfuscatedProfileId(obfuscatedProfileId)
|
|
506
536
|
}
|
|
507
|
-
|
|
508
|
-
if (prorationMode
|
|
509
|
-
== BillingFlowParams.ProrationMode.IMMEDIATE_AND_CHARGE_PRORATED_PRICE
|
|
510
|
-
) {
|
|
511
|
-
subscriptionUpdateParamsBuilder.setReplaceProrationMode(
|
|
512
|
-
BillingFlowParams.ProrationMode.IMMEDIATE_AND_CHARGE_PRORATED_PRICE,
|
|
513
|
-
)
|
|
514
|
-
if (type != BillingClient.ProductType.SUBS) {
|
|
515
|
-
val debugMessage =
|
|
516
|
-
(
|
|
517
|
-
"IMMEDIATE_AND_CHARGE_PRORATED_PRICE for proration mode only works in" +
|
|
518
|
-
" subscription purchase."
|
|
519
|
-
)
|
|
520
|
-
val error = Arguments.createMap()
|
|
521
|
-
error.putString("debugMessage", debugMessage)
|
|
522
|
-
error.putString("code", PROMISE_BUY_ITEM)
|
|
523
|
-
error.putString("message", debugMessage)
|
|
524
|
-
error.putArray("productIds", skuArr)
|
|
525
|
-
sendEvent(reactContext, "purchase-error", error)
|
|
526
|
-
promise.safeReject(PROMISE_BUY_ITEM, debugMessage)
|
|
527
|
-
return@ensureConnection
|
|
528
|
-
}
|
|
529
|
-
} else if (prorationMode
|
|
530
|
-
== BillingFlowParams.ProrationMode.IMMEDIATE_WITHOUT_PRORATION
|
|
531
|
-
) {
|
|
532
|
-
subscriptionUpdateParamsBuilder.setReplaceProrationMode(
|
|
533
|
-
BillingFlowParams.ProrationMode.IMMEDIATE_WITHOUT_PRORATION,
|
|
534
|
-
)
|
|
535
|
-
} else if (prorationMode == BillingFlowParams.ProrationMode.DEFERRED) {
|
|
536
|
-
subscriptionUpdateParamsBuilder.setReplaceProrationMode(
|
|
537
|
-
BillingFlowParams.ProrationMode.DEFERRED,
|
|
538
|
-
)
|
|
539
|
-
} else if (prorationMode
|
|
540
|
-
== BillingFlowParams.ProrationMode.IMMEDIATE_WITH_TIME_PRORATION
|
|
541
|
-
) {
|
|
542
|
-
subscriptionUpdateParamsBuilder.setReplaceProrationMode(
|
|
543
|
-
BillingFlowParams.ProrationMode.IMMEDIATE_WITHOUT_PRORATION,
|
|
544
|
-
)
|
|
545
|
-
} else if (prorationMode
|
|
546
|
-
== BillingFlowParams.ProrationMode.IMMEDIATE_AND_CHARGE_FULL_PRICE
|
|
547
|
-
) {
|
|
548
|
-
subscriptionUpdateParamsBuilder.setReplaceProrationMode(
|
|
549
|
-
BillingFlowParams.ProrationMode.IMMEDIATE_AND_CHARGE_FULL_PRICE,
|
|
550
|
-
)
|
|
551
|
-
} else {
|
|
552
|
-
subscriptionUpdateParamsBuilder.setReplaceProrationMode(
|
|
553
|
-
BillingFlowParams.ProrationMode.UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY,
|
|
554
|
-
)
|
|
555
|
-
}
|
|
556
|
-
}
|
|
557
|
-
if (purchaseToken != null) {
|
|
558
|
-
val subscriptionUpdateParams = subscriptionUpdateParamsBuilder.build()
|
|
559
|
-
builder.setSubscriptionUpdateParams(subscriptionUpdateParams)
|
|
560
|
-
}
|
|
537
|
+
|
|
561
538
|
val flowParams = builder.build()
|
|
562
539
|
val billingResultCode = billingClient.launchBillingFlow(activity, flowParams).responseCode
|
|
563
540
|
if (billingResultCode != BillingClient.BillingResponseCode.OK) {
|
|
@@ -577,9 +554,11 @@ class RNIapModule(
|
|
|
577
554
|
promise,
|
|
578
555
|
) { billingClient ->
|
|
579
556
|
val acknowledgePurchaseParams =
|
|
580
|
-
AcknowledgePurchaseParams
|
|
581
|
-
|
|
582
|
-
|
|
557
|
+
AcknowledgePurchaseParams
|
|
558
|
+
.newBuilder()
|
|
559
|
+
.setPurchaseToken(
|
|
560
|
+
token,
|
|
561
|
+
).build()
|
|
583
562
|
billingClient.acknowledgePurchase(
|
|
584
563
|
acknowledgePurchaseParams,
|
|
585
564
|
) { billingResult: BillingResult ->
|
|
@@ -623,7 +602,10 @@ class RNIapModule(
|
|
|
623
602
|
}
|
|
624
603
|
}
|
|
625
604
|
|
|
626
|
-
override fun onPurchasesUpdated(
|
|
605
|
+
override fun onPurchasesUpdated(
|
|
606
|
+
billingResult: BillingResult,
|
|
607
|
+
purchases: List<Purchase>?,
|
|
608
|
+
) {
|
|
627
609
|
val responseCode = billingResult.responseCode
|
|
628
610
|
if (responseCode != BillingClient.BillingResponseCode.OK) {
|
|
629
611
|
val error = Arguments.createMap()
|
|
@@ -691,9 +673,11 @@ class RNIapModule(
|
|
|
691
673
|
val types = arrayOf(BillingClient.ProductType.INAPP, BillingClient.ProductType.SUBS)
|
|
692
674
|
for (type in types) {
|
|
693
675
|
billingClient.queryPurchasesAsync(
|
|
694
|
-
QueryPurchasesParams
|
|
695
|
-
|
|
696
|
-
|
|
676
|
+
QueryPurchasesParams
|
|
677
|
+
.newBuilder()
|
|
678
|
+
.setProductType(
|
|
679
|
+
type,
|
|
680
|
+
).build(),
|
|
697
681
|
) { billingResult: BillingResult, list: List<Purchase> ->
|
|
698
682
|
if (!isValidResult(billingResult, promise)) return@queryPurchasesAsync
|
|
699
683
|
|
|
@@ -739,14 +723,17 @@ class RNIapModule(
|
|
|
739
723
|
}
|
|
740
724
|
|
|
741
725
|
init {
|
|
742
|
-
val lifecycleEventListener: LifecycleEventListener =
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
726
|
+
val lifecycleEventListener: LifecycleEventListener =
|
|
727
|
+
object : LifecycleEventListener {
|
|
728
|
+
override fun onHostResume() {}
|
|
729
|
+
|
|
730
|
+
override fun onHostPause() {}
|
|
731
|
+
|
|
732
|
+
override fun onHostDestroy() {
|
|
733
|
+
billingClientCache?.endConnection()
|
|
734
|
+
billingClientCache = null
|
|
735
|
+
}
|
|
748
736
|
}
|
|
749
|
-
}
|
|
750
737
|
reactContext.addLifecycleEventListener(lifecycleEventListener)
|
|
751
738
|
}
|
|
752
739
|
}
|
|
@@ -7,10 +7,7 @@ import com.facebook.react.uimanager.ViewManager
|
|
|
7
7
|
import java.util.ArrayList
|
|
8
8
|
|
|
9
9
|
class RNIapPackage : ReactPackage {
|
|
10
|
-
|
|
11
|
-
override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
|
|
12
|
-
return emptyList()
|
|
13
|
-
}
|
|
10
|
+
override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> = emptyList()
|
|
14
11
|
|
|
15
12
|
override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
|
|
16
13
|
val modules: MutableList<NativeModule> = ArrayList()
|
|
@@ -26,7 +26,6 @@ import org.junit.Test
|
|
|
26
26
|
import java.util.*
|
|
27
27
|
|
|
28
28
|
class RNIapAmazonModuleTest {
|
|
29
|
-
|
|
30
29
|
@MockK
|
|
31
30
|
lateinit var context: ReactApplicationContext
|
|
32
31
|
|
|
@@ -77,33 +76,38 @@ class RNIapAmazonModuleTest {
|
|
|
77
76
|
|
|
78
77
|
@Test
|
|
79
78
|
fun `Purchase Item`() {
|
|
80
|
-
val purchaseResponse =
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
every {
|
|
79
|
+
val purchaseResponse =
|
|
80
|
+
mockk<PurchaseResponse> {
|
|
81
|
+
every { requestId } returns RequestId.fromString("0")
|
|
82
|
+
|
|
83
|
+
every { requestStatus } returns PurchaseResponse.RequestStatus.SUCCESSFUL
|
|
84
|
+
val mReceipt =
|
|
85
|
+
mockk<Receipt>(relaxed = true) {
|
|
86
|
+
every { sku } returns "mySku"
|
|
87
|
+
every { purchaseDate } returns Date()
|
|
88
|
+
every { receiptId } returns "rId"
|
|
89
|
+
}
|
|
90
|
+
every { receipt } returns mReceipt
|
|
91
|
+
val mUserData =
|
|
92
|
+
mockk<UserData>(relaxed = true) {
|
|
93
|
+
every { userId } returns "uid1"
|
|
94
|
+
}
|
|
95
|
+
every { userData } returns mUserData
|
|
92
96
|
}
|
|
93
|
-
every { userData } returns mUserData
|
|
94
|
-
}
|
|
95
97
|
|
|
96
98
|
every { eventSender.sendEvent(any(), any()) } just Runs
|
|
97
99
|
|
|
98
100
|
every { purchasingServiceProxy.purchase(any()) } answers {
|
|
99
101
|
listener.onPurchaseResponse(
|
|
100
102
|
purchaseResponse,
|
|
101
|
-
)
|
|
103
|
+
)
|
|
104
|
+
RequestId.fromString("0")
|
|
102
105
|
}
|
|
103
106
|
|
|
104
|
-
val itemsMap =
|
|
105
|
-
|
|
106
|
-
|
|
107
|
+
val itemsMap =
|
|
108
|
+
mockk<WritableMap>(relaxed = true) {
|
|
109
|
+
every { getString("productId") } returns "mySku"
|
|
110
|
+
}
|
|
107
111
|
mockkStatic(Arguments::class)
|
|
108
112
|
|
|
109
113
|
every { Arguments.createMap() } returns itemsMap
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-iap",
|
|
3
|
-
"version": "12.
|
|
3
|
+
"version": "12.15.0",
|
|
4
4
|
"description": "React Native In App Purchase Module.",
|
|
5
5
|
"repository": "https://github.com/dooboolab-community/react-native-iap",
|
|
6
6
|
"author": "hyochan <dooboolab@gmail.com> (https://github.com/hyochan)",
|
|
@@ -71,6 +71,7 @@
|
|
|
71
71
|
"lint:ci": "yarn lint:tsc && yarn lint:eslint -f ./node_modules/@firmnav/eslint-github-actions-formatter/dist/formatter.js && yarn lint:prettier",
|
|
72
72
|
"lint:prettier": "prettier --write \"**/*.{md,js,jsx,ts,tsx}\"",
|
|
73
73
|
"lint:swift": "swiftlint lint --fix --format --path ios/*.swift --config .swiftlint.yml",
|
|
74
|
+
"lint:kotlin": "ktlint --format",
|
|
74
75
|
"format": "git ls-files -m | xargs yarn prettier --write --ignore-unknown --no-error-on-unmatched-pattern",
|
|
75
76
|
"bootstrap": "yarn example && yarn && yarn example pods",
|
|
76
77
|
"gen:doc": "typedoc",
|