From fcca4b6ac63d54ab25ea373c4da0b991b594253b Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 12 Aug 2026 18:14:21 +0000 Subject: [PATCH 1/3] Fix originalTransactionIdentifier to strip Google Play recurrence suffix Google Play appends a recurrence suffix (..N) to order ids for subscription renewals, e.g. GPA.1234-1234-1234-12345..1. The original transaction id was being reported as the renewal's own order id. Strip the trailing ..N wherever an original transaction id is expected, while keeping the untouched order id for current-transaction fields. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_0144ioYUormc7wKAGwYVa2Dw --- .../product/receipt/ReceiptManager.kt | 3 +- .../GoogleBillingPurchaseTransaction.kt | 13 ++++- .../GoogleBillingPurchaseTransactionTest.kt | 48 +++++++++++++++++++ 3 files changed, 62 insertions(+), 2 deletions(-) create mode 100644 superwall/src/test/java/com/superwall/sdk/store/abstractions/transactions/GoogleBillingPurchaseTransactionTest.kt diff --git a/superwall/src/main/java/com/superwall/sdk/store/abstractions/product/receipt/ReceiptManager.kt b/superwall/src/main/java/com/superwall/sdk/store/abstractions/product/receipt/ReceiptManager.kt index b17a7087c..d37300257 100644 --- a/superwall/src/main/java/com/superwall/sdk/store/abstractions/product/receipt/ReceiptManager.kt +++ b/superwall/src/main/java/com/superwall/sdk/store/abstractions/product/receipt/ReceiptManager.kt @@ -15,6 +15,7 @@ import com.superwall.sdk.storage.LatestDeviceCustomerInfo import com.superwall.sdk.storage.Storage import com.superwall.sdk.storage.StoredTransactionHistory import com.superwall.sdk.store.abstractions.product.StoreProduct +import com.superwall.sdk.store.abstractions.transactions.GoogleBillingPurchaseTransaction import com.superwall.sdk.store.coordinator.ProductsFetcher import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.launch @@ -144,7 +145,7 @@ class ReceiptManager( } // Collect receipt for original transaction - val txnId = purchase.orderId ?: purchase.purchaseToken + val txnId = GoogleBillingPurchaseTransaction.originalOrderId(purchase.orderId) ?: purchase.purchaseToken if (!originalTransactionIds.contains(txnId)) { transactionReceipts.add( TransactionReceipt( diff --git a/superwall/src/main/java/com/superwall/sdk/store/abstractions/transactions/GoogleBillingPurchaseTransaction.kt b/superwall/src/main/java/com/superwall/sdk/store/abstractions/transactions/GoogleBillingPurchaseTransaction.kt index 666092474..305df7128 100644 --- a/superwall/src/main/java/com/superwall/sdk/store/abstractions/transactions/GoogleBillingPurchaseTransaction.kt +++ b/superwall/src/main/java/com/superwall/sdk/store/abstractions/transactions/GoogleBillingPurchaseTransaction.kt @@ -50,7 +50,7 @@ data class GoogleBillingPurchaseTransaction( constructor(transaction: Purchase) : this( underlyingSK2Transaction = transaction, transactionDate = Date(transaction.purchaseTime), - originalTransactionIdentifier = transaction.orderId, + originalTransactionIdentifier = originalOrderId(transaction.orderId), state = StoreTransactionState.Purchased, storeTransactionId = transaction.orderId, originalTransactionDate = Date(transaction.purchaseTime), @@ -66,4 +66,15 @@ data class GoogleBillingPurchaseTransaction( purchaseToken = transaction.purchaseToken, signature = transaction.signature, ) + + companion object { + private val RECURRENCE_SUFFIX = Regex("""\.\.\d+$""") + + /** + * Returns the original transaction id for a Google Play order id by stripping + * the recurrence suffix that renewals append, e.g. + * `GPA.1234-1234-1234-12345..1` -> `GPA.1234-1234-1234-12345`. + */ + internal fun originalOrderId(orderId: String?): String? = orderId?.replace(RECURRENCE_SUFFIX, "") + } } diff --git a/superwall/src/test/java/com/superwall/sdk/store/abstractions/transactions/GoogleBillingPurchaseTransactionTest.kt b/superwall/src/test/java/com/superwall/sdk/store/abstractions/transactions/GoogleBillingPurchaseTransactionTest.kt new file mode 100644 index 000000000..d59844ffa --- /dev/null +++ b/superwall/src/test/java/com/superwall/sdk/store/abstractions/transactions/GoogleBillingPurchaseTransactionTest.kt @@ -0,0 +1,48 @@ +@file:Suppress("ktlint:standard:function-naming") + +package com.superwall.sdk.store.abstractions.transactions + +import com.superwall.sdk.Given +import com.superwall.sdk.Then +import com.superwall.sdk.When +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNull +import org.junit.Test + +class GoogleBillingPurchaseTransactionTest { + @Test + fun `originalOrderId strips the Play recurrence suffix`() { + Given("Google Play order ids with and without recurrence suffixes") { + val baseOrderId = "GPA.1234-1234-1234-12345" + + When("deriving the original order id") { + Then("an id without a suffix is unchanged") { + assertEquals(baseOrderId, GoogleBillingPurchaseTransaction.originalOrderId(baseOrderId)) + } + + Then("a `..0` suffix is stripped") { + assertEquals(baseOrderId, GoogleBillingPurchaseTransaction.originalOrderId("$baseOrderId..0")) + } + + Then("a `..1` suffix is stripped") { + assertEquals(baseOrderId, GoogleBillingPurchaseTransaction.originalOrderId("$baseOrderId..1")) + } + + Then("a multi-digit `..12` suffix is stripped") { + assertEquals(baseOrderId, GoogleBillingPurchaseTransaction.originalOrderId("$baseOrderId..12")) + } + + Then("single dots within the id are not truncated") { + assertEquals( + "GPA.1234-1234-1234-12345", + GoogleBillingPurchaseTransaction.originalOrderId("GPA.1234-1234-1234-12345"), + ) + } + + Then("a null order id stays null") { + assertNull(GoogleBillingPurchaseTransaction.originalOrderId(null)) + } + } + } + } +} From e5bf06a8841f938e0bb0c287c6f8641e2d906c6c Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 12 Aug 2026 18:34:17 +0000 Subject: [PATCH 2/3] Update coverage badge [skip ci] --- .github/badges/jacoco.svg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/badges/jacoco.svg b/.github/badges/jacoco.svg index ec50def2a..fd9384d2c 100644 --- a/.github/badges/jacoco.svg +++ b/.github/badges/jacoco.svg @@ -1 +1 @@ -coverage45.4% \ No newline at end of file +coverage45.5% \ No newline at end of file From ff9b36811ec53d899a47dde9c7adaecb4ef55429 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 12 Aug 2026 18:35:40 +0000 Subject: [PATCH 3/3] ci: retrigger after runner infra flake Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_0144ioYUormc7wKAGwYVa2Dw