Newer
Older
/**
* Class Payment
*/
class Payment extends QUI\ERP\Accounting\Payments\Api\AbstractPayment
{
/**
* Amazon API Order attributes
*/
const ATTR_AUTHORIZATION_REFERENCE_IDS = 'amazon-AuthorizationReferenceIds';
const ATTR_AMAZON_AUTHORIZATION_ID = 'amazon-AmazonAuthorizationId';
const ATTR_AMAZON_CAPTURE_ID = 'amazon-AmazonCaptureId';
const ATTR_AMAZON_ORDER_REFERENCE_ID = 'amazon-OrderReferenceId';
const ATTR_CAPTURE_REFERENCE_IDS = 'amazon-CaptureReferenceIds';
const ATTR_ORDER_AUTHORIZED = 'amazon-OrderAuthorized';
const ATTR_ORDER_REFERENCE_SET = 'amazon-OrderReferenceSet';
const ATTR_RECONFIRM_ORDER = 'amazon-ReconfirmOrder';
/**
* Setting options
*/
const SETTING_ARTICLE_TYPE_MIXED = 'mixed';
const SETTING_ARTICLE_TYPE_PHYSICAL = 'physical';
const SETTING_ARTICLE_TYPE_DIGITAL = 'digital';
/**
* Amazon Pay PHP SDK Client
*
* @var AmazonPayClient
*/
protected $AmazonPayClient = null;
/**
* Current Order that is being processed
*
* @var AbstractOrder
*/
protected $Order = null;
/**
* @return string
*/
public function getTitle()
{
return $this->getLocale()->get('quiqqer/payment-amazon', 'payment.title');
}
/**
* @return string
*/
public function getDescription()
{
return $this->getLocale()->get('quiqqer/payment-amazon', 'payment.description');
* This method returns the payment success type
*
* @param string $hash - Vorgangsnummer - hash number - procedure number
* @return bool
*/
public function isSuccessful($hash)
{
try {
$Order = OrderHandler::getInstance()->getOrderByHash($hash);
} catch (\Exception $Exception) {
QUI\System\Log::addError(
'Amazon Pay :: Cannot check if payment process for Order #' . $hash . ' is successful'
. ' -> ' . $Exception->getMessage()
);
return false;
}
if ($Order->getPaymentDataEntry(self::ATTR_ORDER_AUTHORIZED)) {
return true;
}
$orderReferenceId = $Order->getPaymentDataEntry(self::ATTR_AMAZON_ORDER_REFERENCE_ID);
if (empty($orderReferenceId)) {
return false;
}
try {
self::authorizePayment($orderReferenceId, $Order);
} catch (AmazonPayException $Exception) {
return false;
} catch (\Exception $Exception) {
QUI\System\Log::writeException($Exception);
return false;
}
// If payment is authorized everyhting is fine
$Order->setPaymentData(self::ATTR_ORDER_AUTHORIZED, true);
$Order->update(QUI::getUsers()->getSystemUser());
/**
* Is the payment a gateway payment?
*
* @return bool
*/
public function isGateway()
{
return true;
}
/**
* @return bool
*/
public function refundSupport()
{
return true;
}
/**
* Execute the request from the payment provider
*
* @param QUI\ERP\Accounting\Payments\Gateway\Gateway $Gateway
* @throws QUI\ERP\Accounting\Payments\Transactions\Exception
*/
public function executeGatewayPayment(QUI\ERP\Accounting\Payments\Gateway\Gateway $Gateway)
{
$AmazonPay = $this->getAmazonPayClient();
$Order = $Gateway->getOrder();
$this->Order = $Order;
$Order->addHistory('Amazon Pay :: Check if payment from Amazon was successful');
$amazonCaptureId = $Order->getPaymentDataEntry(self::ATTR_AMAZON_CAPTURE_ID);
$Response = $AmazonPay->getCaptureDetails(array(
'amazon_capture_id' => $amazonCaptureId
));
$response = $this->getResponseData($Response);
// check the amount that has already been captured
$calculations = $Order->getArticles()->getCalculations();
$targetSum = $calculations['sum'];
$targetCurrencyCode = $Order->getCurrency()->getCode();
$captureData = $response['GetCaptureDetailsResult']['CaptureDetails'];
$actualSum = $captureData['CaptureAmount']['Amount'];
$actualCurrencyCode = $captureData['CaptureAmount']['CurrencyCode'];
\QUI\System\Log::writeRecursive("COMPARE sums QUIQQER: $actualSum <> AMAZON: $targetSum");
if ($actualSum < $targetSum) {
$Order->addHistory(
'Amazon Pay :: The amount that was captured from Amazon was less than the'
. ' total sum of the order. Total sum: ' . $targetSum . ' ' . $targetCurrencyCode
. ' | Actual sum captured by Amazon: ' . $actualSum . ' ' . $actualCurrencyCode
);
return;
}
// book payment in QUIQQER ERP
$Gateway->purchase(
$actualSum,
}
/**
* Execute a refund
*
* @param QUI\ERP\Accounting\Payments\Transactions\Transaction $Transaction
*/
public function refund(QUI\ERP\Accounting\Payments\Transactions\Transaction $Transaction)
{
// @todo
}
/**
* If the Payment method is a payment gateway, it can return a gateway display
*
* @param AbstractOrder $Order
* @param QUI\ERP\Order\Controls\OrderProcess\Processing $Step
* @return string
*/
public function getGatewayDisplay(AbstractOrder $Order, $Step = null)
{
$Control = new PaymentDisplay();
$Control->setAttribute('Order', $Order);
$Control->setAttribute('Payment', $this);
$Step->setTitle(
QUI::getLocale()->get(
'quiqqer/payment-amazon',
'payment.step.title'
)
);
$Engine = QUI::getTemplateManager()->getEngine();
$Step->setContent($Engine->fetch(dirname(__FILE__) . '/PaymentDisplay.Header.html'));
*
* @param string $orderReferenceId
* @param AbstractOrder $Order
*
* @throws AmazonPayException
*/
public function authorizePayment($orderReferenceId, AbstractOrder $Order)
$Order->addHistory('Amazon Pay :: Authorize payment');
if ($Order->getPaymentDataEntry(self::ATTR_ORDER_AUTHORIZED)) {
$Order->addHistory('Amazon Pay :: Authorization already exist');
return;
}
$AmazonPay = $this->getAmazonPayClient();
$this->Order = $Order;
$calculations = $Order->getArticles()->getCalculations();
$reconfirmOrder = $Order->getPaymentDataEntry(self::ATTR_RECONFIRM_ORDER);
// Re-confirm Order after previously declined Authorization because of "InvalidPaymentMethod"
if ($reconfirmOrder) {
$Order->addHistory(
'Amazon Pay :: Re-confirm Order after declined Authorization because of "InvalidPaymentMethod"'
);
$orderReferenceId = $Order->getPaymentDataEntry(self::ATTR_AMAZON_ORDER_REFERENCE_ID);
$Response = $AmazonPay->confirmOrderReference(array(
'amazon_order_reference_id' => $orderReferenceId
));
$this->getResponseData($Response); // check response data
$Order->setPaymentData(self::ATTR_RECONFIRM_ORDER, false);
$Order->addHistory('Amazon Pay :: OrderReference re-confirmed');
} elseif (!$Order->getPaymentDataEntry(self::ATTR_ORDER_REFERENCE_SET)) {
$Order->addHistory(
'Amazon Pay :: Setting details of the Order to Amazon Pay API'
);
$Response = $AmazonPay->setOrderReferenceDetails(array(
'amazon_order_reference_id' => $orderReferenceId,
'amount' => $calculations['sum'],
'currency_code' => $Order->getCurrency()->getCode(),
'seller_order_id' => $Order->getId()
$response = $this->getResponseData($Response);
$orderReferenceDetails = $response['SetOrderReferenceDetailsResult']['OrderReferenceDetails'];
if (isset($orderReferenceDetails['Constraints']['Constraint']['ConstraintID'])) {
$Order->addHistory(
'Amazon Pay :: An error occurred while setting the details of the Order: "'
. $orderReferenceDetails['Constraints']['Constraint']['ConstraintID'] . '""'
);
$this->throwAmazonPayException(
$orderReferenceDetails['Constraints']['Constraint']['ConstraintID'],
array(
'reRenderWallet' => 1
)
);
}
$AmazonPay->confirmOrderReference(array(
'amazon_order_reference_id' => $orderReferenceId
));
$Order->setPaymentData(self::ATTR_ORDER_REFERENCE_SET, true);
$Order->update(QUI::getUsers()->getSystemUser());
$Order->addHistory('Amazon Pay :: Requesting new Authorization');
$authorizationReferenceId = $this->getNewAuthorizationReferenceId($Order);
$Response = $AmazonPay->authorize(array(
'amazon_order_reference_id' => $orderReferenceId,
'authorization_amount' => $calculations['sum'],
'authorization_reference_id' => $authorizationReferenceId,
'transaction_timeout' => 0 // get authorization status synchronously
// save reference ids in $Order
$authorizationDetails = $response['AuthorizeResult']['AuthorizationDetails'];
$amazonAuthorizationId = $authorizationDetails['AmazonAuthorizationId'];
$this->addAuthorizationReferenceIdToOrder($authorizationReferenceId);
$Order->setPaymentData(self::ATTR_AMAZON_AUTHORIZATION_ID, $amazonAuthorizationId);
$Order->setPaymentData(self::ATTR_AMAZON_ORDER_REFERENCE_ID, $orderReferenceId);
$Order->update(QUI::getUsers()->getSystemUser());
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
// check Authorization
$Order->addHistory('Amazon Pay :: Checking Authorization status');
$status = $authorizationDetails['AuthorizationStatus'];
$state = $status['State'];
switch ($state) {
case 'Open':
// everything is fine
$Order->addHistory(
'Amazon Pay :: Authorization is OPEN an can be used for capturing'
);
$Order->setPaymentData(self::ATTR_ORDER_AUTHORIZED, true);
$Order->update(QUI::getUsers()->getSystemUser());
break;
case 'Declined':
$reason = $status['ReasonCode'];
switch ($reason) {
case 'InvalidPaymentMethod':
$Order->addHistory(
'Amazon Pay :: Authorization was DECLINED. User has to choose another payment method.'
. ' ReasonCode: "' . $reason . '"'
);
$Order->setPaymentData(self::ATTR_ORDER_REFERENCE_SET, true);
$Order->update(QUI::getUsers()->getSystemUser());
$this->throwAmazonPayException($reason, array(
'reRenderWallet' => 1
));
break;
case 'TransactionTimedOut':
$Order->addHistory(
'Amazon Pay :: Authorization was DECLINED. User has to choose another payment method.'
. ' ReasonCode: "' . $reason . '"'
);
$AmazonPay->cancelOrderReference(array(
'amazon_order_reference_id' => $orderReferenceId,
'cancelation_reason' => 'Order #' . $Order->getHash() . ' could not be authorized :: TransactionTimedOut'
));
$Order->setPaymentData(self::ATTR_ORDER_REFERENCE_SET, false);
$Order->update(QUI::getUsers()->getSystemUser());
$this->throwAmazonPayException($reason, array(
'reRenderWallet' => 1,
'orderCancelled' => 1
));
break;
default:
$Order->addHistory(
'Amazon Pay :: Authorization was DECLINED. OrderReference has to be closed. Cannot use Amazon Pay for this Order.'
. ' ReasonCode: "' . $reason . '"'
);
$Response = $AmazonPay->getOrderReferenceDetails(array(
'amazon_order_reference_id' => $orderReferenceId
));
$response = $Response->toArray();
$orderReferenceDetails = $response['GetOrderReferenceDetailsResult']['OrderReferenceDetails'];
$orderReferenceStatus = $orderReferenceDetails['OrderReferenceStatus']['State'];
if ($orderReferenceStatus === 'Open') {
$AmazonPay->cancelOrderReference(array(
'amazon_order_reference_id' => $orderReferenceId,
'cancelation_reason' => 'Order #' . $Order->getHash() . ' could not be authorized'
));
$Order->setPaymentData(self::ATTR_AMAZON_ORDER_REFERENCE_ID, false);
$Order->update(QUI::getUsers()->getSystemUser());
}
}
break;
default:
$reason = $status['ReasonCode'];
$Order->addHistory(
'Amazon Pay :: Authorization cannot be used because it is in state "' . $state . '".'
. ' ReasonCode: "' . $reason . '"'
);
* Capture the actual payment for an Order
*
* @param AbstractOrder $Order
* @return void
* @throws AmazonPayException
*/
public function capturePayment(AbstractOrder $Order)
{
$this->Order = $Order;
$AmazonPay = $this->getAmazonPayClient();
$orderReferenceId = $Order->getPaymentDataEntry(self::ATTR_AMAZON_ORDER_REFERENCE_ID);
if (empty($orderReferenceId)) {
$Order->addHistory(
'Amazon Pay :: Capture failed because the Order has no AmazonOrderReferenceId'
);
throw new AmazonPayException(array(
'quiqqer/payment-amazon',
'exception.Payment.capture.not_authorized',
array(
'orderHash' => $Order->getHash()
)
));
}
try {
$this->authorizePayment($orderReferenceId, $Order);
} catch (AmazonPayException $Exception) {
'Amazon Pay :: Capture failed because the Order has no OPEN Authorization'
throw new AmazonPayException(array(
'quiqqer/payment-amazon',
'exception.Payment.capture.not_authorized',
array(
'orderHash' => $Order->getHash()
)
));
} catch (\Exception $Exception) {
$Order->addHistory(
'Amazon Pay :: Capture failed because of an error: ' . $Exception->getMessage()
);
QUI\System\Log::writeException($Exception);
return;
}
$calculations = $Order->getArticles()->getCalculations();
$sum = $calculations['sum'];
// check if $sum was already fully captured
$amazonCaptureId = $Order->getPaymentDataEntry(self::ATTR_AMAZON_CAPTURE_ID);
if (!empty($amazonCaptureId)) {
$Order->addHistory('Amazon Pay :: Capture already exists');
$Response = $AmazonPay->getCaptureDetails(array(
'amazon_capture_id' => $amazonCaptureId
));
\QUI\System\Log::writeRecursive($this->getResponseData($Response));
return;
}
$captureReferenceId = $this->getNewCaptureReferenceId($Order);
$Response = $AmazonPay->capture(array(
'amazon_authorization_id' => $Order->getPaymentDataEntry(self::ATTR_AMAZON_AUTHORIZATION_ID),
'capture_amount' => $sum,
'currency_code' => $Order->getCurrency()->getCode(),
'capture_reference_id' => $captureReferenceId
));
$response = $this->getResponseData($Response);
$captureDetails = $response['CaptureResult']['CaptureDetails'];
$amazonCaptureId = $captureDetails['AmazonCaptureId'];
$this->addCaptureReferenceIdToOrder($amazonCaptureId);
$Order->setPaymentData(self::ATTR_AMAZON_CAPTURE_ID, $amazonCaptureId);
$Order->update(QUI::getUsers()->getSystemUser());
$this->checkCaptureStatus($captureDetails);
$Order->addHistory(
'Amazon Pay :: Capture successful -> ' . $calculations['sum'] . ' ' . $Order->getCurrency()->getCode()
);
}
/**
* Set the Amazon Pay OrderReference to status CLOSED
*
* @param AbstractOrder $Order
* @param string $reason (optional) - Close reason [default: "Order #hash completed"]
protected function closeOrderReference(AbstractOrder $Order, $reason = null)
$orderReferenceId = $Order->getPaymentDataEntry(self::ATTR_AMAZON_ORDER_REFERENCE_ID);
$AmazonPay->closeOrderReference(array(
'amazon_order_reference_id' => $orderReferenceId,
'closure_reason' => $reason ?: 'Order #' . $Order->getHash() . ' completed'
));
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
/**
* Check CaptureDetails of an Amazon Pay Authorization
*
* If "State" is "Completed" everything is fine and the payment is completed
*
* @param array $captureDetails
* @return void
* @throws AmazonPayException
*/
public function checkCaptureStatus($captureDetails)
{
$this->Order->addHistory('Amazon Pay :: Checking Capture status');
$status = $captureDetails['CaptureStatus'];
$state = $status['State'];
switch ($state) {
case 'Completed':
$this->Order->addHistory(
'Amazon Pay :: Capture is COMPLETED'
);
return; // everything is fine
break;
default:
$this->Order->addHistory(
'Amazon Pay :: Capture operation failed with state "' . $state . '".'
. ' ReasonCode: "' . $status['ReasonCode'] . '"'
);
$this->throwAmazonPayException($status['ReasonCode']);
break;
}
}
/**
* Check if the Amazon Pay API response is OK
*
* @param ResponseInterface $Response - Amazon Pay API Response
* @return array
* @throws AmazonPayException
*/
protected function getResponseData(ResponseInterface $Response)
{
$response = $Response->toArray();
if (!empty($response['Error']['Code'])) {
$this->throwAmazonPayException($response['Error']['Code']);
}
return $response;
}
/**
* Throw AmazonPayException for specific Amazon API Error
* @param array $exceptionAttributes (optional) - Additional Exception attributes that may be relevant for the Frontend
* @return string
*
* @throws AmazonPayException
*/
protected function throwAmazonPayException($errorCode, $exceptionAttributes = array())
$L = $this->getLocale();
$lg = 'quiqqer/payment-amazon';
$msg = $L->get($lg, 'payment.error_msg.general_error');
$msg = $L->get($lg, 'payment.error_msg.' . $errorCode);
break;
}
$Exception = new AmazonPayException($msg);
throw $Exception;
}
/**
* Generate a unique, random Authorization Reference ID to identify
* authorization transactions for an order
*
* @param AbstractOrder $Order
* @return string
*/
protected function getNewAuthorizationReferenceId(AbstractOrder $Order)
{
return mb_substr('a_' . $Order->getId() . '_' . uniqid(), 0, 32);
*
* @param string $authorizationReferenceId
* @return void
*/
protected function addAuthorizationReferenceIdToOrder($authorizationReferenceId)
$authorizationReferenceIds = $this->Order->getPaymentDataEntry(self::ATTR_AUTHORIZATION_REFERENCE_IDS);
if (empty($authorizationReferenceIds)) {
$authorizationReferenceIds = array();
}
$authorizationReferenceIds[] = $authorizationReferenceId;
$this->Order->setPaymentData(self::ATTR_AUTHORIZATION_REFERENCE_IDS, $authorizationReferenceIds);
$this->Order->update(QUI::getUsers()->getSystemUser());
}
/**
* Generate a unique, random CaptureReferenceId to identify
* captures for an order
*
* @param AbstractOrder $Order
* @return string
*/
protected function getNewCaptureReferenceId(AbstractOrder $Order)
return mb_substr('c_' . $Order->getId() . '_' . uniqid(), 0, 32);
*
* @param string $captureReferenceId
* @return void
*/
protected function addCaptureReferenceIdToOrder($captureReferenceId)
$captureReferenceIds = $this->Order->getPaymentDataEntry(self::ATTR_CAPTURE_REFERENCE_IDS);
if (empty($captureReferenceIds)) {
$captureReferenceIds = array();
}
$captureReferenceIds[] = $captureReferenceId;
$this->Order->setPaymentData(self::ATTR_CAPTURE_REFERENCE_IDS, $captureReferenceIds);
$this->Order->update(QUI::getUsers()->getSystemUser());
}
/**
* Get Amazon Pay Client for current payment process
*
* @return AmazonPayClient
*/
protected function getAmazonPayClient()
{
if (!is_null($this->AmazonPayClient)) {
return $this->AmazonPayClient;
}
$this->AmazonPayClient = new AmazonPayClient(array(
'merchant_id' => Provider::getApiSetting('merchant_id'),
'access_key' => Provider::getApiSetting('access_key'),
'secret_key' => Provider::getApiSetting('secret_key'),
'client_id' => Provider::getApiSetting('client_id'),
'sandbox' => boolval(Provider::getApiSetting('sandbox')),
'region' => Provider::getApiSetting('region')
));
return $this->AmazonPayClient;
}