@tinkerlandai/indiana-react-native 0.9.5 → 0.10.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 +3 -3
- package/dist/index.cjs +2 -2
- package/dist/index.d.cts +37281 -3203
- package/dist/index.d.ts +37281 -3203
- package/dist/index.js +2 -2
- package/native-sdk/android/java/ai/tinkerland/indiana/CustomerResponseSemantics.kt +7 -3
- package/native-sdk/android/java/ai/tinkerland/indiana/CustomerSemantics.kt +3 -3
- package/native-sdk/android/java/ai/tinkerland/indiana/GeneratedProtocol.kt +1 -1
- package/native-sdk/android/java/ai/tinkerland/indiana/IndianaClient.kt +2 -2
- package/native-sdk/android/java/ai/tinkerland/indiana/MerchantCommerceSemantics.kt +38 -0
- package/native-sdk/android/java/ai/tinkerland/indiana/NativeStoreOffer.kt +1 -1
- package/native-sdk/android/java/ai/tinkerland/indiana/SDKVersion.kt +1 -1
- package/native-sdk/android/resources/funnel-wire-schemas.json +1 -1
- package/native-sdk/ios/CustomerResponseSemantics.swift +7 -3
- package/native-sdk/ios/CustomerSemantics.swift +3 -3
- package/native-sdk/ios/GeneratedProtocol.swift +1 -1
- package/native-sdk/ios/IndianaClient.swift +2 -2
- package/native-sdk/ios/MerchantCommerceSemantics.swift +51 -0
- package/native-sdk/ios/NativeStoreOffer.swift +1 -1
- package/native-sdk/ios/SDKVersion.swift +1 -1
- package/native-sdk/ios/funnel-wire-schemas.json +1 -1
- package/package.json +1 -1
|
@@ -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 == "
|
|
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
|
|
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
|
|
33
|
-
require(!value.has("nativeOffer") || provider
|
|
34
|
-
require(!value.has("newBillingSubscription") || provider
|
|
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"))
|
|
@@ -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.
|
|
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")
|
|
@@ -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
|
|
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-]*")))
|