File: D:/HostingSpaces/SBogers10/stafa.komma.pro/app/Komma/Shop/Payment/PSPAdapters/Mollie.php
<?php
namespace App\Komma\Shop\Payment\PSPAdapters;
use App\Komma\Globalization\RegionInfo;
use App\Komma\Shop\Orders\Models\Order;
use App\Komma\Shop\Orders\OrderStatus;
use App\Komma\Shop\Payment\TransactionStatus as KmsPaymentStatus;
use App\Komma\Shop\Payment\TransactionStatus;
use Illuminate\Routing\UrlGenerator;
use Mollie\Api\Resources\Payment;
use Mollie\Api\Types\PaymentStatus as MolliePaymentStatus;
use App\Komma\Shop\Payment\Transaction;
use App\Komma\Users\Models\KmsUser;
use Carbon\Carbon;
use Illuminate\Http\RedirectResponse;
use Mollie\Api\Exceptions\ApiException;
use Mollie\Api\MollieApiClient;
use Symfony\Component\HttpFoundation\Response;
class Mollie extends AbstractPSPAdapter
{
/** @var MollieApiClient A client provided by the PSP to communicate with their api */
private $paymentServiceProviderClient;
/**
* @var array an array of supported locales mollie supports
* @see https://docs.mollie.com/reference/v2/payments-api/create-payment
*/
private $supportedLocales = [
'en_US',
'nl_NL',
'nl_BE',
'fr_FR',
'fr_BE',
'de_DE',
'de_AT',
'de_CH',
'es_ES',
'ca_ES',
'pt_PT',
'it_IT',
'nb_NO',
'sv_SE',
'fi_FI',
'da_DK',
'is_IS',
'hu_HU',
'pl_PL',
'lv_LV',
'lt_LT',
];
/** @var string The name of the website as to include in the external payment environment */
private $shopName;
/**
* Mollie constructor.
* @param MollieApiClient $apiClient
* @throws ApiException
*/
public function __construct(MollieApiClient $apiClient)
{
parent::__construct();
$this->pspName = 'mollie';
$apiKey = config('payment.payment_service_providers.'. $this->pspName . '.' . $this->environment . '.api_keys.key');
$this->shopName = config('payment.payment_service_providers.'. $this->pspName . '.' . $this->environment . '.shop_name');
$this->paymentServiceProviderClient = $apiClient;
$this->paymentServiceProviderClient->setApiKey($apiKey);
}
/**
* Creates a payment transaction for a certain order.
*
* @param Order $order
* @return Transaction
* @throws ApiException
*/
public function createTransaction(Order $order): Transaction
{
/** @var KmsUser $customer */
$customer = $order->customer()->first();
$shopRegionInfo = new RegionInfo('NL'); //Shop was build with euros in mind and the Dutch language as the primary one
//Check if the customers culture is supported by mollie. Else default to the english one
$userLocale = str_replace('-', '_', $customer->culture);
$locale = (in_array($userLocale, $this->supportedLocales, true)) ? $userLocale : 'en_US';
//Create the payment at the PSP side and get a payment instance in which they describe the status of that payment
$paymentData = [
"amount" => [
"currency" => $shopRegionInfo->getISOCurrencySymbol(),
"value" => (string)$shopRegionInfo->getNumberFormat()->centsToCurrency($order->total_price, false)
],
"description" => "Order #" . $order->id . ' (' . $this->shopName . ')',
"redirectUrl" => route('transaction.view.general', ['order' => $order->id]),
"webhookUrl" => route('transaction.statusupdate', ['order' => $order->id]),
"metadata" => [
"order_id" => $order->id,
],
"locale" => $locale //e.g. nl-BE for example
];
$payment = $this->paymentServiceProviderClient->payments->create($paymentData);
$transaction = new Transaction();
$transaction->psp = $this->pspName;
$transaction->status = TransactionStatus::OPEN;
$transaction = $this->updateTransactionUsingMolliePayment($payment, $transaction);
$transaction->order()->associate($order);
$transaction->ip = request()->ip();
$transaction->currency_iso_4217_code = $shopRegionInfo->getISOCurrencySymbol();
$transaction->amount = $order->total_price;
$transaction->save();
return $transaction;
}
/**
* Updates a kms shop transaction using the payment from a mollie transaction.
* Notice. You need to save the transaction yourself.
*
* @param Payment $payment
* @param Transaction $transaction
* @return Transaction
* @throws \Exception
*/
private function updateTransactionUsingMolliePayment(Payment $payment, Transaction $transaction): Transaction
{
if($payment->id) $transaction->psp_id = $payment->id;
if($payment->getCheckoutUrl()) $transaction->payment_link = $payment->getCheckoutUrl();
if($payment->method) $transaction->payment_method = $payment->method;
if($payment->status) $transaction->status = $this->adaptPSPTransactionStatusToKmsTransactionStatus($payment->status); //see transactions translation file. This value must match a key in the status array.
if($payment->expiresAt) $transaction->expire_date = $this->adaptMollieDateStringToCarbon($payment->expiresAt);
if($payment->paidAt) $transaction->payment_date = $this->adaptMollieDateStringToCarbon($payment->paidAt);
return $transaction;
}
/**
* Updates the payment status for a certain order payment transaction.
*
* Must be triggered when the PSP has an update for a certain payment transaction.
* To indicate for example that a payment transaction was completed successfully.
*
* Important. When this method is done processing. It SHOULD call the saveTransactionAndNotify method.
* This to tell listeners that a transaction was changed.
* It SHOULD in general return a json response. But MAY also return a view after updating the transation.
*
* @param Order|null $order
* @return Response
*/
public function processPSPResponse(Order $order = null): Response
{
\Log::debug(' '); //Empty line in log
//Validate the request
$pspID = \Input::get('id');
if(!$pspID) {
\Log::error('Mollie Adapter: Mollie wanted to update a transaction without specifying it\'s id. Not updating any transaction.');
return response()->json(['Please provide a transaction id using the key "id"'], 400);
}
\Log::debug('Mollie Adapter: Mollie called kms with a status update for transaction with psp_id: '.$pspID.'. Updating transaction....');
$transactionQuery = Transaction::where('psp_id', '=', $pspID)->where('psp', '=', $this->pspName);
if($order) {
$transactionQuery->where('order_id', '=', $order->id);
}
$transaction = $transactionQuery->first();
if(!$transaction) {
if(!$order)\Log::debug('Mollie Adapter: Could not find transaction. psp_id: '.$pspID.'. psp: '.$this->pspName);
else \Log::debug('Mollie Adapter: Could not find transaction. psp_id: '.$pspID.'. psp: '.$this->pspName.' order id: '.$order->id);
return response()->json(['The transaction / payment could not be found'], 404);
}
//Try to retrieve the payment from mollie using the id
$payment = null;
try {
$payment = $this->paymentServiceProviderClient->payments->get($pspID);
} catch (\Exception $e) {
\Log::error('Mollie Adapter: Tried to get a payment with psp_id '.$pspID.' from mollie. But an ApiException occured:');
\Log::error($e->getMessage());
\Log::error($e->getTraceAsString());
return response()->json(['Did try to get the payment from Mollie. But the process did throw an ApiException. '.$e->getMessage()], 400);
}
//Try to update the transaction using the payment from mollie.
try {
$transaction = $this->updateTransactionUsingMolliePayment($payment, $transaction);
$this->saveTransactionAndNotify($transaction);
} catch (\Exception $e) {
\Log::error('Mollie Adapter: Tried update a kms transaction using a Mollie payment, but an exception occured: ');
\Log::error($e->getMessage());
\Log::error($e->getTraceAsString());
return response()->json(['Tried to update a payment but could not because of an exception. Message: '.$e->getMessage()], 400);
}
\Log::debug('Mollie Adapter: Transaction with psp_id "'.$pspID.'" successfully updated! Also telling that to mollie with http status 200. Transaction status: '.$transaction->status);
return response()->json('Payment updated successfully!', 200); //Status 200 let's mollie know we processed the request successfully, and that they don't need to trie again.
}
/**
* Return a response that redirects the user to a page
* where the user can select a payment method (credit card, iDEAL etc) and issuer (bank),
* and complete the payment
*
* @param Transaction $transaction
* @return RedirectResponse
* @throws ApiException
*/
public function redirectForPayment(Transaction $transaction): RedirectResponse
{
$payment = $this->paymentServiceProviderClient->payments->get($transaction->psp_id);
$to = $payment->getCheckoutUrl();
$transaction->status = TransactionStatus::PAYMENT_PENDING;
$transaction->save();
$transaction->order->status = OrderStatus::PENDING;
$transaction->order->save();
return response()->redirectTo($to, 303); //303 forces a GET redirect according to Mollie.
}
/**
* Converts a mollie date string like 2019-01-16T10:37:27+00:00
* to a carbon instance
* @param string $date
* @return Carbon
*/
public function adaptMollieDateStringToCarbon(string $date)
{
$carbon = Carbon::parse($date);
return $carbon;
}
/**
* Converts one of the mollie payment statuses to kms transaction statuses
*
* @param $status
* @return string the Kms shop transaction status
* @see TransactionStatus
* @see transactions.php translation file status array.
* @throws \Exception
*/
public function adaptPSPTransactionStatusToKmsTransactionStatus($status): string
{
switch ($status) {
case MolliePaymentStatus::STATUS_AUTHORIZED:
return KmsPaymentStatus::AUTHORIZED;
case MolliePaymentStatus::STATUS_OPEN:
return KmsPaymentStatus::OPEN;
case MolliePaymentStatus::STATUS_CANCELED:
return KmsPaymentStatus::CANCELED_CUSTOMER;
case MolliePaymentStatus::STATUS_EXPIRED:
return KmsPaymentStatus::EXPIRED;
case MolliePaymentStatus::STATUS_FAILED:
return KmsPaymentStatus::PAYMENT_UNKNOWN;
case MolliePaymentStatus::STATUS_PAID:
return KmsPaymentStatus::PAYMENT_PAID;
case MolliePaymentStatus::STATUS_PENDING:
return KmsPaymentStatus::PAYMENT_PENDING;
}
throw new \Exception('Mollie: Received an unknown payment status from Mollie for Kms: '.$status);
}
/**
* Converts a payment status from the kms shop to a payment status that the psp understands
*
* @param $status
* @return mixed
* @throws \Exception
*/
public function adaptKmsTransactionStatusToPSPTransactionStatus(string $status)
{
switch ($status) {
case KmsPaymentStatus::AUTHORIZED:
return MolliePaymentStatus::STATUS_AUTHORIZED;
case KmsPaymentStatus::OPEN:
return MolliePaymentStatus::STATUS_OPEN;
case KmsPaymentStatus::CANCELED:
case KmsPaymentStatus::CANCELED_CUSTOMER:
return MolliePaymentStatus::STATUS_CANCELED;
case KmsPaymentStatus::EXPIRED:
return MolliePaymentStatus::STATUS_EXPIRED;
case KmsPaymentStatus::PAYMENT_UNKNOWN:
case KmsPaymentStatus::AUTHORISATION_REJECTED:
case KmsPaymentStatus::AUTHORISATION_UNKNOWN:
return MolliePaymentStatus::STATUS_FAILED;
case KmsPaymentStatus::REFUNDED:
case KmsPaymentStatus::CHARGEDBACK:
case KmsPaymentStatus::PAYMENT_PAID:
return MolliePaymentStatus::STATUS_PAID;
case KmsPaymentStatus::AUTHORISATION_PENDING:
case KmsPaymentStatus::CANCEL_PENDING:
case KmsPaymentStatus::CHARGEBACK_PENDING:
case KmsPaymentStatus::REFUND_PENDING:
case KmsPaymentStatus::PAYMENT_PENDING:
case KmsPaymentStatus::REDIRECTED_USER_TO_PSP:
return MolliePaymentStatus::STATUS_PENDING;
}
throw new \Exception('Mollie: Received an unknown payment status from KMS for Mollie: '.$status);
}
}