HEX
Server: Microsoft-IIS/8.5
System: Windows NT YDAWBH120 6.3 build 9600 (Windows Server 2012 R2 Standard Edition) AMD64
User: tentjecom_web (0)
PHP: 7.4.14
Disabled: NONE
Upload Files
File: D:/HostingSpaces/stafa/werkenbijstafa.nl/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);
    }
}