Created
January 9, 2019 13:23
-
-
Save kolomiec-valeriy/5bec950e2e10e3b2908166e82d5c3ed4 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace App\Subscription; | |
use App\Entity\Subscription\IAPAndroidReceipt; | |
use App\Exception\InvalidAndroidReceiptException; | |
use App\Exception\InvalidArgumentsException; | |
use App\Exception\ReceiptNotFoundException; | |
use Carbon\Carbon; | |
use Doctrine\Common\Persistence\ManagerRegistry; | |
use GuzzleHttp\Client; | |
use GuzzleHttp\Exception\RequestException; | |
use Mcfedr\QueueManagerBundle\Exception\JobNotDeletableException; | |
use Mcfedr\QueueManagerBundle\Manager\QueueManagerRegistry; | |
use Mcfedr\QueueManagerBundle\Queue\Worker; | |
use Psr\Log\LoggerInterface; | |
class AndroidSubscriptionManager implements Worker | |
{ | |
/** | |
* @var ManagerRegistry | |
*/ | |
private $doctrine; | |
/** | |
* @var QueueManagerRegistry | |
*/ | |
private $manager; | |
/** | |
* @var LoggerInterface | |
*/ | |
private $logger; | |
/** | |
* @var SubscriptionTypeManager | |
*/ | |
private $typeManager; | |
/** | |
* @var Client | |
*/ | |
private $guzzleClient; | |
public function __construct( | |
ManagerRegistry $doctrine, | |
QueueManagerRegistry $manager, | |
LoggerInterface $logger, | |
SubscriptionTypeManager $subscriptionTypeManager, | |
Client $guzzleClient | |
) { | |
$this->doctrine = $doctrine; | |
$this->manager = $manager; | |
$this->logger = $logger; | |
$this->typeManager = $subscriptionTypeManager; | |
$this->guzzleClient = $guzzleClient; | |
} | |
public function handleSubscription(IAPAndroidReceipt $androidReceipt) | |
{ | |
try { | |
$subscriptionData = $this->getSubscriptionData($androidReceipt); | |
$oldExpire = $androidReceipt->getExpirationAt(); | |
$androidReceipt = $this->saveReceipt( | |
$androidReceipt, | |
$subscriptionData | |
); | |
$this->validateSubscriptionDataStatusCode($androidReceipt); | |
if ($androidReceipt->isActiveSubscription()) { | |
if (!$oldExpire) { | |
$this->logger->info('Subscription_Android_New'); | |
} elseif ($oldExpire != $androidReceipt->getExpirationAt()) { | |
$this->logger->info('Subscription_Android_Renew'); | |
} | |
$androidReceipt->setAttemptCount(0); | |
if ($androidReceipt->getExpirationAt()) { | |
$expiredDate = clone $androidReceipt->getExpirationAt(); | |
$expiredDate->modify('+1 day'); | |
$this->queue($androidReceipt, $expiredDate); | |
} | |
$this->doctrine->getManager()->flush(); | |
$this->typeManager->queue($androidReceipt->getAccount()); | |
} elseif (!$androidReceipt->isCanceled( | |
) && (IAPAndroidReceipt::PAYMENT_STATE_PENDING === $androidReceipt->getPaymentState( | |
) || $androidReceipt->getAttemptCount() < 5)) { | |
$this->logger->warning( | |
'Android receipt. Payment pending. Trying after 5 mins', | |
[ | |
'android_receipt_id' => $androidReceipt->getId(), | |
] | |
); | |
$androidReceipt->setAttemptCount( | |
$androidReceipt->getAttemptCount() + 1 | |
); | |
$this->queue($androidReceipt, new Carbon('+5 minute')); | |
} else { | |
$this->doctrine->getManager()->flush(); | |
$this->typeManager->queue($androidReceipt->getAccount()); | |
} | |
} catch (InvalidAndroidReceiptException $e) { | |
$this->logger->error( | |
'Android Receipt exception', | |
[ | |
'e' => $e->getMessage(), | |
'android_receipt_id' => $androidReceipt->getId(), | |
] | |
); | |
$this->typeManager->queue($androidReceipt->getAccount()); | |
} | |
} | |
public function queue( | |
IAPAndroidReceipt $androidReceipt, | |
\DateTime $when = null | |
) { | |
$this->cancel($androidReceipt); | |
$androidReceipt->setJobReference( | |
$this->manager->put( | |
'android_subscription_manager', | |
[ | |
'receipt_id' => $androidReceipt->getId(), | |
], | |
[ | |
'queue' => 'subscriptions', | |
'time' => $when, | |
], | |
'delay' | |
) | |
); | |
$this->doctrine->getManager()->flush(); | |
} | |
/** | |
* Updates the validity of the receipt. | |
* | |
* @param IAPAndroidReceipt $androidReceipt | |
*/ | |
private function validateSubscriptionDataStatusCode( | |
IAPAndroidReceipt $androidReceipt | |
) { | |
if (null === $androidReceipt->getCancelReason()) { | |
$androidReceipt->setCanceled(false); | |
} else { | |
if (0 == $androidReceipt->getCancelReason()) { | |
$this->logger->info( | |
'Android receipt. User cancelled the subscription', | |
[ | |
'android_receipt_id' => $androidReceipt->getId(), | |
] | |
); | |
} elseif (1 == $androidReceipt->getCancelReason()) { | |
$this->logger->info( | |
'Android receipt. Subscription was cancelled by the system, for example because of a billing problem', | |
[ | |
'android_receipt_id' => $androidReceipt->getId(), | |
] | |
); | |
} | |
$androidReceipt->setCanceled(true); | |
} | |
if ( | |
IAPAndroidReceipt::PAYMENT_STATE_RECEIVED === $androidReceipt->getPaymentState( | |
) || | |
IAPAndroidReceipt::PURCHASE_STATE_PURCHASED === $androidReceipt->getPurchaseState( | |
) | |
) { | |
$androidReceipt->setValid(true); | |
} elseif ( | |
IAPAndroidReceipt::PAYMENT_STATE_PENDING === $androidReceipt->getPaymentState( | |
) || | |
IAPAndroidReceipt::PURCHASE_STATE_CANCELLED === $androidReceipt->getPurchaseState( | |
) | |
) { | |
$androidReceipt->setValid(false); | |
} else { | |
$this->logger->error( | |
'Android receipt. Unknown receipt status', | |
[ | |
'android_receipt_id' => $androidReceipt->getId(), | |
'payment_state' => $androidReceipt->getPaymentState(), | |
] | |
); | |
$androidReceipt->setValid(false); | |
} | |
} | |
/** | |
* Store the Google data in the receipt object. | |
* | |
* @param IAPAndroidReceipt $androidReceipt | |
* @param array $subscriptionData | |
* | |
* @return IAPAndroidReceipt | |
*/ | |
private function saveReceipt( | |
IAPAndroidReceipt $androidReceipt, | |
array $subscriptionData | |
) { | |
$originExpirationDate = $androidReceipt->getExpirationAt(); | |
$androidReceipt->setPurchaseKind($subscriptionData['kind']); | |
$androidReceipt->setDeveloperPayload( | |
$subscriptionData['developerPayload'] | |
); | |
if ($androidReceipt->getProductIdentifier( | |
) && $androidReceipt->getProductIdentifier()->isLifetime()) { | |
$androidReceipt | |
->setPurchaseState($subscriptionData['purchaseState']); | |
$androidReceipt->setConsumptionState( | |
$subscriptionData['purchaseState'] | |
); | |
$androidReceipt->setPurchaseAt( | |
Carbon::createFromFormat( | |
'U', | |
(int) ($subscriptionData['purchaseTimeMillis'] / 1000) | |
) | |
); | |
} else { | |
$androidReceipt | |
->setPriceCurrencyCode($subscriptionData['priceCurrencyCode']) | |
->setPriceAmountMicros($subscriptionData['priceAmountMicros']) | |
->setCountryCode($subscriptionData['countryCode']) | |
->setPaymentState( | |
array_key_exists( | |
'paymentState', | |
$subscriptionData | |
) ? $subscriptionData['paymentState'] : null | |
) | |
->setCancelReason( | |
array_key_exists( | |
'cancelReason', | |
$subscriptionData | |
) ? $subscriptionData['cancelReason'] : null | |
) | |
->setExpirationAt( | |
Carbon::createFromFormat( | |
'U', | |
(int) ($subscriptionData['expiryTimeMillis'] / 1000) | |
) | |
) | |
->setPurchaseAt( | |
Carbon::createFromFormat( | |
'U', | |
(int) ($subscriptionData['startTimeMillis'] / 1000) | |
) | |
); | |
} | |
if (null != $originExpirationDate && $androidReceipt->getExpirationAt( | |
) > $originExpirationDate) { | |
$androidReceipt->setRenewCount( | |
$androidReceipt->getRenewCount() + 1 | |
); | |
} | |
return $androidReceipt; | |
} | |
/** | |
* Fetch the android receipt data from Google. | |
* | |
* @param IAPAndroidReceipt $androidReceipt | |
* | |
* @throws InvalidAndroidReceiptException | |
* | |
* @return array | |
*/ | |
public function getSubscriptionData(IAPAndroidReceipt $androidReceipt) | |
{ | |
try { | |
$url = "https://www.googleapis.com/androidpublisher/v2/applications/{$androidReceipt->getPackageName()}/purchases/subscriptions/{$androidReceipt->getProductIdentifier()->getProductIdentifier()}/tokens/{$androidReceipt->getReceiptToken()}"; | |
if ($androidReceipt->getProductIdentifier( | |
) && $androidReceipt->getProductIdentifier()->isLifetime()) { | |
$url = "https://www.googleapis.com/androidpublisher/v2/applications/{$androidReceipt->getPackageName()}/purchases/products/{$androidReceipt->getProductIdentifier()->getProductIdentifier()}/tokens/{$androidReceipt->getReceiptToken()}"; | |
} | |
$responseData = $this->guzzleClient->get($url); | |
$responseJson = json_decode($responseData->getBody(), true); | |
$this->logger->info( | |
'Android receipt data', | |
[ | |
'androidReceipt_id' => $androidReceipt->getId(), | |
'response' => $responseJson, | |
] | |
); | |
return $responseJson; | |
} catch (RequestException $e) { | |
$this->logger->error( | |
'Android receipt fetching', | |
[ | |
'e' => $e->getMessage(), | |
'androidReceipt_id' => $androidReceipt->getId(), | |
'product_id' => $androidReceipt->getProductIdentifier(), | |
] | |
); | |
throw new InvalidAndroidReceiptException( | |
'Failed to fetch android receipt data', 0, $e | |
); | |
} | |
} | |
private function cancel(IAPAndroidReceipt $androidReceipt) | |
{ | |
if (($job = $androidReceipt->getJobReference())) { | |
try { | |
$androidReceipt->setJobReference(null); | |
$this->manager->delete($job); | |
} catch (JobNotDeletableException $e) { | |
$this->logger->error( | |
'Failed to delete android receipt job', | |
[ | |
'e' => $e->getMessage(), | |
'android_receipt_id' => $androidReceipt->getId(), | |
'job' => print_r($job, true), | |
] | |
); | |
} | |
} | |
} | |
/** | |
* Called to start the queued task. | |
* | |
* @param array $arguments | |
* | |
* @throws \Exception | |
*/ | |
public function execute(array $arguments) | |
{ | |
if (!isset($arguments['receipt_id'])) { | |
throw new InvalidArgumentsException('Missing argument'); | |
} | |
$receiptId = $arguments['receipt_id']; | |
$receiptObject = $this->doctrine->getRepository( | |
IAPAndroidReceipt::class | |
)->find($receiptId); | |
if (!$receiptObject) { | |
throw new ReceiptNotFoundException( | |
"Android Receipt not found with id {$receiptId}" | |
); | |
} | |
$this->handleSubscription($receiptObject); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment