diff --git a/src/main/kotlin/com/dietmap/yaak/api/appstore/subscription/StatusUpdateNotification.kt b/src/main/kotlin/com/dietmap/yaak/api/appstore/subscription/StatusUpdateNotification.kt index 8508594..4bc9f42 100644 --- a/src/main/kotlin/com/dietmap/yaak/api/appstore/subscription/StatusUpdateNotification.kt +++ b/src/main/kotlin/com/dietmap/yaak/api/appstore/subscription/StatusUpdateNotification.kt @@ -7,18 +7,20 @@ import com.fasterxml.jackson.annotation.JsonProperty import java.io.Serializable data class StatusUpdateNotification( - @get:JsonProperty("environment") val environment: String?, - @get:JsonProperty("notification_type") val notificationType: String?, - @get:JsonProperty("latest_receipt") val latestReceipt: String?, - @get:JsonProperty("latest_receipt_info") val latestReceiptInfo: LatestReceiptInfo, - @get:JsonProperty("expiration_intent") val expirationIntent: String?, @get:JsonProperty("auto_renew_adam_id") val autoRenewAdamId: String?, - @get:JsonProperty("auto_renew_status") val autoRenewStatus: Boolean?, @get:JsonProperty("auto_renew_product_id") val autoRenewProductId: String?, + @get:JsonProperty("auto_renew_status") val autoRenewStatus: Boolean?, @get:JsonProperty("auto_renew_status_change_date") val autoRenewStatusChangeDate: String?, - @get:JsonProperty("auto_renew_status_change_date_pst") val autoRenewStatusChangeDatePst: String?, @get:JsonProperty("auto_renew_status_change_date_ms") val autoRenewStatusChangeDateMs: Long?, - @get:JsonProperty("unified_receipt") val unifiedReceipt: UnifiedReceipt? + @get:JsonProperty("auto_renew_status_change_date_pst") val autoRenewStatusChangeDatePst: String?, + @get:JsonProperty("bid") val bid: String?, + @get:JsonProperty("bvrs") val bvrs: String?, + @get:JsonProperty("environment") val environment: String?, + @get:JsonProperty("expiration_intent") val expirationIntent: String?, + @get:JsonProperty("notification_type") val notificationType: String?, + @get:JsonProperty("original_transaction_id") val originalTransactionId: String?, + @get:JsonProperty("password") val password: String?, + @get:JsonProperty("unified_receipt") val unifiedReceipt: UnifiedReceipt ) : Serializable @@ -27,7 +29,7 @@ data class UnifiedReceipt( @get:JsonProperty("latest_receipt") val latestReceipt: String?, @get:JsonProperty("latest_receipt_info") val latestReceiptInfo: Collection?, @get:JsonProperty("pending_renewal_info") val pendingRenewalInfo: Collection?, - @get:JsonProperty("status") val latestExpiredReceiptInfo: String? + @get:JsonProperty("status") val status: String? ) : Serializable diff --git a/src/main/kotlin/com/dietmap/yaak/api/appstore/subscription/SubscriptionController.kt b/src/main/kotlin/com/dietmap/yaak/api/appstore/subscription/SubscriptionController.kt index 56212cd..85f8db6 100644 --- a/src/main/kotlin/com/dietmap/yaak/api/appstore/subscription/SubscriptionController.kt +++ b/src/main/kotlin/com/dietmap/yaak/api/appstore/subscription/SubscriptionController.kt @@ -44,7 +44,7 @@ class SubscriptionController(private val subscriptionService: AppStoreSubscripti } /** - * Handler for Server 2 Server notifications + * Handler for Server 2 Server notifications in version 1 */ @PostMapping("/statusUpdateNotification") fun handleStatusUpdateNotification(@Valid @RequestBody statusUpdateNotification: StatusUpdateNotification): ResponseEntity { diff --git a/src/main/kotlin/com/dietmap/yaak/domain/appstore/AppStoreSubscriptionService.kt b/src/main/kotlin/com/dietmap/yaak/domain/appstore/AppStoreSubscriptionService.kt index e406957..8a87a89 100644 --- a/src/main/kotlin/com/dietmap/yaak/domain/appstore/AppStoreSubscriptionService.kt +++ b/src/main/kotlin/com/dietmap/yaak/domain/appstore/AppStoreSubscriptionService.kt @@ -22,15 +22,10 @@ class AppStoreSubscriptionService(private val userAppClient: UserAppClient, priv fun handleInitialPurchase(tenant: String?, subscriptionPurchaseRequest: SubscriptionPurchaseRequest) : UserAppSubscriptionOrder? { val receiptResponse = appStoreClient(tenant).verifyReceipt(ReceiptRequest(subscriptionPurchaseRequest.receipt)) - logger.debug { "handleInitialPurchase: ReceiptResponse: $receiptResponse" } - if (receiptResponse.isValid()) { - val latestReceiptInfo = receiptResponse.latestReceiptInfo!!.stream().findFirst().get() - var effectivePrice = subscriptionPurchaseRequest.price - // intro offer period purchase if (latestReceiptInfo.isInIntroOfferPeriod && subscriptionPurchaseRequest.effectivePrice != null) { effectivePrice = subscriptionPurchaseRequest.effectivePrice @@ -48,7 +43,8 @@ class AppStoreSubscriptionService(private val userAppClient: UserAppClient, priv appMarketplace = AppMarketplace.APP_STORE, expiryTimeMillis = latestReceiptInfo.expiresDateMs, discountCode = subscriptionPurchaseRequest.discountCode, - appStoreReceipt = subscriptionPurchaseRequest.receipt + appStoreReceipt = subscriptionPurchaseRequest.receipt, + isTrialPeriod = latestReceiptInfo.isTrialPeriod ) return userAppClient.sendSubscriptionNotification(notification) @@ -61,15 +57,10 @@ class AppStoreSubscriptionService(private val userAppClient: UserAppClient, priv fun handleAutoRenewal(tenant: String?, subscriptionRenewRequest: SubscriptionRenewRequest) { val receiptResponse = appStoreClient(tenant).verifyReceipt(ReceiptRequest(subscriptionRenewRequest.receipt)) - logger.debug { "handleAutoRenewal: ReceiptResponse: $receiptResponse" } - if (receiptResponse.isValid()) { - if (receiptResponse.latestReceiptInfo != null) { - val latestReceiptInfo = receiptResponse.latestReceiptInfo.stream().findFirst().get() - val notification = UserAppSubscriptionNotification( notificationType = NotificationType.SUBSCRIPTION_RENEWED, description = "Subscription renewal from AppStore", @@ -81,11 +72,11 @@ class AppStoreSubscriptionService(private val userAppClient: UserAppClient, priv countryCode = null, currencyCode = null, discountCode = subscriptionRenewRequest.discountCode, - appStoreReceipt = subscriptionRenewRequest.receipt + appStoreReceipt = subscriptionRenewRequest.receipt, + isTrialPeriod = latestReceiptInfo.isTrialPeriod ) val subscriptionOrder = userAppClient.sendSubscriptionNotification(notification); - checkArgument(subscriptionOrder != null) { "Could not process SubscriptionRenewRequest $subscriptionRenewRequest in user app" } } } else { @@ -99,7 +90,7 @@ class AppStoreSubscriptionService(private val userAppClient: UserAppClient, priv logger.debug { "Processing StatusUpdateNotification: ${statusUpdateNotification.notificationType}" } var notificationType = NotificationType.SUBSCRIPTION_PURCHASED - val latestReceiptInfo = statusUpdateNotification.latestReceiptInfo + val latestReceiptInfo = statusUpdateNotification.unifiedReceipt.latestReceiptInfo?.maxBy { it.purchaseDateMs }!! when (val appStoreNotificationType = parseAppStoreNotificationTypeEnum(statusUpdateNotification.notificationType)) { @@ -119,7 +110,7 @@ class AppStoreSubscriptionService(private val userAppClient: UserAppClient, priv } // a customer downgrades - AppStoreNotificationType.DID_CHANGE_RENEWAL_PREF, AppStoreNotificationType.DID_CHANGE_RENEWAL_STATUS -> { + AppStoreNotificationType.DID_CHANGE_RENEWAL_PREF -> { // auto_renewal_product_id - product customer will auto renew at // skipping it // latest_receipt_info.original_transaction_id @@ -158,7 +149,7 @@ class AppStoreSubscriptionService(private val userAppClient: UserAppClient, priv // latest_receipt_info.expires_date_ms - date when the subscription will expire } - AppStoreNotificationType.DID_RENEW -> { + AppStoreNotificationType.DID_RENEW, AppStoreNotificationType.DID_CHANGE_RENEWAL_STATUS -> { // restore service for a renewed subscription // update customer's subscription to active / subscribe @@ -186,7 +177,8 @@ class AppStoreSubscriptionService(private val userAppClient: UserAppClient, priv countryCode = null, currencyCode = null, discountCode = null, - appStoreReceipt = statusUpdateNotification.latestReceipt + appStoreReceipt = statusUpdateNotification.unifiedReceipt.latestReceipt, + isTrialPeriod = latestReceiptInfo.isTrialPeriod ) logger.debug {"Sending UserAppSubscriptionNotification: $notification" } diff --git a/src/main/kotlin/com/dietmap/yaak/domain/googleplay/GooglePlaySubscriptionService.kt b/src/main/kotlin/com/dietmap/yaak/domain/googleplay/GooglePlaySubscriptionService.kt index 8e76194..339d426 100644 --- a/src/main/kotlin/com/dietmap/yaak/domain/googleplay/GooglePlaySubscriptionService.kt +++ b/src/main/kotlin/com/dietmap/yaak/domain/googleplay/GooglePlaySubscriptionService.kt @@ -48,7 +48,8 @@ class GooglePlaySubscriptionService(val androidPublisherService: AndroidPublishe orderingUserId = purchaseRequest.orderingUserId ?: subscription[USER_ACCOUNT_ID_KEY] as String?, discountCode = purchaseRequest.discountCode, expiryTimeMillis = subscription.expiryTimeMillis, - googlePlayPurchaseDetails = GooglePlayPurchaseDetails(purchaseRequest.packageName, purchaseRequest.subscriptionId, purchaseRequest.purchaseToken) + googlePlayPurchaseDetails = GooglePlayPurchaseDetails(purchaseRequest.packageName, purchaseRequest.subscriptionId, purchaseRequest.purchaseToken), + isTrialPeriod = subscription.paymentState == PAYMENT_FREE_TRIAL_CODE )) checkArgument(notificationResponse != null) { "Could not create subscription order ${subscription.orderId} in user app" } diff --git a/src/main/kotlin/com/dietmap/yaak/domain/userapp/UserAppSubscriptionNotification.kt b/src/main/kotlin/com/dietmap/yaak/domain/userapp/UserAppSubscriptionNotification.kt index 750bcd1..996539c 100644 --- a/src/main/kotlin/com/dietmap/yaak/domain/userapp/UserAppSubscriptionNotification.kt +++ b/src/main/kotlin/com/dietmap/yaak/domain/userapp/UserAppSubscriptionNotification.kt @@ -25,7 +25,8 @@ data class UserAppSubscriptionNotification( val orderingUserId: String? = String(), val discountCode: String? = String(), val appStoreReceipt: String? = String(), - val googlePlayPurchaseDetails: GooglePlayPurchaseDetails? = null + val googlePlayPurchaseDetails: GooglePlayPurchaseDetails? = null, + val isTrialPeriod: Boolean? = false ) enum class NotificationType { diff --git a/src/test/kotlin/com/dietmap/yaak/api/appstore/subscription/SubscriptionControllerTest.kt b/src/test/kotlin/com/dietmap/yaak/api/appstore/subscription/SubscriptionControllerTest.kt index e98fef1..a20e392 100644 --- a/src/test/kotlin/com/dietmap/yaak/api/appstore/subscription/SubscriptionControllerTest.kt +++ b/src/test/kotlin/com/dietmap/yaak/api/appstore/subscription/SubscriptionControllerTest.kt @@ -18,27 +18,26 @@ internal class SubscriptionControllerTest : SupportController() { @MockBean private lateinit var userAppClient: UserAppClient - @Mock - private lateinit var latestReceiptInfo: LatestReceiptInfo - @Mock private lateinit var unifiedReceipt: UnifiedReceipt private val testStatusUpdateNotification: StatusUpdateNotification = StatusUpdateNotification( - "sandbox", "CANCEL", "cancellationDate", latestReceiptInfo, "", - "latestExpiredReceipt", true, "", "autoRenewProductId", - "autoRenewStatusChangeDate", 12323230, unifiedReceipt) + "adamId", "sandbox", true, "cancellationDate", 1, + "latestExpiredReceipt", "1", "", "autoRenewProductId", + "autoRenewStatusChangeDate", "CANEL", "123", "123", unifiedReceipt + ) @Test fun `simulate subscription status update notification`() { this.mockMvc.perform( - MockMvcRequestBuilders.post("/api/appstore/subscriptions/statusUpdateNotification") - .contentType(MediaType.APPLICATION_JSON) - .content(asJsonString(testStatusUpdateNotification)) - .accept(MediaType.APPLICATION_JSON)) - .andDo(MockMvcResultHandlers.print()) - .andExpect(MockMvcResultMatchers.status().isOk) + MockMvcRequestBuilders.post("/api/appstore/subscriptions/statusUpdateNotification") + .contentType(MediaType.APPLICATION_JSON) + .content(asJsonString(testStatusUpdateNotification)) + .accept(MediaType.APPLICATION_JSON) + ) + .andDo(MockMvcResultHandlers.print()) + .andExpect(MockMvcResultMatchers.status().isOk) } } \ No newline at end of file