diff --git a/src/GooglePlay/Acknowledger.php b/src/GooglePlay/Acknowledger.php index bd9b166..c464b92 100644 --- a/src/GooglePlay/Acknowledger.php +++ b/src/GooglePlay/Acknowledger.php @@ -2,16 +2,25 @@ namespace ReceiptValidator\GooglePlay; +use Exception; +use Google_Service_AndroidPublisher; +use ReceiptValidator\RunTimeException; + /** * Class Acknowledger. */ class Acknowledger { + // Do acknowledge only in case if it have not done + const ACKNOWLEDGE_STRATEGY_IMPLICIT = 'strategy_implicit'; + // Try to do acknowledge directly (exception will be returned in case when acknowledge already was done) + const ACKNOWLEDGE_STRATEGY_EXPLICIT = 'strategy_explicit'; + const SUBSCRIPTION = 'SUBSCRIPTION'; const PRODUCT = 'PRODUCT'; /** - * @var \Google_Service_AndroidPublisher + * @var Google_Service_AndroidPublisher */ protected $androidPublisherService; /** @@ -26,31 +35,46 @@ class Acknowledger * @var string */ protected $productId; + /** + * @var string + */ + protected $strategy; /** * Acknowledger constructor. * - * @param \Google_Service_AndroidPublisher $googleServiceAndroidPublisher - * @param string $packageName - * @param string $purchaseToken - * @param string $productId + * @param Google_Service_AndroidPublisher $googleServiceAndroidPublisher + * @param string $packageName + * @param string $purchaseToken + * @param string $productId + * @param string $strategy + * + * @throws RunTimeException */ public function __construct( - \Google_Service_AndroidPublisher $googleServiceAndroidPublisher, + Google_Service_AndroidPublisher $googleServiceAndroidPublisher, $packageName, $productId, - $purchaseToken + $purchaseToken, + $strategy = self::ACKNOWLEDGE_STRATEGY_EXPLICIT ) { + if (!in_array($strategy, [self::ACKNOWLEDGE_STRATEGY_EXPLICIT, self::ACKNOWLEDGE_STRATEGY_IMPLICIT])) { + throw new RuntimeException(sprintf('Invalid strategy provided %s', $strategy)); + } + $this->androidPublisherService = $googleServiceAndroidPublisher; $this->packageName = $packageName; $this->purchaseToken = $purchaseToken; $this->productId = $productId; + $this->strategy = $strategy; } /** * @param string $type * @param string $developerPayload * + * @throws RunTimeException + * * @return bool */ public function acknowledge(string $type = self::SUBSCRIPTION, string $developerPayload = '') @@ -58,28 +82,68 @@ public function acknowledge(string $type = self::SUBSCRIPTION, string $developer try { switch ($type) { case self::SUBSCRIPTION: - $this->androidPublisherService->purchases_subscriptions->acknowledge( - $this->packageName, - $this->productId, - $this->purchaseToken, - new \Google_Service_AndroidPublisher_SubscriptionPurchasesAcknowledgeRequest( - ['developerPayload' => $developerPayload] - ) - ); + if ($this->strategy === self::ACKNOWLEDGE_STRATEGY_EXPLICIT) { + // Here exception might be thrown as previously, so no BC break here + $this->androidPublisherService->purchases_subscriptions->acknowledge( + $this->packageName, + $this->productId, + $this->purchaseToken, + new \Google_Service_AndroidPublisher_SubscriptionPurchasesAcknowledgeRequest( + ['developerPayload' => $developerPayload] + ) + ); + } elseif ($this->strategy === self::ACKNOWLEDGE_STRATEGY_IMPLICIT) { + $subscriptionPurchase = $this->androidPublisherService->purchases_subscriptions->get( + $this->packageName, + $this->productId, + $this->purchaseToken + ); + + if ($subscriptionPurchase->getAcknowledgementState() !== AbstractResponse::ACKNOWLEDGEMENT_STATE_DONE) { + $this->androidPublisherService->purchases_subscriptions->acknowledge( + $this->packageName, + $this->productId, + $this->purchaseToken, + new \Google_Service_AndroidPublisher_SubscriptionPurchasesAcknowledgeRequest( + ['developerPayload' => $developerPayload] + ) + ); + } + } break; case self::PRODUCT: - $this->androidPublisherService->purchases_products->acknowledge( - $this->packageName, - $this->productId, - $this->purchaseToken, - new \Google_Service_AndroidPublisher_ProductPurchasesAcknowledgeRequest( - ['developerPayload' => $developerPayload] - ) - ); + if ($this->strategy === self::ACKNOWLEDGE_STRATEGY_EXPLICIT) { + // Here exception might be thrown as previously, so no BC break here + $this->androidPublisherService->purchases_products->acknowledge( + $this->packageName, + $this->productId, + $this->purchaseToken, + new \Google_Service_AndroidPublisher_ProductPurchasesAcknowledgeRequest( + ['developerPayload' => $developerPayload] + ) + ); + } elseif ($this->strategy === self::ACKNOWLEDGE_STRATEGY_IMPLICIT) { + $productPurchase = $this->androidPublisherService->purchases_products->get( + $this->packageName, + $this->productId, + $this->purchaseToken + ); + + if ($productPurchase->getAcknowledgementState() !== AbstractResponse::ACKNOWLEDGEMENT_STATE_DONE) { + $this->androidPublisherService->purchases_products->acknowledge( + $this->packageName, + $this->productId, + $this->purchaseToken, + new \Google_Service_AndroidPublisher_ProductPurchasesAcknowledgeRequest( + ['developerPayload' => $developerPayload] + ) + ); + } + } break; default: throw new \RuntimeException( - \sprintf( + sprintf( 'Invalid type provided : %s expected %s', $type, implode(',', [self::PRODUCT, self::SUBSCRIPTION]) @@ -88,8 +152,8 @@ public function acknowledge(string $type = self::SUBSCRIPTION, string $developer } return true; - } catch (\Exception $e) { - throw new \RuntimeException($e->getMessage(), $e->getCode(), $e); + } catch (Exception $e) { + throw new \RuntimeException($e->getCode(), $e->getCode(), $e); } } } diff --git a/tests/GooglePlay/GooglePlayAcknowledgerTest.php b/tests/GooglePlay/GooglePlayAcknowledgerTest.php index 6a4eda7..f5d10b0 100755 --- a/tests/GooglePlay/GooglePlayAcknowledgerTest.php +++ b/tests/GooglePlay/GooglePlayAcknowledgerTest.php @@ -3,8 +3,10 @@ namespace ReceiptValidator\Tests; use Google_Service_AndroidPublisher; +use Google_Service_AndroidPublisher_ProductPurchase; use Google_Service_AndroidPublisher_Resource_PurchasesProducts; use Google_Service_AndroidPublisher_Resource_PurchasesSubscriptions; +use Google_Service_AndroidPublisher_SubscriptionPurchase; use PHPUnit\Framework\TestCase; use ReceiptValidator\GooglePlay\Acknowledger; @@ -13,7 +15,7 @@ */ class GooglePlayAcknowledgerTest extends TestCase { - public function testValidate(): void + public function testValidateWithNonAcknowledgedPurchase(): void { $packageName = 'testPackage'; $productId = '15'; @@ -21,35 +23,221 @@ public function testValidate(): void // mock objects $googleServiceAndroidPublisherMock = $this->getMockBuilder(Google_Service_AndroidPublisher::class) - ->disableOriginalConstructor()->getMock(); - $productPurchaseMock = $this->getMockBuilder(Google_Service_AndroidPublisher_Resource_PurchasesProducts::class) - ->disableOriginalConstructor()->getMock(); - $subscriptionPurchaseMock = $this->getMockBuilder(Google_Service_AndroidPublisher_Resource_PurchasesSubscriptions::class) - ->disableOriginalConstructor()->getMock(); + ->disableOriginalConstructor()->getMock(); + + // products + $purchasesProductsMock = $this->getMockBuilder( + Google_Service_AndroidPublisher_Resource_PurchasesProducts::class + ) + ->disableOriginalConstructor()->getMock(); + $productPurchaseMock = $this->getMockBuilder(Google_Service_AndroidPublisher_ProductPurchase::class) + ->disableOriginalConstructor()->getMock(); + $productPurchaseMock->expects($this->any())->method('getAcknowledgementState')->willReturn(0); + + // subscriptions + $purchasesSubscriptionsMock = $this->getMockBuilder( + Google_Service_AndroidPublisher_Resource_PurchasesSubscriptions::class + ) + ->disableOriginalConstructor()->getMock(); + $subscriptionPurchaseMock = $this->getMockBuilder(Google_Service_AndroidPublisher_SubscriptionPurchase::class) + ->disableOriginalConstructor()->getMock(); + $subscriptionPurchaseMock->expects($this->any())->method('getAcknowledgementState')->willReturn(0); + + // mock expectations + $googleServiceAndroidPublisherMock->purchases_products = $purchasesProductsMock; + $googleServiceAndroidPublisherMock->purchases_subscriptions = $purchasesSubscriptionsMock; + + $purchasesProductsMock->expects($this->once())->method('acknowledge') + ->with( + $packageName, + $productId, + $purchaseToken, + new \Google_Service_AndroidPublisher_ProductPurchasesAcknowledgeRequest( + ['developerPayload' => 'bar'] + ) + ); + + $purchasesSubscriptionsMock->expects($this->once())->method('acknowledge') + ->with( + $packageName, + $productId, + $purchaseToken, + new \Google_Service_AndroidPublisher_SubscriptionPurchasesAcknowledgeRequest( + ['developerPayload' => 'foo'] + ) + ); + + $googlePlayAcknowledger = new Acknowledger( + $googleServiceAndroidPublisherMock, + $packageName, + $productId, + $purchaseToken + ); + + $googlePlayAcknowledger->acknowledge(Acknowledger::SUBSCRIPTION, 'foo'); + $googlePlayAcknowledger->acknowledge(Acknowledger::PRODUCT, 'bar'); + } + + public function testValidateWithAcknowledgedPurchaseAndImplicitStrategy(): void + { + $packageName = 'testPackage'; + $productId = '15'; + $purchaseToken = 'testPurchaseToken'; + + // mock objects + $googleServiceAndroidPublisherMock = $this->getMockBuilder(Google_Service_AndroidPublisher::class) + ->disableOriginalConstructor()->getMock(); + + // products + $purchasesProductsMock = $this->getMockBuilder( + Google_Service_AndroidPublisher_Resource_PurchasesProducts::class + ) + ->disableOriginalConstructor()->getMock(); + $productPurchaseMock = $this->getMockBuilder(Google_Service_AndroidPublisher_ProductPurchase::class) + ->disableOriginalConstructor()->getMock(); + $productPurchaseMock->expects($this->any())->method('getAcknowledgementState')->willReturn(1); + + // subscriptions + $purchasesSubscriptionsMock = $this->getMockBuilder( + Google_Service_AndroidPublisher_Resource_PurchasesSubscriptions::class + ) + ->disableOriginalConstructor()->getMock(); + $subscriptionPurchaseMock = $this->getMockBuilder(Google_Service_AndroidPublisher_SubscriptionPurchase::class) + ->disableOriginalConstructor()->getMock(); + $subscriptionPurchaseMock->expects($this->any())->method('getAcknowledgementState')->willReturn(1); + + // mock expectations + $googleServiceAndroidPublisherMock->purchases_products = $purchasesProductsMock; + $googleServiceAndroidPublisherMock->purchases_subscriptions = $purchasesSubscriptionsMock; + + $purchasesProductsMock->expects($this->once())->method('get') + ->with( + $packageName, + $productId, + $purchaseToken + )->willReturn($productPurchaseMock); + $purchasesProductsMock->expects($this->never())->method('acknowledge') + ->with( + $packageName, + $productId, + $purchaseToken, + new \Google_Service_AndroidPublisher_ProductPurchasesAcknowledgeRequest( + ['developerPayload' => 'bar'] + ) + ); + + $purchasesSubscriptionsMock->expects($this->once())->method('get') + ->with( + $packageName, + $productId, + $purchaseToken + )->willReturn($subscriptionPurchaseMock); + $purchasesSubscriptionsMock->expects($this->never())->method('acknowledge') + ->with( + $packageName, + $productId, + $purchaseToken, + new \Google_Service_AndroidPublisher_SubscriptionPurchasesAcknowledgeRequest( + ['developerPayload' => 'foo'] + ) + ); + + $googlePlayAcknowledger = new Acknowledger( + $googleServiceAndroidPublisherMock, + $packageName, + $productId, + $purchaseToken, + Acknowledger::ACKNOWLEDGE_STRATEGY_IMPLICIT + ); + + $googlePlayAcknowledger->acknowledge(Acknowledger::SUBSCRIPTION, 'foo'); + $googlePlayAcknowledger->acknowledge(Acknowledger::PRODUCT, 'bar'); + } + + public function testValidateWithAcknowledgedPurchaseAndExplicitStrategyForSubscription(): void + { + $packageName = 'testPackage'; + $productId = '15'; + $purchaseToken = 'testPurchaseToken'; + + // mock objects + $googleServiceAndroidPublisherMock = $this->getMockBuilder(Google_Service_AndroidPublisher::class) + ->disableOriginalConstructor() + ->getMock(); + + // subscriptions + $purchasesSubscriptionsMock = $this->getMockBuilder( + Google_Service_AndroidPublisher_Resource_PurchasesSubscriptions::class + ) + ->disableOriginalConstructor() + ->getMock(); + $subscriptionPurchaseMock = $this->getMockBuilder(Google_Service_AndroidPublisher_SubscriptionPurchase::class) + ->disableOriginalConstructor() + ->getMock(); + $subscriptionPurchaseMock->expects($this->any())->method('getAcknowledgementState')->willReturn(1); // mock expectations - $googleServiceAndroidPublisherMock->purchases_products = $productPurchaseMock; - $googleServiceAndroidPublisherMock->purchases_subscriptions = $subscriptionPurchaseMock; - - $productPurchaseMock->expects($this->once())->method('acknowledge') - ->with( - $packageName, - $productId, - $purchaseToken, - new \Google_Service_AndroidPublisher_ProductPurchasesAcknowledgeRequest(['developerPayload' => 'bar']) - ); - - $subscriptionPurchaseMock->expects($this->once())->method('acknowledge') - ->with( - $packageName, - $productId, - $purchaseToken, - new \Google_Service_AndroidPublisher_SubscriptionPurchasesAcknowledgeRequest(['developerPayload' => 'foo']) - ); - - $googlePlayAcknowledger = new Acknowledger($googleServiceAndroidPublisherMock, $packageName, $productId, $purchaseToken); + $googleServiceAndroidPublisherMock->purchases_subscriptions = $purchasesSubscriptionsMock; + + $purchasesSubscriptionsMock->expects($this->once())->method('acknowledge') + ->with( + $packageName, + $productId, + $purchaseToken, + new \Google_Service_AndroidPublisher_SubscriptionPurchasesAcknowledgeRequest( + ['developerPayload' => 'foo'] + ) + ); + + $googlePlayAcknowledger = new Acknowledger( + $googleServiceAndroidPublisherMock, + $packageName, + $productId, + $purchaseToken + ); $googlePlayAcknowledger->acknowledge(Acknowledger::SUBSCRIPTION, 'foo'); + } + + public function testValidateWithAcknowledgedPurchaseAndExplicitStrategyForProduct(): void + { + $packageName = 'testPackage'; + $productId = '15'; + $purchaseToken = 'testPurchaseToken'; + + // mock objects + $googleServiceAndroidPublisherMock = $this->getMockBuilder(Google_Service_AndroidPublisher::class) + ->disableOriginalConstructor()->getMock(); + + // products + $purchasesProductsMock = $this->getMockBuilder( + Google_Service_AndroidPublisher_Resource_PurchasesProducts::class + ) + ->disableOriginalConstructor()->getMock(); + $productPurchaseMock = $this->getMockBuilder(Google_Service_AndroidPublisher_ProductPurchase::class) + ->disableOriginalConstructor()->getMock(); + $productPurchaseMock->expects($this->any())->method('getAcknowledgementState')->willReturn(1); + + // mock expectations + $googleServiceAndroidPublisherMock->purchases_products = $purchasesProductsMock; + + $purchasesProductsMock->expects($this->once())->method('acknowledge') + ->with( + $packageName, + $productId, + $purchaseToken, + new \Google_Service_AndroidPublisher_ProductPurchasesAcknowledgeRequest( + ['developerPayload' => 'bar'] + ) + ); + + $googlePlayAcknowledger = new Acknowledger( + $googleServiceAndroidPublisherMock, + $packageName, + $productId, + $purchaseToken + ); + $googlePlayAcknowledger->acknowledge(Acknowledger::PRODUCT, 'bar'); } }