react-native-nami-sdk 1.1.0 → 2.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.
package/README.md CHANGED
@@ -1,4 +1,4 @@
1
- ![Nami SDK logo](https://nami-brand.s3.amazonaws.com/images/Nami.SDK.2.0.120x137.png)
1
+ ![Nami SDK logo](https://cdn.namiml.com/brand/sdk/Nami-SDK@0.5x.png)
2
2
 
3
3
  # React Native Bridge for the Nami SDK
4
4
 
@@ -10,48 +10,52 @@ import groovy.json.JsonSlurper
10
10
  // * https://github.com/facebook/react-native/blob/0.60-stable/template/android/app/build.gradle
11
11
  // original location:
12
12
  // - https://github.com/facebook/react-native/blob/0.58-stable/local-cli/templates/HelloWorld/android/app/build.gradle
13
- def DEFAULT_COMPILE_SDK_VERSION = 30
14
- def DEFAULT_BUILD_TOOLS_VERSION = '29.0.3'
15
- def DEFAULT_MIN_SDK_VERSION = 21
16
- def DEFAULT_TARGET_SDK_VERSION = 30
13
+ def DEFAULT_BUILD_TOOLS_VERSION = '30.0.2'
17
14
  def safeExtGet(prop, fallback) {
18
15
  rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
19
16
  }
20
17
  apply plugin: 'com.android.library'
21
18
  apply plugin: 'maven'
22
19
  apply plugin: "kotlin-android"
23
- apply plugin: "kotlin-android-extensions"
24
20
  buildscript {
25
- ext.kotlin_version = '1.4.30'
21
+ ext.kotlin_version = '1.4.31'
26
22
  // The Android Gradle plugin is only required when opening the android folder stand-alone.
27
23
  // This avoids unnecessary downloads and potential conflicts when the library is included as a
28
24
  // module dependency in an application project.
29
25
  // ref: https://docs.gradle.org/current/userguide/tutorial_using_tasks.html#sec:build_script_external_dependencies
30
26
  if (project == rootProject) {
31
27
  repositories {
28
+ mavenCentral()
32
29
  google()
33
- jcenter()
34
30
  }
35
31
  dependencies {
36
- classpath 'com.android.tools.build:gradle:4.1.2'
32
+ classpath 'com.android.tools.build:gradle:4.2.1'
37
33
  classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
38
34
  }
39
35
  }
40
36
  }
41
37
  android {
42
- compileSdkVersion safeExtGet('compileSdkVersion', DEFAULT_COMPILE_SDK_VERSION)
38
+ compileSdkVersion 31
43
39
  buildToolsVersion safeExtGet('buildToolsVersion', DEFAULT_BUILD_TOOLS_VERSION)
44
40
  defaultConfig {
45
- minSdkVersion safeExtGet('minSdkVersion', DEFAULT_MIN_SDK_VERSION)
46
- targetSdkVersion safeExtGet('targetSdkVersion', DEFAULT_TARGET_SDK_VERSION)
41
+ minSdkVersion 26
42
+ targetSdkVersion 31
47
43
  versionCode 1
48
44
  versionName "1.0"
49
45
  }
46
+ compileOptions {
47
+ sourceCompatibility JavaVersion.VERSION_1_8
48
+ targetCompatibility JavaVersion.VERSION_1_8
49
+ }
50
50
  lintOptions {
51
51
  abortOnError false
52
52
  }
53
+ kotlinOptions {
54
+ jvmTarget = "1.8"
55
+ }
53
56
  }
54
57
  repositories {
58
+ mavenCentral()
55
59
  // ref: https://www.baeldung.com/maven-local-repository
56
60
  maven { url("https://nami-android.s3.amazonaws.com/") }
57
61
  maven {
@@ -63,14 +67,14 @@ repositories {
63
67
  url("$rootDir/../node_modules/jsc-android/dist")
64
68
  }
65
69
  google()
66
- jcenter()
67
70
  }
71
+
68
72
  dependencies {
69
73
  //noinspection GradleDynamicVersion
70
74
  implementation fileTree(dir: 'libs', include: ['*.jar'])
71
- implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
75
+ implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
72
76
 
73
- implementation "com.namiml:sdk-android:1.1.1"
77
+ implementation "com.namiml:sdk-android:2.0.0"
74
78
 
75
79
  implementation 'com.facebook.react:react-native:+' // From node_modules
76
80
  }
@@ -1,6 +1,6 @@
1
- #Sun Nov 15 19:24:03 EST 2020
1
+ #Wed May 26 17:12:26 EDT 2021
2
2
  distributionBase=GRADLE_USER_HOME
3
+ distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-bin.zip
3
4
  distributionPath=wrapper/dists
4
- zipStoreBase=GRADLE_USER_HOME
5
5
  zipStorePath=wrapper/dists
6
- distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip
6
+ zipStoreBase=GRADLE_USER_HOME
@@ -1,4 +1,3 @@
1
- <manifest xmlns:android="http://schemas.android.com/apk/res/android"
2
- package="com.nami.reactlibrary">
1
+ <manifest package="com.nami.reactlibrary">
3
2
 
4
3
  </manifest>
@@ -15,6 +15,7 @@ import com.facebook.react.bridge.WritableNativeArray
15
15
  import com.namiml.Nami
16
16
  import com.namiml.NamiConfiguration
17
17
  import com.namiml.NamiExternalIdentifierType
18
+ import com.namiml.NamiLanguageCode
18
19
  import com.namiml.NamiLogLevel
19
20
 
20
21
  class NamiBridgeModule(reactContext: ReactApplicationContext) :
@@ -26,6 +27,7 @@ class NamiBridgeModule(reactContext: ReactApplicationContext) :
26
27
  private const val CONFIG_MAP_DEVELOPMENT_MODE_KEY = "developmentMode"
27
28
  private const val CONFIG_MAP_BYPASS_STORE_KEY = "bypassStore"
28
29
  private const val CONFIG_MAP_NAMI_COMMANDS_KEY = "namiCommands"
30
+ private const val CONFIG_MAP_LANGUAGE_CODE_KEY = "namiLanguageCode"
29
31
  private const val PLATFORM_ID_ERROR_VALUE = "APPPLATFORMID_NOT_FOUND"
30
32
  }
31
33
 
@@ -33,12 +35,6 @@ class NamiBridgeModule(reactContext: ReactApplicationContext) :
33
35
  return "NamiBridge"
34
36
  }
35
37
 
36
- // @ReactMethod
37
- // public void sampleMethod(String stringArgument, int numberArgument, Callback callback) {
38
- // // TODO: Implement some actually useful functionality
39
- // callback.invoke("Received numberArgument: " + numberArgument + " stringArgument: " + stringArgument);
40
- // }
41
-
42
38
  @ReactMethod
43
39
  fun configure(configDict: ReadableMap) {
44
40
 
@@ -106,13 +102,32 @@ class NamiBridgeModule(reactContext: ReactApplicationContext) :
106
102
  builder.bypassStore = true
107
103
  }
108
104
 
105
+ val languageCode = if (configDict.hasKey(CONFIG_MAP_LANGUAGE_CODE_KEY)) {
106
+ configDict.getString(CONFIG_MAP_LANGUAGE_CODE_KEY)
107
+ } else {
108
+ null
109
+ }
110
+ languageCode?.let { code ->
111
+ NamiLanguageCode.values().find { it.code == code }.let { namiLanguageCode ->
112
+ if (namiLanguageCode == null) {
113
+ Log.w(
114
+ LOG_TAG,
115
+ "Nami language code from config dictionary \"$code\" not " +
116
+ "found in list of available Nami Language Codes:\n"
117
+ )
118
+ } else {
119
+ builder.namiLanguageCode = namiLanguageCode
120
+ }
121
+ }
122
+ }
123
+
109
124
  val namiCommandsReact: ReadableArray? =
110
125
  if (configDict.hasKey(CONFIG_MAP_NAMI_COMMANDS_KEY)) {
111
126
  configDict.getArray(CONFIG_MAP_NAMI_COMMANDS_KEY)
112
127
  } else {
113
128
  Arguments.createArray()
114
129
  }
115
- val settingsList = mutableListOf("extendedClientInfo:react-native:0.3.1")
130
+ val settingsList = mutableListOf("extendedClientInfo:react-native:1.2.1")
116
131
  namiCommandsReact?.toArrayList()?.filterIsInstance<String>()?.let { commandsFromReact ->
117
132
  settingsList.addAll(commandsFromReact)
118
133
  }
@@ -1,10 +1,12 @@
1
1
  package com.nami.reactlibrary
2
2
 
3
+ import android.util.Log
3
4
  import com.facebook.react.bridge.Callback
4
5
  import com.facebook.react.bridge.ReactApplicationContext
5
6
  import com.facebook.react.bridge.ReactContextBaseJavaModule
6
7
  import com.facebook.react.bridge.ReactMethod
7
- import com.facebook.react.bridge.WritableNativeMap
8
+ import com.facebook.react.modules.core.DeviceEventManagerModule
9
+ import com.namiml.customer.CustomerJourneyState
8
10
  import com.namiml.customer.NamiCustomerManager
9
11
 
10
12
  class NamiCustomerManagerBridgeModule(reactContext: ReactApplicationContext) :
@@ -14,24 +16,27 @@ class NamiCustomerManagerBridgeModule(reactContext: ReactApplicationContext) :
14
16
  return "NamiCustomerManagerBridge"
15
17
  }
16
18
 
19
+ init {
20
+ NamiCustomerManager.registerCustomerJourneyChangedListener { customerJourneyState ->
21
+ emitCustomerJourneyChanged(customerJourneyState)
22
+ }
23
+ }
24
+
25
+ private fun emitCustomerJourneyChanged(customerJourneyState: CustomerJourneyState) {
26
+ Log.i(LOG_TAG, "Emitting CustomerJourneyState changed")
27
+ try {
28
+ reactApplicationContext
29
+ .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
30
+ .emit("CustomerJourneyStateChanged", customerJourneyState.toDict())
31
+ } catch (e: Exception) {
32
+ Log.e(LOG_TAG, "Caught Exception: " + e.message)
33
+ }
34
+ }
35
+
17
36
  @ReactMethod
18
37
  fun currentCustomerJourneyState(resultsCallback: Callback) {
19
38
  reactApplicationContext.runOnUiQueueThread {
20
- val journeyState = NamiCustomerManager.currentCustomerJourneyState()
21
-
22
- val formerSubscriber = journeyState?.formerSubscriber ?: false
23
- val inGracePeriod = journeyState?.inGracePeriod ?: false
24
- val inTrialPeriod = journeyState?.inTrialPeriod ?: false
25
- val inIntroOfferPeriod = journeyState?.inIntroOfferPeriod ?: false
26
-
27
- val journeyDict = WritableNativeMap().apply {
28
- putBoolean("formerSubscriber", formerSubscriber)
29
- putBoolean("inGracePeriod", inGracePeriod)
30
- putBoolean("inTrialPeriod", inTrialPeriod)
31
- putBoolean("inIntroOfferPeriod", inIntroOfferPeriod)
32
- }
33
-
34
- resultsCallback.invoke(journeyDict)
39
+ resultsCallback.invoke(NamiCustomerManager.currentCustomerJourneyState().toDict())
35
40
  }
36
41
  }
37
42
  }
@@ -11,7 +11,8 @@ import com.facebook.react.modules.core.DeviceEventManagerModule
11
11
  import com.namiml.billing.NamiPurchase
12
12
  import com.namiml.billing.NamiPurchaseManager
13
13
  import com.namiml.billing.NamiPurchaseState
14
- import com.namiml.entitlement.NamiEntitlement
14
+ import com.namiml.customer.CustomerJourneyState
15
+ import com.namiml.customer.NamiCustomerManager
15
16
  import com.namiml.paywall.NamiPaywall
16
17
  import com.namiml.paywall.NamiPaywallManager
17
18
  import com.namiml.paywall.NamiSKU
@@ -32,15 +33,15 @@ class NamiEmitter(reactContext: ReactApplicationContext) :
32
33
  }
33
34
 
34
35
  override fun initialize() {
35
- NamiPaywallManager.registerSignInListener { context, paywallData, developerPaywallID ->
36
+ NamiPaywallManager.registerSignInListener { _, _, developerPaywallID ->
36
37
  Log.i(LOG_TAG, "Sign in clicked with developerPaywallID $developerPaywallID")
37
38
 
38
- emitSignInActivated(paywallData, developerPaywallID)
39
+ emitSignInActivated(developerPaywallID)
39
40
  }
40
41
 
41
42
  Log.i(LOG_TAG, "In Emitter Initialize()")
42
43
 
43
- NamiPaywallManager.registerPaywallRaiseListener { context, paywallData, products, developerPaywallId ->
44
+ NamiPaywallManager.registerPaywallRaiseListener { _, paywallData, products, developerPaywallId ->
44
45
  Log.i(
45
46
  LOG_TAG,
46
47
  "Products from registerPaywallRaiseListener callback are $products"
@@ -53,28 +54,24 @@ class NamiEmitter(reactContext: ReactApplicationContext) :
53
54
  NamiPurchaseManager.registerPurchasesChangedListener { list, namiPurchaseState, s ->
54
55
  emitPurchaseMade(list, namiPurchaseState, s)
55
56
  }
56
- }
57
-
58
- fun emitEntitlementsChanged(entitlements: List<NamiEntitlement>) {
59
- val map = Arguments.createMap()
60
57
 
61
- val resultArray: WritableArray = WritableNativeArray()
62
- for (entitlement in entitlements) {
63
- resultArray.pushMap(entitlement.toEntitlementDict())
58
+ NamiCustomerManager.registerCustomerJourneyChangedListener {
59
+ emitCustomerJourneyChanged(it)
64
60
  }
65
- map.putArray("entitlements", resultArray)
61
+ }
66
62
 
67
- Log.i(LOG_TAG, "Emitting entitlements changed")
63
+ private fun emitCustomerJourneyChanged(customerJourneyState: CustomerJourneyState) {
64
+ Log.i(LOG_TAG, "Emitting CustomerJourneyChanged $customerJourneyState")
68
65
  try {
69
66
  reactApplicationContext
70
67
  .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
71
- .emit("EntitlementsChanged", map)
68
+ .emit("CustomerJourneyStateChanged", customerJourneyState.toDict())
72
69
  } catch (e: Exception) {
73
70
  Log.e(LOG_TAG, "Caught Exception: " + e.message)
74
71
  }
75
72
  }
76
73
 
77
- fun emitPurchaseMade(
74
+ private fun emitPurchaseMade(
78
75
  purchases: List<NamiPurchase>,
79
76
  purchaseState: NamiPurchaseState,
80
77
  errorString: String?
@@ -117,7 +114,7 @@ class NamiEmitter(reactContext: ReactApplicationContext) :
117
114
  }
118
115
  }
119
116
 
120
- fun emitPaywallRaise(
117
+ private fun emitPaywallRaise(
121
118
  namiPaywall: NamiPaywall,
122
119
  productDicts: List<NamiSKU>,
123
120
  paywallDeveloperID: String?
@@ -139,7 +136,7 @@ class NamiEmitter(reactContext: ReactApplicationContext) :
139
136
  skusArray.pushMap(sku.toSkuDict())
140
137
  }
141
138
 
142
- map.putArray("skus", skusArray)
139
+ map.putArray("namiSkus", skusArray)
143
140
 
144
141
  try {
145
142
  reactApplicationContext
@@ -150,7 +147,7 @@ class NamiEmitter(reactContext: ReactApplicationContext) :
150
147
  }
151
148
  }
152
149
 
153
- fun emitSignInActivated(paywallData: NamiPaywall, paywallDeveloperID: String?) {
150
+ private fun emitSignInActivated(paywallDeveloperID: String?) {
154
151
 
155
152
  val map = Arguments.createMap().apply {
156
153
  putString("developerPaywallID", paywallDeveloperID)
@@ -163,19 +160,4 @@ class NamiEmitter(reactContext: ReactApplicationContext) :
163
160
  Log.e(LOG_TAG, "Caught Exception: " + e.message)
164
161
  }
165
162
  }
166
-
167
- fun emitPurchaseMade(paywallDeveloperID: String) {
168
-
169
- val map = Arguments.createMap().apply {
170
- putString("key1", "Value1")
171
- putString("key1", "Value1")
172
- }
173
- try {
174
- reactApplicationContext
175
- .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
176
- .emit("customEventName", map)
177
- } catch (e: Exception) {
178
- Log.e(LOG_TAG, "Caught Exception: " + e.message)
179
- }
180
- }
181
163
  }
@@ -12,6 +12,7 @@ import com.facebook.react.bridge.ReactMethod
12
12
  import com.facebook.react.bridge.WritableNativeMap
13
13
  import com.facebook.react.modules.core.DeviceEventManagerModule
14
14
  import com.namiml.paywall.NamiPaywallManager
15
+ import com.namiml.paywall.PreparePaywallResult
15
16
 
16
17
  class NamiPaywallManagerBridgeModule(reactContext: ReactApplicationContext) :
17
18
  ReactContextBaseJavaModule(reactContext), ActivityEventListener {
@@ -46,7 +47,7 @@ class NamiPaywallManagerBridgeModule(reactContext: ReactApplicationContext) :
46
47
  }
47
48
  }
48
49
 
49
- fun emitBockedPaywallClosed() {
50
+ private fun emitBockedPaywallClosed() {
50
51
  val map = Arguments.createMap().apply {
51
52
  putBoolean("blockingPaywallClosed", true)
52
53
  }
@@ -122,46 +123,77 @@ class NamiPaywallManagerBridgeModule(reactContext: ReactApplicationContext) :
122
123
  // TODO: Android SDK paywall impression call.
123
124
  }
124
125
 
125
-
126
126
  @ReactMethod
127
127
  fun preparePaywallForDisplay(backgroundImageRequired: Boolean, imageFetchTimeout: Double) {
128
128
  val imageFetchTimeoutConvertedToLong: Long = imageFetchTimeout.toLong()
129
129
  reactApplicationContext.runOnUiQueueThread {
130
- NamiPaywallManager.preparePaywallForDisplay(backgroundImageRequired, imageFetchTimeoutConvertedToLong) { success, error ->
131
- emitPreparePaywallFinished(success, null, error)
130
+
131
+ NamiPaywallManager.preparePaywallForDisplay(
132
+ backgroundImageRequired,
133
+ imageFetchTimeoutConvertedToLong
134
+ ) { result ->
135
+ when (result) {
136
+ is PreparePaywallResult.Success -> {
137
+ emitPreparePaywallFinished(true, null, null)
138
+ }
139
+ is PreparePaywallResult.Failure -> {
140
+ emitPreparePaywallFinished(false, null, result.error)
141
+ }
142
+ }
132
143
  }
133
144
  }
134
145
  }
135
146
 
136
-
137
147
  @ReactMethod
138
- fun preparePaywallForDisplayByDeveloperPaywallId(developerPaywallID: String, backgroundImageRequired: Boolean, imageFetchTimeout: Double) {
148
+ fun preparePaywallForDisplayByDeveloperPaywallId(
149
+ developerPaywallID: String,
150
+ backgroundImageRequired: Boolean,
151
+ imageFetchTimeout: Double
152
+ ) {
139
153
  val imageFetchTimeoutConvertedToLong: Long = imageFetchTimeout.toLong()
140
154
  reactApplicationContext.runOnUiQueueThread {
141
- NamiPaywallManager.preparePaywallForDisplay(developerPaywallID, backgroundImageRequired, imageFetchTimeoutConvertedToLong) { success, error ->
142
- emitPreparePaywallFinished(success, developerPaywallID, error)
155
+ NamiPaywallManager.preparePaywallForDisplay(
156
+ developerPaywallID,
157
+ backgroundImageRequired,
158
+ imageFetchTimeoutConvertedToLong
159
+ ) { result ->
160
+ when (result) {
161
+ is PreparePaywallResult.Success -> {
162
+ emitPreparePaywallFinished(true, developerPaywallID, null)
163
+ }
164
+ is PreparePaywallResult.Failure -> {
165
+ emitPreparePaywallFinished(false, developerPaywallID, result.error)
166
+ }
167
+ }
143
168
  }
144
169
  }
145
170
  }
146
171
 
147
- fun emitPreparePaywallFinished(success: Boolean, developerPaywallID:String?, error: com.namiml.paywall.PreparePaywallError?) {
172
+ private fun emitPreparePaywallFinished(
173
+ success: Boolean,
174
+ developerPaywallID: String?,
175
+ error: com.namiml.paywall.PreparePaywallError?
176
+ ) {
148
177
  val prepareContentMap = Arguments.createMap()
149
178
  prepareContentMap.putBoolean("success", success)
150
179
 
151
- if ( developerPaywallID != null ) {
180
+ if (developerPaywallID != null) {
152
181
  prepareContentMap.putString("developerPaywallID", developerPaywallID)
153
182
  }
154
183
 
155
- if ( error != null ) {
184
+ if (error != null) {
156
185
  prepareContentMap.putInt("errorCode", error.ordinal)
157
186
  prepareContentMap.putString("errorMessage", error.toString())
158
187
  }
159
188
 
160
- Log.i(LOG_TAG, "Emitting preparePaywallForDisplay finished with result " + success + "error: " + error.toString())
189
+ Log.i(
190
+ LOG_TAG,
191
+ "Emitting preparePaywallForDisplay finished with result " + success + "error: " + error.toString()
192
+ )
161
193
  try {
162
194
  reactApplicationContext
163
- .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
164
- .emit("PreparePaywallFinished", prepareContentMap)
195
+ .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
196
+ .emit("PreparePaywallFinished", prepareContentMap)
165
197
  } catch (e: Exception) {
166
198
  Log.e(LOG_TAG, "Caught Exception: " + e.message)
167
199
  }
@@ -70,6 +70,13 @@ class NamiPurchaseManagerBridgeModule(reactContext: ReactApplicationContext) :
70
70
  }
71
71
  }
72
72
 
73
+ @ReactMethod
74
+ fun consumePurchasedSKU(skuRefId: String) {
75
+ reactApplicationContext.runOnUiQueueThread {
76
+ NamiPurchaseManager.consumePurchasedSKU(skuRefId)
77
+ }
78
+ }
79
+
73
80
  @ReactMethod
74
81
  fun anySKUIDPurchased(skuIDs: ReadableArray, resultsCallback: Callback) {
75
82
  reactApplicationContext.runOnUiQueueThread {
@@ -6,12 +6,15 @@ import com.facebook.react.bridge.WritableArray
6
6
  import com.facebook.react.bridge.WritableMap
7
7
  import com.facebook.react.bridge.WritableNativeArray
8
8
  import com.facebook.react.bridge.WritableNativeMap
9
- import com.namiml.api.model.FormattedSku
10
9
  import com.namiml.billing.NamiPurchase
10
+ import com.namiml.customer.CustomerJourneyState
11
11
  import com.namiml.entitlement.NamiEntitlement
12
+ import com.namiml.paywall.LegalCitations
13
+ import com.namiml.paywall.NamiLocaleConfig
12
14
  import com.namiml.paywall.NamiPaywall
13
15
  import com.namiml.paywall.NamiPurchaseSource
14
16
  import com.namiml.paywall.NamiSKU
17
+ import com.namiml.paywall.PaywallDisplayOptions
15
18
  import com.namiml.paywall.PaywallStyleData
16
19
  import com.namiml.paywall.SubscriptionPeriod
17
20
  import com.namiml.util.extensions.getFormattedPrice
@@ -22,19 +25,9 @@ import java.util.Date
22
25
  import java.util.Locale
23
26
  import java.util.TimeZone
24
27
 
25
- fun FormattedSku.toFormattedSkuDict(): WritableMap {
26
- val map: WritableMap = Arguments.createMap()
27
- map.putString("sku_ref_id", this.skuId)
28
- map.putBoolean("featured", this.featured)
29
- return map
30
- }
31
-
32
28
  fun NamiPaywall.toNamiPaywallDict(): WritableMap {
33
29
 
34
30
  val paywallMap: WritableMap = Arguments.createMap()
35
- // These don't seem to exist, inconsistent between Android and iOS, leave in marketing content
36
- //paywallMap.putString("title", title.orEmpty())
37
- //paywallMap.putString("body", body.orEmpty())
38
31
 
39
32
  val marketingContentMap = Arguments.createMap()
40
33
  marketingContentMap.putString("title", title.orEmpty())
@@ -49,37 +42,55 @@ fun NamiPaywall.toNamiPaywallDict(): WritableMap {
49
42
 
50
43
  Log.i(LOG_TAG, "extraData items are $extraDataMap")
51
44
  paywallMap.putString("id", id)
52
- paywallMap.putString("background_image_url_phone", backgroundImageUrlPhone.orEmpty())
53
- paywallMap.putString("background_image_url_tablet", backgroundImageUrlTablet.orEmpty())
54
- paywallMap.putString("privacy_policy", privacyPolicy.orEmpty())
45
+ paywallMap.putMap("backgrounds", Arguments.createMap().apply {
46
+ putString("phone", backgroundImageUrlPhone.orEmpty())
47
+ putString("tablet", backgroundImageUrlTablet.orEmpty())
48
+ })
55
49
  paywallMap.putString("purchase_terms", purchaseTerms.orEmpty())
56
- paywallMap.putString("tos_link", tosLink.orEmpty())
57
50
  paywallMap.putString("name", name.orEmpty())
58
- paywallMap.putString("cta_type", type)
51
+ paywallMap.putString("paywall_type", type)
52
+ paywallMap.putMap("locale_config", localeConfig.toDict())
53
+ paywallMap.putMap("legal_citations", legalCitations?.toDict())
54
+ paywallMap.putMap("display_options", displayOptions.toDict())
59
55
  paywallMap.putString("developer_paywall_id", developerPaywallId.orEmpty())
60
-
61
- val allowClosing = allowClosing
62
- paywallMap.putBoolean("allow_closing", allowClosing)
63
-
64
- val restoreControl = restoreControl
65
- paywallMap.putBoolean("restore_control", restoreControl)
66
-
67
- val signInControl = signInControl
68
- paywallMap.putBoolean("sign_in_control", signInControl)
69
-
70
- val formattedSkusArray: WritableArray = Arguments.createArray()
71
- for (formattedSku in formattedSkus) {
72
- formattedSkusArray.pushMap(formattedSku.toFormattedSkuDict())
73
- }
74
- paywallMap.putArray("formatted_skus", formattedSkusArray)
75
-
76
56
  styleData?.let {
77
57
  paywallMap.putMap("styleData", it.toPaywallStylingDict())
78
58
  }
59
+ return paywallMap
60
+ }
79
61
 
80
- paywallMap.putBoolean("use_bottom_overlay", useBottomOverlay)
62
+ private fun NamiLocaleConfig.toDict(): WritableMap {
63
+ return Arguments.createMap().apply {
64
+ putString("close_button_text", closeButtonText)
65
+ putString("sign_in_button_text", signInButtonText)
66
+ putString("restore_purchase_button_text", restorePurchaseButtonText)
67
+ putString("purchase_button_hint_text_to_speech", purchaseButtonHintTextToSpeech)
68
+ putString("purchase_terms_prefix_hint_text_to_speech", purchaseTermsPrefixHintTextToSpeech)
69
+ }
70
+ }
81
71
 
82
- return paywallMap
72
+ private fun PaywallDisplayOptions.toDict(): WritableMap {
73
+ return Arguments.createMap().apply {
74
+ putBoolean("allow_closing", allowClosing)
75
+ putBoolean("restore_control", restoreControl)
76
+ putBoolean("sign_in_control", signInControl)
77
+ putString("scrollable_region_size", scrollableRegionSize)
78
+ putBoolean("show_nami_purchase_success_message", shouldShowNamiPurchaseSuccessMessage)
79
+ putBoolean("skus_in_scrollable_region", showSkusInScrollableRegion)
80
+ putBoolean("use_bottom_overlay", useBottomOverlay)
81
+ }
82
+ }
83
+
84
+ private fun LegalCitations.toDict(): WritableMap {
85
+ return Arguments.createMap().apply {
86
+ putString("id", id)
87
+ putString("privacy_url", privacyUrl)
88
+ putString("privacy_text", privacyText)
89
+ putString("tos_url", tosUrl)
90
+ putString("tos_text", tosText)
91
+ putString("clickwrap_text", clickWrapText)
92
+ putString("language", language)
93
+ }
83
94
  }
84
95
 
85
96
  fun PaywallStyleData.toPaywallStylingDict(): WritableMap {
@@ -107,6 +118,9 @@ fun PaywallStyleData.toPaywallStylingDict(): WritableMap {
107
118
 
108
119
  styleMap.putString("skuButtonColor", skuButtonColor)
109
120
  styleMap.putString("skuButtonTextColor", skuButtonTextColor)
121
+ styleMap.putString("skuSubDisplayTextColor", skuSubDisplayTextColor)
122
+ styleMap.putString("skuSubDisplayTextShadowColor", skuSubDisplayTextShadowColor)
123
+ styleMap.putDouble("skuSubDisplayTextShadowRadius", skuSubDisplayTextShadowRadius.toDouble())
110
124
 
111
125
  styleMap.putString("featuredSkusButtonColor", featuredSkuButtonColor)
112
126
  styleMap.putString("featuredSkusButtonTextColor", featuredSkuButtonTextColor)
@@ -196,6 +210,9 @@ fun NamiSKU.toSkuDict(): WritableMap {
196
210
  productDict.putString("localizedDescription", skuDetails.description)
197
211
  productDict.putString("localizedPrice", skuDetails.price)
198
212
  productDict.putString("localizedMultipliedPrice", skuDetails.price)
213
+ productDict.putBoolean("featured", featured)
214
+ productDict.putString("displayText", displayText)
215
+ productDict.putString("displaySubText", displaySubText)
199
216
  productDict.putString("price", skuDetails.getFormattedPrice().toString())
200
217
  productDict.putString("priceLanguage", Locale.getDefault().language)
201
218
  productDict.putString("priceCountry", Locale.getDefault().country)
@@ -208,9 +225,6 @@ fun NamiSKU.toSkuDict(): WritableMap {
208
225
  SubscriptionPeriod.HALF_YEAR -> {
209
226
  "half_year"
210
227
  }
211
- SubscriptionPeriod.DAY -> {
212
- "day"
213
- }
214
228
  SubscriptionPeriod.WEEKLY -> {
215
229
  "week"
216
230
  }
@@ -220,6 +234,9 @@ fun NamiSKU.toSkuDict(): WritableMap {
220
234
  SubscriptionPeriod.ANNUAL -> {
221
235
  "year"
222
236
  }
237
+ SubscriptionPeriod.FOUR_WEEKS -> {
238
+ "four_weeks"
239
+ }
223
240
  else -> {
224
241
  null
225
242
  }
@@ -268,6 +285,26 @@ fun NamiPurchase.toPurchaseDict(): WritableMap {
268
285
  return purchaseMap
269
286
  }
270
287
 
288
+ fun CustomerJourneyState?.toDict(): WritableMap {
289
+ val formerSubscriber = this?.formerSubscriber ?: false
290
+ val inGracePeriod = this?.inGracePeriod ?: false
291
+ val inTrialPeriod = this?.inTrialPeriod ?: false
292
+ val inIntroOfferPeriod = this?.inIntroOfferPeriod ?: false
293
+ val inPause = this?.inPause ?: false
294
+ val inAccountHold = this?.inAccountHold ?: false
295
+ val isCancelled = this?.isCancelled ?: false
296
+
297
+ return WritableNativeMap().apply {
298
+ putBoolean("formerSubscriber", formerSubscriber)
299
+ putBoolean("inGracePeriod", inGracePeriod)
300
+ putBoolean("inTrialPeriod", inTrialPeriod)
301
+ putBoolean("inIntroOfferPeriod", inIntroOfferPeriod)
302
+ putBoolean("inPause", inPause)
303
+ putBoolean("inAccountHold", inAccountHold)
304
+ putBoolean("isCancelled", isCancelled)
305
+ }
306
+ }
307
+
271
308
  fun NamiEntitlement.toEntitlementDict(): WritableMap? {
272
309
  val resultMap: WritableMap = WritableNativeMap()
273
310
  resultMap.putString("referenceID", referenceId)
@@ -322,4 +359,3 @@ fun Date.toJavascriptDate(): String {
322
359
  }
323
360
  return df.format(this)
324
361
  }
325
-
package/ios/Nami.m CHANGED
@@ -21,7 +21,9 @@
21
21
  @implementation NamiBridge (RCTExternModule)
22
22
 
23
23
  RCT_EXPORT_METHOD(configure: (NSDictionary *)configDict) {
24
-
24
+ if ([configDict count] == 0 || [configDict[@"logLevel"] isEqual: @"DEBUG"] ) {
25
+ NSLog(@"Configure dictionary is %@", configDict);
26
+ }
25
27
  NSString *appID = configDict[@"appPlatformID-apple"];
26
28
 
27
29
  if ([appID length] > 0 ) {
@@ -39,6 +41,17 @@ RCT_EXPORT_METHOD(configure: (NSDictionary *)configDict) {
39
41
  config.logLevel = NamiLogLevelDebug;
40
42
  }
41
43
 
44
+ NSString *languageString = configDict[@"namiLanguageCode"];
45
+ if ([logLevelString length] > 0) {
46
+ NSLog(@"Nami language code from config dictionary is %@", languageString);
47
+ if ([[NamiLanguageCodes allAvailiableNamiLanguageCodes]
48
+ containsObject:[languageString lowercaseString]] ) {
49
+ config.namiLanguageCode = languageString;
50
+ } else {
51
+ NSLog(@"Warning: Nami language code from config dictionary %@ not found in list of available Nami Language Codes:\n%@", languageString, [NamiLanguageCodes allAvailiableNamiLanguageCodes]);
52
+ }
53
+ }
54
+
42
55
  NSObject *bypassString = configDict[@"bypassStore"];
43
56
  if ( bypassString != NULL )
44
57
  {
@@ -70,7 +83,7 @@ RCT_EXPORT_METHOD(configure: (NSDictionary *)configDict) {
70
83
  }
71
84
 
72
85
  // Start commands with header iformation for Nami to let them know this is a React client.
73
- NSMutableArray *namiCommandStrings = [NSMutableArray arrayWithArray:@[@"extendedClientInfo:react-native:0.3.0"]];
86
+ NSMutableArray *namiCommandStrings = [NSMutableArray arrayWithArray:@[@"extendedClientInfo:react-native:2.0.0"]];
74
87
 
75
88
  // Add additional namiCommands app may have sent in.
76
89
  NSObject *appCommandStrings = configDict[@"namiCommands"];
@@ -25,6 +25,8 @@
25
25
 
26
26
  + (NSArray *)stripPresentationPositionFromOrderedMetadataForPaywallMetaDict: (NSDictionary *)paywallMeta;
27
27
 
28
+ + (NSDictionary<NSString *,NSString *> *) customerJourneyStateDict;
29
+
28
30
  + (NSString *)hexStringForColor:(UIColor *)color;
29
31
 
30
32
  @end
@@ -28,6 +28,10 @@
28
28
  productDict[@"priceLanguage"] = productInt.priceLocale.languageCode;
29
29
  productDict[@"priceCountry"] = productInt.priceLocale.countryCode;
30
30
  productDict[@"priceCurrency"] = productInt.priceLocale.currencyCode;
31
+
32
+ // Add smart text processed values for sku buttons to sku dictionary
33
+ productDict[@"displayText"] = [sku namiDisplayText];
34
+ productDict[@"displaySubText"] = [sku namiSubDisplayText];
31
35
 
32
36
  if (@available(iOS 12.0, *)) {
33
37
  if (productInt != nil && productInt.subscriptionGroupIdentifier != nil) {
@@ -201,6 +205,30 @@
201
205
  return [NSISO8601DateFormatter stringFromDate:purchaseTimestamp timeZone:UTC formatOptions:options];
202
206
  }
203
207
 
208
+
209
+ + (NSDictionary<NSString *,NSString *> *) customerJourneyStateDict {
210
+ CustomerJourneyState *journeyState = [NamiCustomerManager currentCustomerJourneyState];
211
+
212
+ BOOL formerSubscriber = [journeyState formerSubscriber];
213
+ BOOL inGracePeriod = [journeyState inGracePeriod];
214
+ BOOL inTrialPeriod = [journeyState inTrialPeriod];
215
+ BOOL inIntroOfferPeriod = [journeyState inIntroOfferPeriod];
216
+
217
+ BOOL isCancelled = [journeyState isCancelled];
218
+ BOOL inPause = [journeyState inPause];
219
+ BOOL inAccountHold = [journeyState inAccountHold];
220
+
221
+ NSDictionary *journeyDict = @{@"formerSubscriber":@(formerSubscriber),
222
+ @"inGracePeriod":@(inGracePeriod),
223
+ @"inTrialPeriod":@(inTrialPeriod),
224
+ @"inIntroOfferPeriod":@(inIntroOfferPeriod),
225
+ @"isCancelled":@(isCancelled),
226
+ @"inPause":@(inPause),
227
+ @"inAccountHold":@(inAccountHold)
228
+ };
229
+ return journeyDict;
230
+ }
231
+
204
232
  + (NSDictionary<NSString *,NSString *> *) paywallStylingToPaywallStylingDict:(PaywallStyleData *)styling {
205
233
  NSMutableDictionary<NSString *,id> *stylingDict = [NSMutableDictionary new];
206
234
  if (styling != nil) {
@@ -22,18 +22,7 @@
22
22
 
23
23
  RCT_EXPORT_METHOD(currentCustomerJourneyState:(RCTResponseSenderBlock)completion)
24
24
  {
25
- CustomerJourneyState *journeyState = [NamiCustomerManager currentCustomerJourneyState];
26
-
27
- BOOL formerSubscriber = [journeyState formerSubscriber];
28
- BOOL inGracePeriod = [journeyState inGracePeriod];
29
- BOOL inTrialPeriod = [journeyState inTrialPeriod];
30
- BOOL inIntroOfferPeriod = [journeyState inIntroOfferPeriod];
31
-
32
- NSDictionary *journeyDict = @{@"formerSubscriber":@(formerSubscriber),
33
- @"inGracePeriod":@(inGracePeriod),
34
- @"inTrialPeriod":@(inTrialPeriod),
35
- @"inIntroOfferPeriod":@(inIntroOfferPeriod)
36
- };
25
+ NSDictionary *journeyDict = [NamiBridgeUtil customerJourneyStateDict];
37
26
 
38
27
  completion(@[journeyDict]);
39
28
  }
package/ios/NamiEmitter.m CHANGED
@@ -54,6 +54,14 @@ static NamiEmitter *namiEmitter;
54
54
  [NamiPaywallManager registerBlockingPaywallClosedHandler:^{
55
55
  [self sendBlockingPaywallClosed];
56
56
  }];
57
+
58
+ [NamiPurchaseManager registerRestorePurchasesHandlerWithRestorePurchasesStateHandler:^(enum NamiRestorePurchasesState state, NSArray<NamiPurchase *> * _Nonnull newPurchases, NSArray<NamiPurchase *> * _Nonnull oldPurchases, NSError * _Nullable error) {
59
+ [self sendRestorePurchasesStateChanged:state newPurchases:newPurchases oldPurchases:oldPurchases error:error];
60
+ }];
61
+
62
+ [NamiCustomerManager registerJourneyStateChangedHandler:^(CustomerJourneyState * _Nonnull journeyState) {
63
+ [self sendEventCustomerJourneyStateChanged:journeyState];
64
+ }];
57
65
 
58
66
  }
59
67
  namiEmitter = self;
@@ -89,7 +97,7 @@ static NamiEmitter *namiEmitter;
89
97
  }
90
98
 
91
99
  - (NSArray<NSString *> *)supportedEvents {
92
- return @[@"PurchasesChanged", @"SignInActivate", @"AppPaywallActivate", @"EntitlementsChanged", @"BlockingPaywallClosed", @"PreparePaywallFinished" ];
100
+ return @[@"PurchasesChanged", @"SignInActivate", @"AppPaywallActivate", @"EntitlementsChanged", @"BlockingPaywallClosed", @"PreparePaywallFinished", @"RestorePurchasesStateChanged", @"CustomerJourneyStateChanged" ];
93
101
  }
94
102
 
95
103
  - (NSDictionary<NSString *, NSObject *> *)constantsToExport {
@@ -186,6 +194,13 @@ bool hasNamiEmitterListeners;
186
194
  }
187
195
  }
188
196
 
197
+ - (void)sendEventCustomerJourneyStateChanged:(CustomerJourneyState *)journeyState {
198
+ if (hasNamiEmitterListeners) {
199
+ NSDictionary *sendDict = [NamiBridgeUtil customerJourneyStateDict];
200
+ [self sendEventWithName:@"CustomerJourneyStateChanged" body:sendDict];
201
+ }
202
+ }
203
+
189
204
  - (void)sendEventPurchaseMadeWithPurchases:(NSArray<NamiPurchase *>*)purchases withState:(NamiPurchaseState)purchaseState error:(NSError *)error {
190
205
  if (hasNamiEmitterListeners) {
191
206
 
@@ -236,6 +251,14 @@ bool hasNamiEmitterListeners;
236
251
  NSMutableDictionary *paywallMeta = [NSMutableDictionary dictionaryWithDictionary:paywallMetadata.namiPaywallInfoDict];
237
252
  // This part is really meant to be internally facing, scrub from dictionary
238
253
 
254
+ NSMutableDictionary *marketingContentDictionary = [NSMutableDictionary dictionaryWithDictionary:paywallMeta[@"marketing_content"]];
255
+
256
+ // Populated SmartText formatted values in dictioanry to send
257
+ marketingContentDictionary[@"body"] = [paywallMetadata body];
258
+ marketingContentDictionary[@"title"] = [paywallMetadata title];
259
+ paywallMeta[@"marketing_content"] = marketingContentDictionary;
260
+ paywallMeta[@"purchase_terms"] = [paywallMetadata purchaseTerms];
261
+
239
262
  // Strip out presention_position from all listed sku items
240
263
  NSArray *cleanedOrderdMetadata = [NamiBridgeUtil stripPresentationPositionFromOrderedMetadataForPaywallMetaDict:paywallMeta];
241
264
  [paywallMeta setObject:cleanedOrderdMetadata forKey:@"formatted_skus"];
@@ -251,12 +274,73 @@ bool hasNamiEmitterListeners;
251
274
  [paywallMeta removeObjectForKey:@"title"];
252
275
  [paywallMeta removeObjectForKey:@"style"];
253
276
 
254
- [self sendEventWithName:@"AppPaywallActivate" body:@{ @"skus": skuDicts,
277
+ [self sendEventWithName:@"AppPaywallActivate" body:@{ @"namiSkus": skuDicts,
255
278
  @"developerPaywallID": developerPaywallID,
256
279
  @"paywallMetadata": paywallMeta }];
257
280
  }
258
281
  }
259
282
 
283
+ - (NSDictionary *)buildRestorePurchasesStateChangedDict: (enum NamiRestorePurchasesState) state
284
+ newPurchases: (NSArray<NamiPurchase *> * _Nonnull) newPurchases
285
+ oldPurchases: (NSArray<NamiPurchase *> * _Nonnull) oldPurchases
286
+ error: (NSError * _Nullable) error {
287
+ NSString *errorDesc = [error localizedDescription];
288
+ NSDictionary *initialDict;
289
+ if ([errorDesc length] > 0) {
290
+ initialDict = @{@"state": [NSNumber numberWithBool:state], @"stateDesc": [self restorePurchaseStateDescriptionFromCode:state], @"error": [error localizedDescription]};
291
+ } else {
292
+ initialDict = @{@"state": [NSNumber numberWithBool:state], @"stateDesc": [self restorePurchaseStateDescriptionFromCode:state]};
293
+ }
294
+
295
+ NSMutableDictionary *retDict = [NSMutableDictionary dictionary];
296
+ [retDict addEntriesFromDictionary:initialDict];
297
+
298
+ NSMutableArray *newPurchaseDicts = [NSMutableArray array];
299
+ for ( NamiPurchase *purchaseRecord in newPurchases ) {
300
+ if ( purchaseRecord.skuID == nil ) {
301
+ }
302
+ NSDictionary *purchaseDict = [NamiBridgeUtil purchaseToPurchaseDict:purchaseRecord];
303
+ [newPurchaseDicts addObject:purchaseDict];
304
+ }
305
+
306
+ NSMutableArray *oldPurchaseDicts = [NSMutableArray array];
307
+ for ( NamiPurchase *purchaseRecord in oldPurchases ) {
308
+ if ( purchaseRecord.skuID == nil ) {
309
+ }
310
+ NSDictionary *purchaseDict = [NamiBridgeUtil purchaseToPurchaseDict:purchaseRecord];
311
+ [oldPurchaseDicts addObject:purchaseDict];
312
+ }
313
+
314
+ retDict[@"newPurchases"] = newPurchaseDicts;
315
+ retDict[@"oldPurchases"] = oldPurchaseDicts;
316
+
317
+ NSLog(@"NamiBridge: Info: RestorePurchases state change: %@", retDict);
318
+
319
+ return retDict;
320
+ }
321
+
322
+ - (void)sendRestorePurchasesStateChanged: (enum NamiRestorePurchasesState) state
323
+ newPurchases: (NSArray<NamiPurchase *> * _Nonnull) newPurchases
324
+ oldPurchases: (NSArray<NamiPurchase *> * _Nonnull) oldPurchases
325
+ error: (NSError * _Nullable) error {
326
+ NSDictionary * retDict = [self buildRestorePurchasesStateChangedDict:state newPurchases:newPurchases oldPurchases:oldPurchases error:error];
327
+ [self sendEventWithName:@"RestorePurchasesStateChanged" body:retDict];
328
+ }
329
+
330
+ - (NSString *) restorePurchaseStateDescriptionFromCode:(NamiRestorePurchasesState)stateCode {
331
+ switch (stateCode) {
332
+ case NamiRestorePurchasesStateStarted:
333
+ return @"started";
334
+ break;
335
+ case NamiRestorePurchasesStateFinished:
336
+ return @"finished";
337
+ break;
338
+ case NamiRestorePurchasesStateError:
339
+ return @"error";
340
+ break;
341
+ }
342
+ }
343
+
260
344
  - (void) sendBlockingPaywallClosed {
261
345
  // Let system know a blocking paywall has been closed, in case they want to react specifically.
262
346
  if (hasNamiEmitterListeners) {
@@ -95,7 +95,7 @@ RCT_EXPORT_METHOD(fetchCustomPaywallMetaForDeveloperID:(NSString *)developerPayw
95
95
  paywallMeta[@"styleData"] = paywallStylingDict;
96
96
 
97
97
 
98
- NSArray *wrapperArray = @[@{ @"products": productDicts,
98
+ NSArray *wrapperArray = @[@{ @"namiSkus": productDicts,
99
99
  @"developerPaywallID": developerPaywallID,
100
100
  @"paywallMetadata": paywallMeta }];
101
101
  completion(wrapperArray);
@@ -125,6 +125,22 @@ RCT_EXPORT_METHOD(preparePaywallForDisplayByDeveloperPaywallId:(NSString *)devel
125
125
  }];
126
126
  }
127
127
 
128
+
129
+ RCT_EXPORT_METHOD(processSmartTextForProducts:(NSString *)smartText skuIDs:(NSArray<NSString *> *)skuIDs completion:(RCTResponseSenderBlock)completion)
130
+ {
131
+ [NamiPurchaseManager skusForSKUIDsWithSkuIDs:skuIDs productHandler:^(BOOL success, NSArray<NamiSKU *> * _Nullable skus, NSArray<NSString *> * _Nullable invalidSkuIDs, NSError * _Nullable error) {
132
+ if (skus != NULL) {
133
+ // Found some of the skus they were looking for, process text
134
+ NSString *processedText = [NamiPaywallManager processSmartTextWithText:smartText dataStores:skus];
135
+ completion(@[processedText]);
136
+ } else {
137
+ // No products found so cannot process smart text, just send back.
138
+ completion(@[smartText]);
139
+ }
140
+ }];
141
+ }
142
+
143
+
128
144
  @end
129
145
 
130
146
  @implementation NamiPaywallManagerBridge
@@ -15,6 +15,17 @@
15
15
 
16
16
  #import "React/RCTViewManager.h"
17
17
 
18
+ @interface NamiEmitter : RCTEventEmitter
19
+ - (void)sendRestorePurchasesStateChanged: (enum NamiRestorePurchasesState) state
20
+ newPurchases: (NSArray<NamiPurchase *> * _Nonnull) newPurchases
21
+ oldPurchases: (NSArray<NamiPurchase *> * _Nonnull) oldPurchases
22
+ error: (NSError * _Nullable) error;
23
+ - (NSDictionary *)buildRestorePurchasesStateChangedDict: (enum NamiRestorePurchasesState) state
24
+ newPurchases: (NSArray<NamiPurchase *> * _Nonnull) newPurchases
25
+ oldPurchases: (NSArray<NamiPurchase *> * _Nonnull) oldPurchases
26
+ error: (NSError * _Nullable) error;
27
+ + (NamiEmitter *) reactInstance;
28
+ @end
18
29
 
19
30
  @interface NamiPurchaseManagerBridge : NSObject <RCTBridgeModule>
20
31
  @end
@@ -48,23 +59,26 @@ RCT_EXPORT_METHOD(isSKUIDPurchased:(nonnull NSString*)skuID completion:(RCTRespo
48
59
  completion(@[[NSNumber numberWithBool:active]]);
49
60
  }
50
61
 
51
- RCT_EXPORT_METHOD(restorePurchases:(RCTResponseSenderBlock)completion)
62
+ RCT_EXPORT_METHOD(restorePurchasesWithCompletionHandler:(RCTResponseSenderBlock)completion)
52
63
  {
53
- NSLog(@"NamiBridge: Info: Calling RestorePurchases");
54
- [NamiPurchaseManager restorePurchasesWithHandler:^(BOOL success, NSError * _Nullable error) {
55
- NSString *errorDesc = [error localizedDescription];
56
- NSDictionary *retDict;
57
- if ([errorDesc length] > 0) {
58
- retDict = @{@"success": [NSNumber numberWithBool:success], @"error": [error localizedDescription]};
59
- } else {
60
- retDict = @{@"success": [NSNumber numberWithBool:success]};
61
- }
62
-
63
- NSLog(@"NamiBridge: Info: RestorePurchases Returned %@", retDict);
64
+ NSLog(@"NamiBridge: Info: Calling RestorePurchasesWithCompletionHandler");
65
+
66
+ [NamiPurchaseManager restorePurchasesWithStatehandler:^(enum NamiRestorePurchasesState state, NSArray<NamiPurchase *> * _Nonnull newPurchases, NSArray<NamiPurchase *> * _Nonnull oldPurchases, NSError * _Nullable error) {
67
+ NSDictionary *retDict = [[NamiEmitter reactInstance] buildRestorePurchasesStateChangedDict:state newPurchases:newPurchases oldPurchases:oldPurchases error:error];
64
68
  completion(@[retDict]);
65
69
  }];
66
70
  }
67
71
 
72
+ RCT_EXPORT_METHOD(restorePurchases)
73
+ {
74
+ NSLog(@"NamiBridge: Info: Calling RestorePurchases");
75
+
76
+ [NamiPurchaseManager restorePurchasesWithStatehandler:^(enum NamiRestorePurchasesState state, NSArray<NamiPurchase *> * _Nonnull newPurchases, NSArray<NamiPurchase *> * _Nonnull oldPurchases, NSError * _Nullable error) {
77
+ [[NamiEmitter reactInstance] sendRestorePurchasesStateChanged:state newPurchases:newPurchases oldPurchases:oldPurchases error:error];
78
+ }];
79
+ }
80
+
81
+ /// Determines if any one of the passed in SKUID's have been purchased.
68
82
  RCT_EXPORT_METHOD(anySKUIDPurchased:(nonnull NSArray*)skuIDs completion:(RCTResponseSenderBlock)completion)
69
83
  {
70
84
  BOOL active = false;
@@ -78,6 +92,31 @@ RCT_EXPORT_METHOD(anySKUIDPurchased:(nonnull NSArray*)skuIDs completion:(RCTResp
78
92
  completion(@[[NSNumber numberWithBool:active]]);
79
93
  }
80
94
 
95
+ /// For consumable purchases, removes the SKU from Nami so a product may be purchased again.
96
+ RCT_EXPORT_METHOD(consumePurchasedSKU:(nonnull NSString*)skuID)
97
+ {
98
+ [NamiPurchaseManager consumePurchasedSKUWithSkuID:skuID];
99
+ }
100
+
101
+ /// For consumable purchases, removes the SKU from Nami so a product may be purchased again.
102
+ RCT_EXPORT_METHOD(presentCodeRedemptionSheet)
103
+ {
104
+ if (@available(iOS 14.0, *)) {
105
+ [NamiPurchaseManager presentCodeRedemptionSheet];
106
+ } else {
107
+ NSLog(@"NamiBridge: Warning: presentCodeRedemptionSheet only present in iOS14 and higher");
108
+ }
109
+ }
110
+
111
+ RCT_EXPORT_METHOD(canPresentCodeRedemptionSheet:(RCTResponseSenderBlock)completion)
112
+ {
113
+ if (@available(iOS 14.0, *)) {
114
+ completion(@[[NSNumber numberWithBool:true]]);
115
+ } else {
116
+ completion(@[[NSNumber numberWithBool:false]]);
117
+ }
118
+ }
119
+
81
120
  /// This method does the purchase work, and can optionally be fed a paywall metadata object to pass along to the purchase flow.
82
121
  - (void) doSKUPurchaseWithSKUID:(nonnull NSString*)skuID namiPaywall:(NamiPaywall * _Nullable)namiPaywall completion:(RCTResponseSenderBlock)completion {
83
122
  [NamiPurchaseManager skusForSKUIDsWithSkuIDs:@[skuID] productHandler:^(BOOL success, NSArray<NamiSKU *> * _Nullable products, NSArray<NSString *> * _Nullable invalidProducts, NSError * _Nullable error) {
package/ios/Podfile CHANGED
@@ -1,48 +1,32 @@
1
- platform :ios, '11.0'
1
+
2
+ require_relative '../node_modules/react-native/scripts/react_native_pods'
2
3
  require_relative '../node_modules/@react-native-community/cli-platform-ios/native_modules'
3
4
 
5
+ platform :ios, '11.2'
6
+
4
7
  source 'https://cdn.cocoapods.org/'
5
8
 
6
9
  target 'RNNami' do
7
- # Pods for RNNami
8
- pod 'Nami', '2.6.0'
9
- pod 'FBLazyVector', :path => "../node_modules/react-native/Libraries/FBLazyVector"
10
- pod 'FBReactNativeSpec', :path => "../node_modules/react-native/Libraries/FBReactNativeSpec"
11
- pod 'RCTRequired', :path => "../node_modules/react-native/Libraries/RCTRequired"
12
- pod 'RCTTypeSafety', :path => "../node_modules/react-native/Libraries/TypeSafety"
13
- pod 'React', :path => '../node_modules/react-native/'
14
- pod 'React-Core', :path => '../node_modules/react-native/'
15
- pod 'React-CoreModules', :path => '../node_modules/react-native/React/CoreModules'
16
- pod 'React-Core/DevSupport', :path => '../node_modules/react-native/'
17
- pod 'React-RCTActionSheet', :path => '../node_modules/react-native/Libraries/ActionSheetIOS'
18
- pod 'React-RCTAnimation', :path => '../node_modules/react-native/Libraries/NativeAnimation'
19
- pod 'React-RCTBlob', :path => '../node_modules/react-native/Libraries/Blob'
20
- pod 'React-RCTImage', :path => '../node_modules/react-native/Libraries/Image'
21
- pod 'React-RCTLinking', :path => '../node_modules/react-native/Libraries/LinkingIOS'
22
- pod 'React-RCTNetwork', :path => '../node_modules/react-native/Libraries/Network'
23
- pod 'React-RCTSettings', :path => '../node_modules/react-native/Libraries/Settings'
24
- pod 'React-RCTText', :path => '../node_modules/react-native/Libraries/Text'
25
- pod 'React-RCTVibration', :path => '../node_modules/react-native/Libraries/Vibration'
26
- pod 'React-Core/RCTWebSocket', :path => '../node_modules/react-native/'
27
-
28
- pod 'React-cxxreact', :path => '../node_modules/react-native/ReactCommon/cxxreact'
29
- pod 'React-jsi', :path => '../node_modules/react-native/ReactCommon/jsi'
30
- pod 'React-jsiexecutor', :path => '../node_modules/react-native/ReactCommon/jsiexecutor'
31
- pod 'React-jsinspector', :path => '../node_modules/react-native/ReactCommon/jsinspector'
32
- pod 'ReactCommon/jscallinvoker', :path => "../node_modules/react-native/ReactCommon"
33
- pod 'ReactCommon/turbomodule/core', :path => "../node_modules/react-native/ReactCommon"
34
- pod 'Yoga', :path => '../node_modules/react-native/ReactCommon/yoga'
35
-
36
- pod 'DoubleConversion', :podspec => '../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec'
37
- pod 'glog', :podspec => '../node_modules/react-native/third-party-podspecs/glog.podspec'
38
- pod 'Folly', :podspec => '../node_modules/react-native/third-party-podspecs/Folly.podspec'
39
-
40
- use_native_modules!
10
+ config = use_native_modules!
11
+ pod 'Nami', '2.9.4'
41
12
 
42
- end
13
+ use_react_native!(
14
+ :path => config[:reactNativePath],
15
+ # to enable hermes on iOS, change `false` to `true` and then install pods
16
+ :hermes_enabled => false
17
+ )
18
+
19
+ # Enables Flipper.
20
+ #
21
+ # Note that if you have use_frameworks! enabled, Flipper will not work and
22
+ # you should disable the next line.
23
+ use_flipper!()
43
24
 
44
- post_install do |installer|
45
- puts("Attempting to add Nami.xcframework reference to react-native-nami-sdk project.")
25
+ post_install do |installer|
26
+ react_native_post_install(installer)
27
+ #__apply_Xcode_12_5_M1_post_install_workaround(installer)
28
+
29
+ puts("Attempting to add Nami.xcframework reference to react-native-nami-sdk project.")
46
30
  installer.pods_project.targets.each do |target|
47
31
  if target.name == "Pods-RNNami"
48
32
  puts("Found Pods-RNNami target.")
@@ -60,4 +44,28 @@ post_install do |installer|
60
44
  end
61
45
  end
62
46
  end
47
+ ## Fix for XCode 12+, hotpatch of react files should be a great idea
48
+ find_and_replace("../node_modules/react-native/React/CxxBridge/RCTCxxBridge.mm",
49
+ "_initializeModules:(NSArray<id<RCTBridgeModule>> *)modules", "_initializeModules:(NSArray<Class> *)modules")
50
+ find_and_replace("../node_modules/react-native/ReactCommon/turbomodule/core/platform/ios/RCTTurboModuleManager.mm",
51
+ "RCTBridgeModuleNameForClass(module))", "RCTBridgeModuleNameForClass(Class(module)))")
52
+
53
+
54
+ end
55
+ end
56
+
57
+
58
+
59
+
60
+ def find_and_replace(dir, findstr, replacestr)
61
+ Dir[dir].each do |name|
62
+ text = File.read(name)
63
+ replace = text.gsub(findstr,replacestr)
64
+ if text != replace
65
+ puts "Fix: " + name
66
+ File.open(name, "w") { |file| file.puts replace }
67
+ STDOUT.flush
68
+ end
69
+ end
70
+ Dir[dir + '*/'].each(&method(:find_and_replace))
63
71
  end
@@ -230,6 +230,7 @@
230
230
  COPY_PHASE_STRIP = NO;
231
231
  ENABLE_STRICT_OBJC_MSGSEND = YES;
232
232
  ENABLE_TESTABILITY = YES;
233
+ "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = "";
233
234
  GCC_C_LANGUAGE_STANDARD = gnu99;
234
235
  GCC_DYNAMIC_NO_PIC = NO;
235
236
  GCC_NO_COMMON_BLOCKS = YES;
@@ -245,7 +246,7 @@
245
246
  GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
246
247
  GCC_WARN_UNUSED_FUNCTION = YES;
247
248
  GCC_WARN_UNUSED_VARIABLE = YES;
248
- IPHONEOS_DEPLOYMENT_TARGET = 10.3;
249
+ IPHONEOS_DEPLOYMENT_TARGET = 11.2;
249
250
  MTL_ENABLE_DEBUG_INFO = YES;
250
251
  ONLY_ACTIVE_ARCH = YES;
251
252
  SDKROOT = iphoneos;
@@ -274,6 +275,7 @@
274
275
  COPY_PHASE_STRIP = YES;
275
276
  ENABLE_NS_ASSERTIONS = NO;
276
277
  ENABLE_STRICT_OBJC_MSGSEND = YES;
278
+ "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = " ";
277
279
  GCC_C_LANGUAGE_STANDARD = gnu99;
278
280
  GCC_NO_COMMON_BLOCKS = YES;
279
281
  GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
@@ -282,7 +284,7 @@
282
284
  GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
283
285
  GCC_WARN_UNUSED_FUNCTION = YES;
284
286
  GCC_WARN_UNUSED_VARIABLE = YES;
285
- IPHONEOS_DEPLOYMENT_TARGET = 10.3;
287
+ IPHONEOS_DEPLOYMENT_TARGET = 11.2;
286
288
  MTL_ENABLE_DEBUG_INFO = NO;
287
289
  SDKROOT = iphoneos;
288
290
  VALIDATE_PRODUCT = YES;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-nami-sdk",
3
- "version": "1.1.0",
3
+ "version": "2.0.1",
4
4
  "description": "React Native Module for Nami - The Smartest Way to Sell Subscriptions.",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -39,12 +39,13 @@
39
39
  ],
40
40
  "license": "SEE LICENSE FILE",
41
41
  "peerDependencies": {
42
- "react": "^16.8.1",
43
- "react-native": ">=0.60.0-rc.0 <1.0.x"
42
+ "react": "^17.0.2",
43
+ "react-native": "^0.65.2"
44
44
  },
45
45
  "devDependencies": {
46
- "react": "^16.9.0",
47
- "react-native": "^0.61.5"
46
+ "react": "^17.0.2",
47
+ "react-native": "^0.65.2",
48
+ "react-native-codegen": "^0.0.12"
48
49
  },
49
50
  "repository": {
50
51
  "type": "git",
@@ -13,12 +13,12 @@ Pod::Spec.new do |s|
13
13
  s.homepage = package['homepage']
14
14
  s.license = package['license']
15
15
 
16
- s.platform = :ios, "11.0"
16
+ s.platform = :ios, "11.2"
17
17
  s.source = { :git => "https://github.com/namiml/react-native-nami-sdk.git", :tag => "#{s.version}" }
18
18
  s.source_files = "ios/**/*.{h,m}"
19
19
  s.requires_arc = true
20
20
 
21
- s.dependency 'Nami', '2.6.0'
21
+ s.dependency 'Nami', '2.9.4'
22
22
  s.dependency 'React'
23
23
 
24
24
  end