diff --git a/demo/demo/tests/fixtures/google_in_app.py b/demo/demo/tests/fixtures/google_in_app.py index 7ce8c13..b31f5ab 100644 --- a/demo/demo/tests/fixtures/google_in_app.py +++ b/demo/demo/tests/fixtures/google_in_app.py @@ -100,6 +100,27 @@ def build_google_rtdn_notification(type_: GoogleSubscriptionNotificationType): return build_google_rtdn_notification +@pytest.fixture +def google_rtdn_voided_purchase_notification(settings, purchase_token): + return { + "message": { + "messageId": "136969346945", + "publishTime": "2022-10-25T13:15:00.858Z", + "data": b64encode(json.dumps({ + "version": '1.0', + "packageName": settings.GOOGLE_PLAY_PACKAGE_NAME, + "eventTimeMillis": 100, + "voidedPurchaseNotification": { + 'purchaseToken': purchase_token, + 'orderId': 'GS.0000-0000-0000', + 'productType': 1, + }, + }).encode('utf8')).decode('utf8'), + }, + "subscription": "projects/myproject/subscriptions/mysubscription", + } + + @pytest.fixture def google_rtdn_notification(google_rtdn_notification_factory) -> dict: return google_rtdn_notification_factory(GoogleSubscriptionNotificationType.PURCHASED) diff --git a/demo/demo/tests/test_google_in_app.py b/demo/demo/tests/test_google_in_app.py index 10e15f0..a605c1a 100644 --- a/demo/demo/tests/test_google_in_app.py +++ b/demo/demo/tests/test_google_in_app.py @@ -1,6 +1,7 @@ from __future__ import annotations from dataclasses import dataclass +from datetime import timedelta from typing import Any from unittest import mock @@ -356,3 +357,36 @@ def test__google__subscriptions__cancel__google( assert user.subscriptions.active().count() == 1 assert user.subscriptions.active().latest().auto_prolong is False + + +@pytest.mark.django_db(databases=['actual_db']) +def test__google__subscriptions__voided_purchase( + user, + client, + subscription, + google_in_app, + google_rtdn_voided_purchase_notification, +): + subscription.end = now() + days(10) + subscription.save() + + SubscriptionPayment.objects.create( + user=user, + plan=subscription.plan, + subscription=subscription, + provider_codename=google_in_app.codename, + provider_transaction_id='12345', + status=SubscriptionPayment.Status.PENDING, + subscription_start=now() + days(10), + subscription_end=now() + days(40), + ) + + assert user.subscriptions.active().count() == 1 + + response = client.post('/api/webhook/google_in_app/', google_rtdn_voided_purchase_notification, content_type="application/json") + assert response.status_code == 200, response.content + + assert user.subscriptions.active().count() == 0 + subscription = user.subscriptions.latest() + assert now() - timedelta(seconds=5) <= subscription.end <= now() + # assert subscription.auto_prolong is False diff --git a/subscriptions/providers/google_in_app/__init__.py b/subscriptions/providers/google_in_app/__init__.py index 4b55d94..67c1658 100644 --- a/subscriptions/providers/google_in_app/__init__.py +++ b/subscriptions/providers/google_in_app/__init__.py @@ -349,6 +349,14 @@ def handle_google_notification(self, notification: GoogleDeveloperNotification) When a subscription is purchased, a SubscriptionNotification with type SUBSCRIPTION_PURCHASED notification is sent. When you receive this notification, you should query the Google Play Developer API to get the latest subscription state. """ log.debug('Received RTDN notification %s', notification) + + if notification.voidedPurchaseNotification: + log.debug('Received voided purchase notification -> cancelling subscription') + self.dismiss_token(notification.voidedPurchaseNotification.purchaseToken) + return Response({}, status=HTTP_200_OK) + + assert notification.subscriptionNotification, \ + 'subscriptionNotification field not found in notification' payment = self.update_or_create_subscription( purchase_token=notification.subscriptionNotification.purchaseToken, event=notification.subscriptionNotification.notificationType, diff --git a/subscriptions/providers/google_in_app/schemas.py b/subscriptions/providers/google_in_app/schemas.py index 9754a03..5ad2e31 100644 --- a/subscriptions/providers/google_in_app/schemas.py +++ b/subscriptions/providers/google_in_app/schemas.py @@ -40,6 +40,15 @@ class Config: extra = Extra.forbid +class VoidedPurchaseNotification(BaseModel): + purchaseToken: str + orderId: str + productType: int + + class Config: + extra = Extra.forbid + + class GoogleDeveloperNotification(BaseModel): # https://developer.android.com/google/play/billing/rtdn-reference#sub version: str @@ -47,6 +56,7 @@ class GoogleDeveloperNotification(BaseModel): eventTimeMillis: str oneTimeProductNotification: Optional[Any] = None subscriptionNotification: Optional[GoogleSubscriptionNotification] = None + voidedPurchaseNotification: Optional[VoidedPurchaseNotification] = None testNotification: Optional[GoogleTestNotification] = None class Config: