react-native-nami-sdk 1.0.1 → 2.0.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/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.0.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
  }
@@ -99,9 +100,6 @@ class NamiPaywallManagerBridgeModule(reactContext: ReactApplicationContext) :
99
100
 
100
101
  @ReactMethod
101
102
  fun canRaisePaywall(successCallback: Callback) {
102
- // BOOL canRaise = [[NamiPaywallManager shared] canRaisePaywall];
103
- // completion(@[[NSNumber numberWithBool:canRaise]]);
104
-
105
103
  reactApplicationContext.runOnUiQueueThread {
106
104
  val canRaiseResult = NamiPaywallManager.canRaisePaywall()
107
105
  successCallback.invoke(canRaiseResult)
@@ -124,4 +122,80 @@ class NamiPaywallManagerBridgeModule(reactContext: ReactApplicationContext) :
124
122
  fun paywallImpression(developerPaywallID: String) {
125
123
  // TODO: Android SDK paywall impression call.
126
124
  }
125
+
126
+ @ReactMethod
127
+ fun preparePaywallForDisplay(backgroundImageRequired: Boolean, imageFetchTimeout: Double) {
128
+ val imageFetchTimeoutConvertedToLong: Long = imageFetchTimeout.toLong()
129
+ reactApplicationContext.runOnUiQueueThread {
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
+ }
143
+ }
144
+ }
145
+ }
146
+
147
+ @ReactMethod
148
+ fun preparePaywallForDisplayByDeveloperPaywallId(
149
+ developerPaywallID: String,
150
+ backgroundImageRequired: Boolean,
151
+ imageFetchTimeout: Double
152
+ ) {
153
+ val imageFetchTimeoutConvertedToLong: Long = imageFetchTimeout.toLong()
154
+ reactApplicationContext.runOnUiQueueThread {
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
+ }
168
+ }
169
+ }
170
+ }
171
+
172
+ private fun emitPreparePaywallFinished(
173
+ success: Boolean,
174
+ developerPaywallID: String?,
175
+ error: com.namiml.paywall.PreparePaywallError?
176
+ ) {
177
+ val prepareContentMap = Arguments.createMap()
178
+ prepareContentMap.putBoolean("success", success)
179
+
180
+ if (developerPaywallID != null) {
181
+ prepareContentMap.putString("developerPaywallID", developerPaywallID)
182
+ }
183
+
184
+ if (error != null) {
185
+ prepareContentMap.putInt("errorCode", error.ordinal)
186
+ prepareContentMap.putString("errorMessage", error.toString())
187
+ }
188
+
189
+ Log.i(
190
+ LOG_TAG,
191
+ "Emitting preparePaywallForDisplay finished with result " + success + "error: " + error.toString()
192
+ )
193
+ try {
194
+ reactApplicationContext
195
+ .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
196
+ .emit("PreparePaywallFinished", prepareContentMap)
197
+ } catch (e: Exception) {
198
+ Log.e(LOG_TAG, "Caught Exception: " + e.message)
199
+ }
200
+ }
127
201
  }
@@ -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
@@ -23,6 +23,8 @@ RCT_EXTERN_METHOD(allPurchasedProducts)
23
23
  RCT_EXTERN_METHOD(getPurchasedProducts: (RCTResponseSenderBlock)callback)
24
24
  @end
25
25
 
26
+ static NamiEmitter *namiEmitter;
27
+
26
28
  @implementation NamiEmitter : RCTEventEmitter
27
29
 
28
30
  - (instancetype)init
@@ -52,11 +54,29 @@ RCT_EXTERN_METHOD(getPurchasedProducts: (RCTResponseSenderBlock)callback)
52
54
  [NamiPaywallManager registerBlockingPaywallClosedHandler:^{
53
55
  [self sendBlockingPaywallClosed];
54
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
+ }];
55
65
 
56
66
  }
67
+ namiEmitter = self;
57
68
  return self;
58
69
  }
59
70
 
71
+ - (void)dealloc {
72
+ namiEmitter = nil;
73
+ }
74
+
75
+
76
+ + (NamiEmitter *) reactInstance {
77
+ return namiEmitter;
78
+ }
79
+
60
80
  - (void) getPurchasedProducts : (RCTResponseSenderBlock) callback {
61
81
  NSArray *allProducts = [self allPurchasedProducts];
62
82
  callback(allProducts);
@@ -77,7 +97,7 @@ RCT_EXTERN_METHOD(getPurchasedProducts: (RCTResponseSenderBlock)callback)
77
97
  }
78
98
 
79
99
  - (NSArray<NSString *> *)supportedEvents {
80
- return @[@"PurchasesChanged", @"SignInActivate", @"AppPaywallActivate", @"EntitlementsChanged", @"BlockingPaywallClosed" ];
100
+ return @[@"PurchasesChanged", @"SignInActivate", @"AppPaywallActivate", @"EntitlementsChanged", @"BlockingPaywallClosed", @"PreparePaywallFinished", @"RestorePurchasesStateChanged", @"CustomerJourneyStateChanged" ];
81
101
  }
82
102
 
83
103
  - (NSDictionary<NSString *, NSObject *> *)constantsToExport {
@@ -150,6 +170,37 @@ bool hasNamiEmitterListeners;
150
170
  }
151
171
  }
152
172
 
173
+ - (void)sendEventPreparePaywallForDisplayFinishedWithResult:(BOOL)success developerPaywallID: (NSString * _Nullable) developerPaywallID error:(NSError * _Nullable) error {
174
+ if (hasNamiEmitterListeners) {
175
+
176
+ NSMutableDictionary *sendDict = [NSMutableDictionary dictionaryWithDictionary: @{ @"success": @(success) }];
177
+
178
+ if (developerPaywallID != nil) {
179
+ [sendDict addEntriesFromDictionary:@{
180
+ @"developerPaywallID": developerPaywallID
181
+ }];
182
+ }
183
+
184
+ if (error != nil) {
185
+ [sendDict addEntriesFromDictionary:@{
186
+ @"errorCode": @(error.code),
187
+ @"errorMessage": [error localizedDescription]
188
+ }
189
+ ];
190
+ }
191
+
192
+ NSLog(@"NamiBridge: Info: attempting to send result of preparePaywallForDisplay with result dictionary: %@", sendDict);
193
+ [self sendEventWithName:@"PreparePaywallFinished" body:sendDict];
194
+ }
195
+ }
196
+
197
+ - (void)sendEventCustomerJourneyStateChanged:(CustomerJourneyState *)journeyState {
198
+ if (hasNamiEmitterListeners) {
199
+ NSDictionary *sendDict = [NamiBridgeUtil customerJourneyStateDict];
200
+ [self sendEventWithName:@"CustomerJourneyStateChanged" body:sendDict];
201
+ }
202
+ }
203
+
153
204
  - (void)sendEventPurchaseMadeWithPurchases:(NSArray<NamiPurchase *>*)purchases withState:(NamiPurchaseState)purchaseState error:(NSError *)error {
154
205
  if (hasNamiEmitterListeners) {
155
206
 
@@ -200,6 +251,14 @@ bool hasNamiEmitterListeners;
200
251
  NSMutableDictionary *paywallMeta = [NSMutableDictionary dictionaryWithDictionary:paywallMetadata.namiPaywallInfoDict];
201
252
  // This part is really meant to be internally facing, scrub from dictionary
202
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
+
203
262
  // Strip out presention_position from all listed sku items
204
263
  NSArray *cleanedOrderdMetadata = [NamiBridgeUtil stripPresentationPositionFromOrderedMetadataForPaywallMetaDict:paywallMeta];
205
264
  [paywallMeta setObject:cleanedOrderdMetadata forKey:@"formatted_skus"];
@@ -215,12 +274,73 @@ bool hasNamiEmitterListeners;
215
274
  [paywallMeta removeObjectForKey:@"title"];
216
275
  [paywallMeta removeObjectForKey:@"style"];
217
276
 
218
- [self sendEventWithName:@"AppPaywallActivate" body:@{ @"skus": skuDicts,
277
+ [self sendEventWithName:@"AppPaywallActivate" body:@{ @"namiSkus": skuDicts,
219
278
  @"developerPaywallID": developerPaywallID,
220
279
  @"paywallMetadata": paywallMeta }];
221
280
  }
222
281
  }
223
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
+
224
344
  - (void) sendBlockingPaywallClosed {
225
345
  // Let system know a blocking paywall has been closed, in case they want to react specifically.
226
346
  if (hasNamiEmitterListeners) {
@@ -15,7 +15,10 @@
15
15
 
16
16
  #import "React/RCTViewManager.h"
17
17
 
18
-
18
+ @interface NamiEmitter : RCTEventEmitter
19
+ - (void)sendEventPreparePaywallForDisplayFinishedWithResult:(BOOL)success developerPaywallID: (NSString * _Nullable) developerPaywallID error:(NSError * _Nullable) error;
20
+ + (NamiEmitter *) reactInstance;
21
+ @end
19
22
 
20
23
  @interface NamiPaywallManagerBridge : NSObject <RCTBridgeModule>
21
24
  @property (atomic) BOOL blockPaywallRaise;
@@ -58,6 +61,7 @@ RCT_EXPORT_METHOD(canRaisePaywall:(RCTResponseSenderBlock)completion)
58
61
  completion(@[[NSNumber numberWithBool:canRaise]]);
59
62
  }
60
63
 
64
+
61
65
  RCT_EXPORT_METHOD(presentNamiPaywall:(NSArray *)skuIDs metapaywallDefinition:(NSDictionary *)paywallDict)
62
66
  {
63
67
  NSString *paywallDeveloperID = paywallDict[@"developerPaywallID"];
@@ -91,7 +95,7 @@ RCT_EXPORT_METHOD(fetchCustomPaywallMetaForDeveloperID:(NSString *)developerPayw
91
95
  paywallMeta[@"styleData"] = paywallStylingDict;
92
96
 
93
97
 
94
- NSArray *wrapperArray = @[@{ @"products": productDicts,
98
+ NSArray *wrapperArray = @[@{ @"namiSkus": productDicts,
95
99
  @"developerPaywallID": developerPaywallID,
96
100
  @"paywallMetadata": paywallMeta }];
97
101
  completion(wrapperArray);
@@ -104,6 +108,38 @@ RCT_EXPORT_METHOD(paywallImpression:(NSString *)developerPaywallID)
104
108
  [NamiPaywallManager paywallImpressionWithDeveloperID:developerPaywallID];
105
109
  }
106
110
 
111
+ RCT_EXPORT_METHOD( preparePaywallForDisplay:(BOOL)backgroundImageRequired
112
+ imageFetchTimeout:(double)imageFetchTimeout )
113
+ {
114
+ [NamiPaywallManager preparePaywallForDisplayWithBackgroundImageRequired:backgroundImageRequired imageFetchTimeout:imageFetchTimeout prepareHandler:^(BOOL success, NSError * _Nullable error) {
115
+ [[NamiEmitter reactInstance] sendEventPreparePaywallForDisplayFinishedWithResult:success developerPaywallID:nil error:error];
116
+ }];
117
+ }
118
+
119
+ RCT_EXPORT_METHOD(preparePaywallForDisplayByDeveloperPaywallId:(NSString *)developerPaywallID
120
+ backgroundImageRequired: (BOOL)backgroundImageRequired
121
+ imageFetchTimeout:(double)imageFetchTimeout )
122
+ {
123
+ [NamiPaywallManager preparePaywallForDisplayWithDeveloperPaywallID:developerPaywallID backgroundImageRequired:backgroundImageRequired imageFetchTimeout:imageFetchTimeout prepareHandler:^(BOOL success, NSError * _Nullable error) {
124
+ [[NamiEmitter reactInstance] sendEventPreparePaywallForDisplayFinishedWithResult:success developerPaywallID:developerPaywallID error:error];
125
+ }];
126
+ }
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
+
107
143
 
108
144
  @end
109
145
 
@@ -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.5.2'
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.3'
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.0.1",
3
+ "version": "2.0.0",
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.5.2'
21
+ s.dependency 'Nami', '2.9.3'
22
22
  s.dependency 'React'
23
23
 
24
24
  end