Newer
Older
use QUI\ERP\Accounting\Payments\Transactions\Factory as TransactionFactory;
use QUI\ERP\Accounting\Payments\Transactions\Transaction;
use QUI\ERP\Order\AbstractOrder;
use QUI\ERP\Order\Handler as OrderHandler;
use Stripe\Exception\ApiErrorException;
use Stripe\PaymentIntent as StripePaymentIntent;
abstract class AbstractBasePayment extends QUI\ERP\Accounting\Payments\Api\AbstractPayment
const ATTR_STRIPE_PAYMENT_INTENT_ID = 'stripe-PaymentIntentId';
const ATTR_STRIPE_PAYMENT_METHOD_ID = 'stripe-PaymentMethodId';
const ATTR_STRIPE_REFUND_ID = 'stripe-RefundId';
const ATTR_STRIPE_ORDER_SUCCESSFUL = 'stripe-OrderSuccessful';
const ATTR_STRIPE_BILLING_PLAN_ID = 'stripe-BillingPlanId';
const ATTR_STRIPE_SUBSCRIPTION_ID = 'stripe-SubscriptionId';
const ATTR_STRIPE_INVOICE_ID = 'stripe-InvoiceId';
/**
* Stripe User attributes
*/
const USER_ATTR_STRIPE_CUSTOMER_ID = 'quiqqer.payment.stripe.customer_id';
const ERROR_GENERAL = 'error_general';
const ERROR_REFUND = 'error_refund';
const ERROR_REFUND_STATUS = 'error_refund_status';
const ERROR_REFUND_MISSING_PAYMENT_INTENT_ID = 'error_missing_payment_intent_id';
/**
* Get title for frontend
*
* @return string
*/
abstract public function getFrontendTitle();
/**
* Get description for frontend
*
* @return string
*/
abstract public function getFrontendDescription();
/**
* Get title for the Payment step (OrderProcess)
*
* @return string
*/
abstract public function getPaymentStepTitle();
/**
* Get description step for the Payment step (OrderProcess)
*
* @return string
*/
abstract public function getPaymentStepInfo();
/**
* Get type string of Stripe PaymentMethod
*
* @return string
*/
abstract public function getPaymentMethodType();
/**
* Is the payment process successful?
* 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) {
return $Order->getPaymentDataEntry(self::ATTR_STRIPE_ORDER_SUCCESSFUL);
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
}
/**
* Is the payment a gateway payment?
*
* @return bool
*/
public function isGateway()
{
return true;
}
/**
* @return bool
*/
public function refundSupport()
{
return true;
}
/**
* Execute a refund
*
* @param QUI\ERP\Accounting\Payments\Transactions\Transaction $Transaction
* @param int|float $amount
* @param string $message
* @param false|string $hash - if a new hash will be used
* @throws QUI\ERP\Accounting\Payments\Transactions\RefundException
*/
public function refund(
Transaction $Transaction,
$amount,
$message = '',
$hash = false
) {
try {
if ($hash === false) {
$hash = $Transaction->getHash();
}
$this->refundPayment($Transaction, $hash, $amount, $message);
QUI\System\Log::writeDebugException($Exception);
throw new QUI\ERP\Accounting\Payments\Transactions\RefundException([
'exception.BasePayment.refund_error_stripe',
[
'stripeError' => $Exception->getMessage()
]
]);
} catch (\Exception $Exception) {
QUI\System\Log::writeException($Exception);
throw new QUI\ERP\Accounting\Payments\Transactions\RefundException([
]);
}
}
/**
* 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
*
* @throws QUI\Exception
*/
public function getGatewayDisplay(AbstractOrder $Order, $Step = null)
{
$type = $this->getPaymentMethodType();
$Control = new PaymentDisplay([
'infoText' => $this->getPaymentStepInfo()
$stepTitle = $this->getPaymentStepTitle();
$Step->setTitle($stepTitle);
$Engine->assign([
'stepTitle' => $stepTitle
]);
$Step->setContent($Engine->fetch(dirname(__FILE__) . '/PaymentDisplay.Header.html'));
* @param string $paymentMethodId - Stripe PaymentMethod ID
* @return StripePaymentIntent
public function createPaymentIntent(AbstractOrder $Order, $paymentMethodId)
if (!empty($Order->getPaymentDataEntry(self::ATTR_STRIPE_PAYMENT_INTENT_ID))) {
// Re-set PaymentMethod
if (empty($PaymentIntent->payment_method)) {
$PaymentIntent = StripePaymentIntent::update($PaymentIntent->id, [
'payment_method' => $paymentMethodId
]);
}
return $PaymentIntent;
}
$PaymentIntent = $this->createPaymentIntentForOrder($Order, $paymentMethodId);
$Order->setPaymentData(self::ATTR_STRIPE_PAYMENT_INTENT_ID, $PaymentIntent->id);
$this->addOrderHistoryEntry($Order, 'PaymentIntent ' . $PaymentIntent->id . ' created.');
/**
* Return attributes for creating a PaymentIntent
*
* @param AbstractOrder $Order
* @param string $paymentMethodId - Stripe PaymentMethod ID
* @return StripePaymentIntent
*/
abstract protected function createPaymentIntentForOrder(AbstractOrder $Order, $paymentMethodId);
/**
* Confirm a Stripe PaymentIntent
*
* @param AbstractOrder $Order
* @param StripePaymentIntent $PaymentIntent
* @return array - confirmation data
*
* @throws ApiErrorException
*/
public function confirmPaymentIntent(AbstractOrder $Order, StripePaymentIntent $PaymentIntent)
{
if (empty($PaymentIntent->payment_method)) {
$this->addOrderHistoryEntry($Order, 'Payment method validation fail. Re-fetch payment method from user.');
if ($Order->getPaymentDataEntry(self::ATTR_STRIPE_ORDER_SUCCESSFUL)) {
return ['status' => 'success'];
$this->addOrderHistoryEntry($Order, 'Confirming PaymentIntent.');
if ($PaymentIntent->status === $PaymentIntent::STATUS_REQUIRES_CONFIRMATION) {
$PaymentIntent = $PaymentIntent->confirm();
}
if (
$PaymentIntent->status === $PaymentIntent::STATUS_REQUIRES_ACTION
&& $PaymentIntent->next_action->type === 'use_stripe_sdk'
) {
$confirmData['status'] = 'action_required';
$confirmData['clientSecret'] = $PaymentIntent->client_secret;
$this->addOrderHistoryEntry($Order, 'Additional user action required for PaymentIntent confirmation.');
} elseif ($PaymentIntent->status === $PaymentIntent::STATUS_SUCCEEDED) {
$confirmData['status'] = 'success';
$this->addOrderHistoryEntry($Order, 'PaymentIntent successully confirmed.');
$Order->setPaymentData(self::ATTR_STRIPE_ORDER_SUCCESSFUL, true);
$this->saveOrder($Order);
$Transaction = Gateway::getInstance()->purchase(
$PaymentIntent->amount_received / 100,
QUI\ERP\Currency\Handler::getCurrency(mb_strtoupper($PaymentIntent->currency)),
$Transaction->setData(
self::ATTR_STRIPE_PAYMENT_INTENT_ID,
$Order->getPaymentDataEntry(self::ATTR_STRIPE_PAYMENT_INTENT_ID)
);
} catch (\Exception $Exception) {
QUI\System\Log::writeException($Exception);
'Error while trying to set order as successful: ' . $Exception->getMessage()
} else {
$confirmData['status'] = 'error';
$this->addOrderHistoryEntry($Order, 'Confirmation error.');
/**
* Get Stripe PaymentIntent by order
*
* @param AbstractOrder $Order
* @return false|StripePaymentIntent - PaymentIntent or false if not found
*
* @throws ApiErrorException
* @throws StripeException
*/
public function getPaymentIntentByOrder(AbstractOrder $Order)
{
$paymentIntentId = $Order->getPaymentDataEntry(self::ATTR_STRIPE_PAYMENT_INTENT_ID);
if (empty($paymentIntentId)) {
throw new StripeException([
'quiqqer/payment-stripe',
'exception.BasePayment.getPaymentIntentByOrder.payment_intent_id_missing',
[
'orderHash' => $Order->getHash()
]
]);
}
/**
* Refund partial or full payment of an Order
*
* @param QUI\ERP\Accounting\Payments\Transactions\Transaction $Transaction
* @param string $refundHash - Hash of the refund Transaction
* @param string $reason (optional) - The reason for the refund [default: none; max. 255 characters]
* @return void
*
* @throws QUI\Exception
*/
public function refundPayment(Transaction $Transaction, $refundHash, $amount, $reason = '')
{
$Process = new QUI\ERP\Process($Transaction->getGlobalProcessId());
$Process->addHistory('Stripe :: Start refund for transaction #' . $Transaction->getTxId());
if (!$Transaction->getData(self::ATTR_STRIPE_PAYMENT_INTENT_ID)) {
$Process->addHistory(
'PayPal :: Transaction cannot be refunded because it is not yet captured / completed.'
);
$this->throwStripeException(
self::ERROR_REFUND_MISSING_PAYMENT_INTENT_ID,
[
'globalProcessingId' => $Transaction->getGlobalProcessId()
]
);
$paymentIntentId = $Transaction->getData(self::ATTR_STRIPE_PAYMENT_INTENT_ID);
$Currency = $Transaction->getCurrency();
$RefundTransaction = TransactionFactory::createPaymentRefundTransaction(
$amount,
$refundHash,
$Transaction->getPayment()->getName(),
[
'isRefund' => 1,
],
null,
false,
$Transaction->getGlobalProcessId()
);
$RefundTransaction->pending();
$PaymentIntent = StripePaymentIntent::retrieve($paymentIntentId);
$AmountValue = new QUI\ERP\Accounting\CalculationValue($amount, $Transaction->getCurrency(), 2);
$refundAmount = $AmountValue->get() * 100; // convert to smallest currency unit
'charge' => $PaymentIntent->charges->data[0]->id,
'amount' => $refundAmount,
// 'reason' => StripeRefund::REASON_REQUESTED_BY_CUSTOMER,
'metadata' => [
'refundTxId' => $RefundTransaction->getTxId()
]
]);
} catch (\Exception $Exception) {
QUI\System\Log::writeException($Exception);
$Process->addHistory(
QUI::getLocale()->get(
'quiqqer/payment-stripe',
'history.refund.error_api',
[
switch ($Refund->status) {
case StripeRefund::STATUS_SUCCEEDED:
$RefundTransaction->complete();
$RefundTransaction->setData(self::ATTR_STRIPE_REFUND_ID, $Refund->id);
$RefundTransaction->updateData();
$Process->addHistory(
QUI::getLocale()->get(
]
)
);
QUI::getEvents()->fireEvent('transactionSuccessfullyRefunded', [
$RefundTransaction,
$this
]);
break;
case StripeRefund::STATUS_PENDING:
$RefundTransaction->pending();
$RefundTransaction->setData(self::ATTR_STRIPE_REFUND_ID, $Refund->id);
$RefundTransaction->updateData();
$Process->addHistory(
QUI::getLocale()->get(
'quiqqer/payment-stripe',
'history.refund.pending',
[
'refundId' => $Refund->id,
$RefundTransaction->error();
$stripeFailureReason = '';
if (!empty($Refund->failure_reason)) {
$stripeFailureReason = $Refund->failure_reason;
}
QUI::getLocale()->get(
'quiqqer/payment-stripe',
'history.refund.error_status',
[
'refundId' => $Refund->id,
'refundStatus' => $Refund->status,
'amount' => $amount,
'currency' => $Currency->getCode(),
'txId' => $Transaction->getTxId()
$this->throwStripeException(self::ERROR_REFUND_STATUS, ['refundStatus' => $Refund->status]);
protected function addOrderHistoryEntry(AbstractOrder $Order, $msg)
$Order->addHistory('Stripe :: ' . $msg);
*
* @param string $errorCode (optional) - default: general error message
* @param array $exceptionAttributes (optional) - Additional Exception attributes that may be relevant for the Frontend
* @return string
*
protected function throwStripeException($errorCode = self::ERROR_GENERAL, $exceptionAttributes = [])
$L = $this->getLocale();
$lg = 'quiqqer/payment-stripe';
$msg = $L->get($lg, 'exception.BasePayment.' . $errorCode);
$Exception->setAttributes($exceptionAttributes);
throw $Exception;
}
/**
* Save Order with SystemUser
*
* @param AbstractOrder $Order
* @return void
*/
protected function saveOrder(AbstractOrder $Order)
{
$Order->update(QUI::getUsers()->getSystemUser());
}
}