paysystemList->getList() as $k => $p) { if ($p->getId() == $this->getId()) { $p->setPublic(false); } } */ $di->billingPlanTable->customFields()->add( new Am_CustomFieldText(self::DATA_PRODUCT_SKU, "Google In App product SKU/subscription ID")); } /** * Do not send failure emails for in-app purchases * @param Invoice $invoice * @param $failedReason * @param $nextRebill */ function sendRebillFailedToUser(Invoice $invoice, $failedReason, $nextRebill) { } function supportsTokenPayment() { return true; } function _initSetupForm(\Am_Form_Setup $form) { if (!empty($_GET['reset_token_' . $this->getId()])) { $this->getDi()->store->delete(self::STORE_REFRESH_TOKEN_KEY); return Am_Mvc_Response::redirectLocation($this->getDi()->url("admin-setup/{$this->getId()}", false)); } $form->addText(self::CONFIG_CLIENT_ID, ['class' => 'am-el-wide']) ->setLabel(___('OAuth 2.0 Client ID')); $form->addText(self::CONFIG_CLIENT_SECRET, ['class' => 'am-el-wide']) ->setLabel(___('OAuth 2.0 Client Secret')); $form->addText(self::CONFIG_PACKAGE_NAME) ->setLabel(___('Package Name The package name of the application the inapp product was sold in (for example, "com.some.thing").')); if ($this->getOauthClientId() && $this->getOauthClientSecret()) { if (!$this->getRefreshToken()) { $url = sprintf("https://accounts.google.com/o/oauth2/auth?%s", http_build_query([ 'scope' => self::SCOPE, 'response_type' => 'code', 'access_type' => 'offline', 'redirect_uri' => $this->getPluginUrl('redirect'), 'client_id' => $this->getOauthClientId() ])); $el = $form->addHTML()->setHTML(<<Click this link to set Refresh Token HTML ); } else { $url = "?reset_token_{$this->getId()}=1"; $el = $form->addHTML()->setHTML(<<Token is set Click this link to Reset Refresh Token TOKENSET ); } $el->setLabel(___('Refresh Token')); } } /** * @param Am_Mvc_Request $request * @param Am_Mvc_Response $response * @param $invokeArgs */ function directAction($request, $response, $invokeArgs) { if ($request->getActionName() == 'redirect') { if ($request->getParam('scope') != self::SCOPE) { throw new Am_Exception_InputError("Please enable access to your Google Account"); } $code = $request->getParam('code'); if (empty($code)) { throw new Am_Exception_InputError("Code is empty!"); } $request = new Am_HttpRequest(self::ENDPOINT_TOKEN, Am_HttpRequest::METHOD_POST); $request->addPostParameter([ 'grant_type' => 'authorization_code', 'code' => $code, 'client_id' => $this->getOauthClientId(), 'client_secret' => $this->getOauthClientSecret(), 'redirect_uri' => $this->getPluginUrl('redirect') , ]); $resp = $request->send(); $log = Am_Di::getInstance()->invoiceLogRecord; $log->paysys_id = $this->getId(); $log->title = 'Google Redirect'; $log->add($request); $log->add($resp); if ($resp->getStatus() != 200) { throw new Am_Exception_InputError("Unable to contact Google Oauth token endpoint. Please start over"); } $json = json_decode($resp->getBody(), true); if (empty($json)) { throw new Am_Exception_InputError("Wrong response received from Google"); } if (empty($json['refresh_token'])) { throw new Am_Exception_InputError("No refresh token is response"); } $this->getDi()->store->set(self::STORE_REFRESH_TOKEN_KEY, $json['refresh_token']); $this->getDi()->store->set(self::STORE_ACCESS_TOKEN_KEY, $json['access_token'], "+" . ($json['expires_in'] - 10) . " seconds"); Am_Mvc_Response::redirectLocation($this->getDi()->surl('admin-setup/google-iap')); } else { return parent::directAction($request, $response, $invokeArgs); } } function storesCcInfo() { return false; } function getOauthClientId() { return $this->getConfig(self::CONFIG_CLIENT_ID); } function getOauthClientSecret() { return $this->getConfig(self::CONFIG_CLIENT_SECRET); } function getRefreshToken() { return $this->getDi()->store->get(self::STORE_REFRESH_TOKEN_KEY); } function getAccessToken() { $token = $this->getDi()->store->get(self::STORE_ACCESS_TOKEN_KEY); if (empty($token)) { if (!$this->getRefreshToken()) { throw new Am_Exception_InternalError("Refresh token is empty, please reconfigure Google Iap plugin"); } $request = new Am_HttpRequest(self::ENDPOINT_TOKEN, Am_HttpRequest::METHOD_POST); $request->addPostParameter([ 'grant_type' => 'refresh_token', 'client_id' => $this->getOauthClientId(), 'client_secret' => $this->getOauthClientSecret(), 'refresh_token' => $this->getRefreshToken() ]); $resp = $request->send(); if ($resp->getStatus() != 200) { throw new Am_Exception_InputError("Unable to contact Google Oauth token endpoint. Please start over"); } $json = json_decode($resp->getBody(), true); if (empty($json)) { throw new Am_Exception_InputError("Wrong response received from Google"); } if (empty($json['access_token'])) { throw new Am_Exception_InputError("No access token is response"); } $this->getDi()->store->set(self::STORE_ACCESS_TOKEN_KEY, $json['access_token'], "+" . ($json['expires_in'] - 10) . " seconds"); $token = $json['access_token']; } return $token; } function createTransaction($request, $response, array $invokeArgs) { return new Am_Paysystem_GoogleIap_Transaction_Incoming($this,$request, $response, $invokeArgs); } public function _doBill(Invoice $invoice, $doFirst, CcRecord $cc, Am_Paysystem_Result $result) { } public function doBill( Invoice $invoice, $doFirst, CcRecord $cc = null) { if ($doFirst) { return; } $this->invoice = $invoice; try { return $this->processTokenPayment($invoice); } catch (Exception $ex) { $this->getDi()->errorLogTable->logException($ex); $result = new Am_Paysystem_Result(); $result->setFailed($ex->getMessage()); return $result; } } function processTokenPayment(Invoice $invoice, $token = null) { $token = $this->validateToken($invoice, $token); $log = $this->getDi()->invoiceLogRecord; $log->setInvoice($invoice); $result = new Am_Paysystem_Result(); try { $subscriptionInfo = $this->getObjectInfo($invoice->rebill_times?self::TYPE_SUBSCRIPTIONS : self::TYPE_PRODUCTS, $invoice, $token); } catch (Exception $ex) { $result->setFailed($ex->getMessage()); return $result; } $tr = new Am_Paysystem_Transaction_GoogleIap($this, $subscriptionInfo); $tr->setInvoice($invoice); $tr->process(); $result->setSuccess($tr); return $result; } function getToken(Invoice $invoice) { $value = $invoice->data()->get(self::DATA_PURCHASE_TOKEN); return $value; } function saveToken(Invoice $invoice, $token) { $invoice->data()->set(self::DATA_PURCHASE_TOKEN, $token)->update(); return $token; } function getEndpoint($type, $sku, $token) { return sprintf( "https://www.googleapis.com/androidpublisher/v3/applications/%s/purchases/%s/%s/tokens/%s", $this->getConfig(self::CONFIG_PACKAGE_NAME), $type, $sku, $token ); } function getObjectInfo($type, Invoice $invoice, $token) { $log = $this->getDi()->invoiceLogRecord; $log->setInvoice($invoice); $sku = $invoice->getItem(0)->getBillingPlanData(self::DATA_PRODUCT_SKU); $req = new Am_HttpRequest($endpoint = $this->getEndpoint($type, $sku, $token), Am_HttpRequest::METHOD_GET); $req->setHeader("Authorization", "Bearer ".$this->getAccessToken()); $resp = $req->send(); $log->add([ 'Request' => $endpoint, 'Response' => $resp->getBody() ]); if ($resp->getStatus() != 200) { throw new Am_Exception_InternalError("Unable to get {$type} object"); } $obj = @json_decode($resp->getBody(), true); if (empty($obj)) { throw new Am_Exception_InternalError("Object {$type} is empty"); } return $obj; } } class Am_Paysystem_Transaction_GoogleIap extends Am_Paysystem_Transaction_Abstract { protected $receipt; function __construct(Am_Paysystem_GoogleIap $plugin, $receipt) { parent::__construct($plugin); $this->receipt = $receipt; } public function getUniqId() { return $this->receipt['orderId']; } function processValidated() { if(@$this->receipt['paymentState'] == 2){ $this->invoice->addAccessPeriod($this); } else if(@$this->receipt['paymentState'] == 1 || (isset($this->receipt['purchaseState']) && $this->receipt['purchaseState']==0) ) { $this->invoice->addPayment($this); } } } class Am_Paysystem_GoogleIap_Transaction_Incoming extends Am_Paysystem_Transaction_Incoming { protected $notification = ''; protected $subscriptionInfo = ''; const SUBSCRIPTION_RECOVERED = 1; const SUBSCRIPTION_RENEWED = 2; const SUBSCRIPTION_CANCELED = 3; const SUBSCRIPTION_PURCHASED = 4; const SUBSCRIPTION_ON_HOLD = 5; const SUBSCRIPTION_IN_GRACE_PERIOD = 6; const SUBSCRIPTION_RESTARTED = 7; const SUBSCRIPTION_PAUSED = 10; const SUBSCRIPTION_REVOKED = 12; const SUBSCRIPTION_EXPIRED = 13; /** * @return Am_Paysystem_GoogleIap|void */ function getPlugin() { return parent::getPlugin(); } public function validateSource() { $body = @json_decode($this->request->getRawBody(), true); if (empty($body)) { return false; } if(!empty($body['data'])) $notification = json_decode(@base64_decode($body['data']), true); elseif(!empty($body['message']['data'])) $notification = json_decode(@base64_decode($body['message']['data']), true); else return false; if (empty($notification)) { return false; } // We are interested only in subscription notifications, since one-time orders are added through api if (empty($notification['subscriptionNotification'])) { return false; } $this->notification = $notification; return true; } function findInvoiceId() { //$token = $this->notification['purchaseToken']; $token = $this->notification['purchaseToken'] ?? $this->notification['subscriptionNotification']['purchaseToken']; $invoice = $this->getPlugin()->getDi()->invoiceTable->findFirstByData(Am_Paysystem_GoogleIap::DATA_PURCHASE_TOKEN, $token); if (empty($invoice)) { throw new Am_Exception_Paysystem_TransactionUnknown("Unable to find invoice for this notification " . $token); } $this->subscriptionInfo = $this->getPlugin()->getObjectInfo(Am_Paysystem_GoogleIap::TYPE_SUBSCRIPTIONS, $invoice, $token); if (!empty($this->subscriptionInfo)) { return $invoice->public_id; } } public function validateTerms() { // TODO: Implement validateTerms() method. return true; } /** * Make sure this transaction is not regarding a pending or failed payment * @return true if OK, false if not */ public function validateStatus() { return true; } /** * Function must return an unique identified of transaction, so the same * transaction will not be handled twice. It can be for example: * txn_id form paypal, invoice_id-payment_sequence_id from other paysystem * invoice_id and random is not accceptable here * timestamped date of transaction is acceptable * @return string (up to 32 chars) */ function getUniqId() { return $this->subscriptionInfo['orderId']; } function processValidated() { switch ($this->notification['notificationType']) { case self::SUBSCRIPTION_RENEWED: if($this->subscriptionInfo['paymentState'] == 2){ $this->invoice->addAccessPeriod($this); }else if($this->subscriptionInfo['paymentState'] == 1){ $this->invoice->addPayment($this); } break; case self::SUBSCRIPTION_REVOKED: case self::SUBSCRIPTION_EXPIRED: $this->invoice->stopAccess($this); case self::SUBSCRIPTION_CANCELED: $this->invoice->setCancelled(true); break; } } }