@tinkerlandai/indiana-react-native 0.9.5 → 0.10.2

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.
Files changed (31) hide show
  1. package/IndianaReactNative.podspec +1 -1
  2. package/README.md +6 -6
  3. package/dist/index.cjs +2 -2
  4. package/dist/index.d.cts +37283 -3205
  5. package/dist/index.d.ts +37283 -3205
  6. package/dist/index.js +2 -2
  7. package/native-sdk/android/assets/indiana/index.html +36 -36
  8. package/native-sdk/android/java/ai/tinkerland/indiana/ContentView.kt +1 -1
  9. package/native-sdk/android/java/ai/tinkerland/indiana/CustomerResponseSemantics.kt +7 -3
  10. package/native-sdk/android/java/ai/tinkerland/indiana/CustomerSemantics.kt +3 -3
  11. package/native-sdk/android/java/ai/tinkerland/indiana/FeatureClient.kt +1 -1
  12. package/native-sdk/android/java/ai/tinkerland/indiana/GeneratedProtocol.kt +1 -1
  13. package/native-sdk/android/java/ai/tinkerland/indiana/HTTPTransport.kt +1 -1
  14. package/native-sdk/android/java/ai/tinkerland/indiana/IndianaClient.kt +2 -2
  15. package/native-sdk/android/java/ai/tinkerland/indiana/MerchantCommerceSemantics.kt +38 -0
  16. package/native-sdk/android/java/ai/tinkerland/indiana/NativeStoreOffer.kt +1 -1
  17. package/native-sdk/android/java/ai/tinkerland/indiana/SDKVersion.kt +1 -1
  18. package/native-sdk/android/resources/funnel-wire-schemas.json +1 -1
  19. package/native-sdk/ios/ContentView.swift +1 -1
  20. package/native-sdk/ios/CustomerResponseSemantics.swift +7 -3
  21. package/native-sdk/ios/CustomerSemantics.swift +3 -3
  22. package/native-sdk/ios/FeatureClient.swift +1 -1
  23. package/native-sdk/ios/GeneratedProtocol.swift +1 -1
  24. package/native-sdk/ios/HTTPTransport.swift +1 -1
  25. package/native-sdk/ios/IndianaClient.swift +2 -2
  26. package/native-sdk/ios/MerchantCommerceSemantics.swift +51 -0
  27. package/native-sdk/ios/NativeStoreOffer.swift +1 -1
  28. package/native-sdk/ios/SDKVersion.swift +1 -1
  29. package/native-sdk/ios/WebView/index.html +36 -36
  30. package/native-sdk/ios/funnel-wire-schemas.json +1 -1
  31. package/package.json +2 -2
@@ -14,7 +14,7 @@ class IndianaContentView(activity: Activity, val session: IndianaContentSession)
14
14
  var onDismiss: (() -> Unit)? = null
15
15
  var onError: ((Exception) -> Unit)? = null
16
16
  private val work = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
17
- private val host = IndianaBundledWebView(activity, "Indiana content", work, ::dispatch) { error -> onError?.invoke(error) }
17
+ private val host = IndianaBundledWebView(activity, "Longplay content", work, ::dispatch) { error -> onError?.invoke(error) }
18
18
  private var stopped = false
19
19
  init {
20
20
  addView(host.view, LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT))
@@ -61,6 +61,7 @@ internal object IndianaCustomerResponseSemantics {
61
61
  }; visit(value)
62
62
  }
63
63
  fun objectValue(v: JSONObject, fields: Set<String>, path: List<String>) {
64
+ IndianaMerchantCommerceSemantics.validate(v, fields)
64
65
  fun has(vararg keys: String) = fields.containsAll(keys.toList())
65
66
  if (has("productKind", "seatBundle", "applePromotionalOffer", "replacement", "offerKind")) {
66
67
  val apple = v.getString("provider") == "apple"; val member = v.getString("productKind") == "membership"; val kind = v.getString("offerKind"); val replacement = o(v.opt("replacement")); val proof = o(v.opt("applePromotionalOffer"))
@@ -101,7 +102,7 @@ internal object IndianaCustomerResponseSemantics {
101
102
  if (has("operationId", "lines", "discount", "charge", "nativeOffer", "execution")) attachQuote(v)
102
103
  if (has("executor", "checkoutUrl", "execution")) {
103
104
  require(v.isNull("execution") || v.getString("executor") == "merchant")
104
- if (has("quoteFinalized", "orderId")) require(v.isNull("checkoutUrl") || v.getString("executor") == "indiana")
105
+ if (has("quoteFinalized", "orderId")) require(v.isNull("checkoutUrl") || v.getString("executor") == "indiana" || o(v.opt("merchantExecution")).optString("type") == "purchase")
105
106
  if (!v.isNull("checkoutUrl")) require(URI(v.getString("checkoutUrl")).scheme == "https")
106
107
  }
107
108
  if (has("action", "executor", "update", "cancel", "schedule", "release")) {
@@ -157,7 +158,10 @@ internal object IndianaCustomerResponseSemantics {
157
158
  }
158
159
  private fun attachQuote(v: JSONObject) {
159
160
  val lines = a(v.opt("lines")); val trial = o(lines.firstOrNull()?.opt("trial")); val execution = o(v.opt("execution")); val discount = o(v.opt("discount")); val provider = v.getString("provider")
160
- if (provider == "stripe") {
161
+ if (provider == "merchant") {
162
+ require(execution.getString("provider") == "merchant" && execution.getString("operationId") == v.getString("operationId") && n(execution.opt("catalogAmountMinor")).compareTo(n(v.opt("catalogAmountMinor"))) == 0 && execution.getString("currency") == v.getString("currency") && time(execution.opt("expiresAt")) <= time(v.opt("expiresAt")))
163
+ require(execution.isNull("trialPeriodDays") == (trial.length() == 0) && (trial.length() == 0 || n(execution.opt("trialPeriodDays")).compareTo(n(trial.opt("days"))) == 0) && o(execution.opt("discount")).optString("grantId") == discount.optString("offerId") && !v.has("nativeOffer"))
164
+ } else if (provider == "stripe") {
161
165
  if (trial.has("cardRequired") && !trial.getBoolean("cardRequired") && !o(execution.opt("invoiceMode")).optBoolean("enabled")) require(execution.length() == 0 && discount.length() == 0)
162
166
  else {
163
167
  require(execution.length() > 0 && o(execution.opt("metadata")).optString("grooveCheckoutId") == v.getString("operationId") && time(execution.opt("expiresAt")) <= time(v.opt("expiresAt")))
@@ -169,7 +173,7 @@ internal object IndianaCustomerResponseSemantics {
169
173
  }
170
174
  require(!v.has("nativeOffer"))
171
175
  } else { val selection = o(v.opt("nativeOffer")); require(execution.length() == 0 && selection.length() > 0 && lines.size == 1 && n(selection.opt("discountMinor")) <= n(v.opt("catalogAmountMinor")) && selection.isNull("trialDays") == (trial.length() == 0) && (trial.length() == 0 || n(selection.opt("trialDays")).compareTo(n(trial.opt("days"))) == 0) && (discount.length() == 0 || n(selection.opt("discountMinor")).compareTo(n(discount.opt("amountMinor"))) == 0)) }
172
- if (discount.length() > 0) { val allocations = a(discount.opt("allocations")); require(v.isNull("billingSubscriptionId") && (provider == "stripe" || o(discount.opt("duration")).getString("type") == "once") && n(discount.opt("amountMinor")) <= n(v.opt("catalogAmountMinor")) && allocations.size == lines.size); for ((row, line) in allocations.zip(lines)) require(row.getString("productVersionId") == line.getString("productVersionId") && n(row.opt("amountMinor")) <= n(line.opt("catalogAmountMinor"))); require(allocations.fold(BigDecimal.ZERO) { total, row -> total + n(row.opt("amountMinor")) }.compareTo(n(discount.opt("amountMinor"))) == 0) }
176
+ if (discount.length() > 0) { val allocations = a(discount.opt("allocations")); require(v.isNull("billingSubscriptionId") && (provider in listOf("stripe", "merchant") || o(discount.opt("duration")).getString("type") == "once") && n(discount.opt("amountMinor")) <= n(v.opt("catalogAmountMinor")) && allocations.size == lines.size); for ((row, line) in allocations.zip(lines)) require(row.getString("productVersionId") == line.getString("productVersionId") && n(row.opt("amountMinor")) <= n(line.opt("catalogAmountMinor"))); require(allocations.fold(BigDecimal.ZERO) { total, row -> total + n(row.opt("amountMinor")) }.compareTo(n(discount.opt("amountMinor"))) == 0) }
173
177
  if (!v.isNull("charge")) { val charge = v.getJSONObject("charge"); require(!v.isNull("billingSubscriptionId") && n(charge.opt("totalMinor")).compareTo(n(charge.opt("debitMinor")) - n(charge.opt("discountMinor")) + n(charge.opt("taxMinor"))) == 0 && n(charge.opt("discountMinor")) <= n(charge.opt("debitMinor")) && time(charge.opt("periodEndsAt")) > time(charge.opt("effectiveAt"))) }
174
178
  require(unique(lines.map { it.getString("lineId") }) && lines.fold(BigDecimal.ZERO) { total, line -> total + n(line.opt("catalogAmountMinor")) }.compareTo(n(v.opt("catalogAmountMinor"))) == 0)
175
179
  }
@@ -29,9 +29,9 @@ internal object IndianaCustomerSemantics {
29
29
  val plans = objects(value.getJSONArray("plans")); val provider = value.getString("provider")
30
30
  require(plans.map { it.getString("productVersionId") }.distinct().size == plans.size)
31
31
  require(plans.all { (it.optJSONObject("featureQuantities")?.length() ?: 0) <= 100 })
32
- require(!value.has("offerId") || !value.has("billingSubscriptionId") && (provider == "stripe" || value.has("nativeOffer")))
33
- require(!value.has("nativeOffer") || provider != "stripe" && plans.size == 1)
34
- require(!value.has("newBillingSubscription") || provider == "stripe" && !value.has("billingSubscriptionId"))
32
+ require(!value.has("offerId") || !value.has("billingSubscriptionId") && (provider in listOf("stripe", "merchant") || value.has("nativeOffer")))
33
+ require(!value.has("nativeOffer") || provider in listOf("apple", "google") && plans.size == 1)
34
+ require(!value.has("newBillingSubscription") || provider in listOf("stripe", "merchant") && !value.has("billingSubscriptionId"))
35
35
  require(value.optJSONObject("invoiceMode")?.optBoolean("enabled") != true || provider == "stripe" && !value.has("entityId"))
36
36
  value.optJSONObject("nativeOffer")?.let { offer ->
37
37
  require(offer.getString("offerKind") != "none" || offer.isNull("offerId") && offer.getInt("discountMinor") == 0 && offer.isNull("trialDays"))
@@ -19,7 +19,7 @@ class IndianaFeatureClient internal constructor(private val activity: Activity,
19
19
  private val pending = mutableMapOf<UUID, CompletableDeferred<Any?>>()
20
20
  var onError: ((Exception) -> Unit)? = null
21
21
  private var stopped = false
22
- private val host = IndianaBundledWebView(activity, "Indiana Feature evaluator", work, ::dispatch) { error -> if (!readiness.isCompleted) close() else onError?.invoke(error) }
22
+ private val host = IndianaBundledWebView(activity, "Longplay Feature evaluator", work, ::dispatch) { error -> if (!readiness.isCompleted) close() else onError?.invoke(error) }
23
23
  init { host.start() }
24
24
  suspend fun evalFeature(key: String): JSONObject {
25
25
  val result = call(JSONObject().put("method", "evalFeature").put("key", key)) as? JSONObject ?: throw IndianaClientException("invalid_response")
@@ -2,7 +2,7 @@
2
2
  package ai.tinkerland.indiana
3
3
  enum class IndianaEnvironment(val value: String) { SANDBOX("sandbox"), LIVE("live") }
4
4
  internal object IndianaProtocol {
5
- val wireProtocol = "0.9"
5
+ val wireProtocol = "0.10"
6
6
  val uuidPattern = Regex("^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)\$")
7
7
  val utcPattern = Regex("^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))\$")
8
8
  val scopeKeys = setOf("projectId", "environment")
@@ -8,7 +8,7 @@ import java.net.HttpURLConnection
8
8
  import java.io.ByteArrayOutputStream
9
9
 
10
10
  /** Override baseUrl for test services; Sandbox selects business scope within that service. */
11
- data class IndianaConfiguration(val baseUrl: String = "https://api.tinker.cash",val scope: IndianaScope,val timeoutMs: Int=15_000) {
11
+ data class IndianaConfiguration(val baseUrl: String = "https://api.longplayai.com",val scope: IndianaScope,val timeoutMs: Int=15_000) {
12
12
  internal val apiOrigin: String get() {
13
13
  val url = URI(baseUrl)
14
14
  return URI(url.scheme.lowercase(), null, url.host.lowercase(), if (url.port == 443) -1 else url.port, null, null, null).toString()
@@ -18,7 +18,7 @@ import kotlinx.coroutines.cancel
18
18
  import kotlinx.coroutines.sync.Mutex
19
19
  import kotlinx.coroutines.sync.withLock
20
20
 
21
- enum class IndianaSubscriptionProvider(val value: String) { STRIPE("stripe"), APPLE("apple"), GOOGLE("google") }
21
+ enum class IndianaSubscriptionProvider(val value: String) { STRIPE("stripe"), APPLE("apple"), GOOGLE("google"), MERCHANT("merchant") }
22
22
  data class IndianaSubscriptionPlan(val productVersionId: UUID, val seatQuantities: Map<String, Int> = emptyMap(), val featureQuantities: Map<UUID, Int> = emptyMap())
23
23
 
24
24
  interface IndianaGoogleStore: AutoCloseable {
@@ -348,7 +348,7 @@ class IndianaClient internal constructor(private val configuration: IndianaConfi
348
348
  val body = JSONObject().put("requestId", requestId.toString()).put("plans", entries).put("provider", provider.value).put("currency", currency)
349
349
  billingSubscriptionId?.let { body.put("billingSubscriptionId", it.toString()) }
350
350
  if (nativeOffer != null && (!nativeOffer.matches(provider) || plans.size != 1) ||
351
- offerId != null && (billingSubscriptionId != null || provider != IndianaSubscriptionProvider.STRIPE && nativeOffer == null)) throw IndianaClientException("invalid_request")
351
+ offerId != null && (billingSubscriptionId != null || provider !in listOf(IndianaSubscriptionProvider.STRIPE, IndianaSubscriptionProvider.MERCHANT) && nativeOffer == null)) throw IndianaClientException("invalid_request")
352
352
  offerId?.let { body.put("offerId", it.toString()) }
353
353
  nativeOffer?.let { body.put("nativeOffer", it.wire()) }
354
354
  return authenticated("subscriptions/preview-attach", body)
@@ -0,0 +1,38 @@
1
+ package ai.tinkerland.indiana
2
+
3
+ import org.json.JSONObject
4
+
5
+ /** Merchant refinements dispatch on generated DTO fields, not user JSON. */
6
+ internal object IndianaMerchantCommerceSemantics {
7
+ fun validate(v: JSONObject, fields: Set<String>) {
8
+ fun has(vararg keys: String) = fields.containsAll(keys.toList())
9
+ fun require(value: Boolean) { if (!value) throw IndianaClientException("invalid_response") }
10
+ fun objects(key: String) = v.getJSONArray(key).let { rows -> (0 until rows.length()).map { rows.getJSONObject(it) } }
11
+ if (has("configurationRevision", "connectionRevision", "lineItems", "catalogAmountMinor", "discount")) {
12
+ val lines = objects("lineItems"); val total = v.getLong("catalogAmountMinor")
13
+ require(lines.sumOf { it.getLong("totalMinor") } == total && (v.isNull("discount") || v.getJSONObject("discount").getLong("amountMinor") <= total) && lines.map { it.getString("priceId") }.distinct().size == lines.size)
14
+ }
15
+ if (has("phase", "code", "reason", "retryable", "recovery") && v.optString("reason") == "RESULT_UNKNOWN") require(!v.getBoolean("retryable") && v.getString("recovery") == "recover_operation")
16
+ if (has("merchantExecutionId", "paymentStatus", "checkoutUrl", "checkoutExpiresAt") && !v.isNull("checkoutUrl")) require(v.getString("paymentStatus") in listOf("not_started", "pending") && !v.isNull("checkoutExpiresAt"))
17
+ if (has("merchantExecutionId", "scheduledFor", "status") || has("provider", "eventId", "subscription", "scheduledFor", "status")) require((v.getString("status") == "scheduled") == !v.isNull("scheduledFor"))
18
+ if (has("merchantExecutionId", "merchantRefundId", "status") && v.getString("status") == "succeeded") require(!v.isNull("merchantRefundId"))
19
+ if (has("lookup", "operationId", "customerId", "execution")) v.optJSONObject("execution")?.let { require(it.getString("operationId") == v.getString("operationId") && it.getString("customerId") == v.getString("customerId")) }
20
+ if (has("scope", "operationId", "customerId", "confirmedAt", "initialExecutionAllowed", "instruction")) v.getJSONObject("instruction").let { require(it.getString("operationId") == v.getString("operationId") && it.getString("customerId") == v.getString("customerId")) }
21
+ if (has("merchantSubscriptionId", "periodStartsAt", "periodEndsAt", "items")) {
22
+ val items = objects("items")
23
+ require(utcMillis(v.getString("periodEndsAt")) > utcMillis(v.getString("periodStartsAt")) && items.map { it.getString("priceId") }.distinct().size == items.size)
24
+ }
25
+ if (has("merchantInvoiceId", "subtotalMinor", "discountMinor", "taxMinor", "totalMinor", "paidMinor")) require(v.getLong("subtotalMinor") - v.getLong("discountMinor") + v.getLong("taxMinor") == v.getLong("totalMinor") && v.getLong("discountMinor") <= v.getLong("subtotalMinor") && v.getLong("paidMinor") == v.getLong("totalMinor"))
26
+ if (has("merchantInvoiceId", "lines", "paidMinor")) {
27
+ val lines = objects("lines")
28
+ require(lines.map { it.getString("merchantLineId") }.distinct().size == lines.size)
29
+ for (key in listOf("subtotalMinor", "discountMinor", "taxMinor", "totalMinor")) require(lines.sumOf { it.getLong(key) } == v.getLong(key))
30
+ }
31
+ if (has("merchantLineId", "subtotalMinor", "discountMinor", "taxMinor", "totalMinor")) {
32
+ val subtotal = v.getLong("subtotalMinor"); val discount = v.getLong("discountMinor"); val tax = v.getLong("taxMinor")
33
+ require(subtotal - discount + tax == v.getLong("totalMinor") && (if (subtotal < 0) discount == 0L && tax == 0L else discount <= subtotal))
34
+ }
35
+ if (has("provider", "eventId", "merchantOrderId", "paymentStatus", "invoice") && v.getString("paymentStatus") == "paid") require(!v.isNull("invoice"))
36
+ if (has("provider", "merchantInvoiceId", "cancelSubscriptions", "entitlementAction", "amountMinor") || has("provider", "eventId", "merchantRefundId", "amountMinor", "status")) require(v.getLong("amountMinor") > 0)
37
+ }
38
+ }
@@ -16,7 +16,7 @@ data class IndianaNativeStoreOffer(val offerKind: IndianaNativeOfferKind, val of
16
16
  internal fun wire(): JSONObject = JSONObject().put("offerKind", offerKind.value).put("offerId", offerId ?: JSONObject.NULL)
17
17
  .put("discountMinor", discountMinor).put("trialDays", trialDays ?: JSONObject.NULL)
18
18
  internal fun matches(provider: IndianaSubscriptionProvider): Boolean = when (provider) {
19
- IndianaSubscriptionProvider.STRIPE -> false
19
+ IndianaSubscriptionProvider.STRIPE, IndianaSubscriptionProvider.MERCHANT -> false
20
20
  IndianaSubscriptionProvider.APPLE -> (offerKind == IndianaNativeOfferKind.PROMOTIONAL) == (offerId != null)
21
21
  IndianaSubscriptionProvider.GOOGLE -> (offerKind == IndianaNativeOfferKind.NONE) == (offerId == null) &&
22
22
  (offerId == null || offerId.matches(Regex("[a-z0-9][a-z0-9-]*")))
@@ -2,5 +2,5 @@
2
2
  package ai.tinkerland.indiana
3
3
 
4
4
  object IndianaSDK {
5
- const val version = "0.9.5"
5
+ const val version = "0.10.2"
6
6
  }