Your IP : 216.73.216.79


Current Path : /var/www/html/crowdfunding/frontend/controllers/
Upload File :
Current File : /var/www/html/crowdfunding/frontend/controllers/PaymentController.php

<?php

namespace frontend\controllers;

/***
ini_set('display_startup_errors', 1);
ini_set('display_errors', 1);
error_reporting(-1);
***/

use Yii;
use yii\helpers\Url;
use yii\web\Controller;
use yii\filters\VerbFilter;
use backend\modules\payment\models\Payment;
use backend\modules\payment\models\PaymentCallback;
use backend\modules\payment\models\radiuz\Payment as RadiuzPayment;
use backend\modules\payment\models\radiuz\PaymentCallback as RadiuzCallback;
use common\components\payment\PaymentProxy;
use common\components\payment\provider\ZakatFpx;
use common\components\payment\provider\zakatfpx\PaymentFields;
use common\components\payment\provider\zakatfpx\models\PaymentProviderZakatfpx;
use common\components\payment\provider\zakatfpx\models\RadiuzPaymentProviderZakatfpx;
use frontend\models\PaymentRadiuz;
use bedezign\yii2\audit\components\web\ErrorHandler;

class PaymentController extends Controller
{

    /**
     * zakat type base on zakat fpx type code
     */
    const ZAKAT_TYPE = '030';

    /**
     *  crowd zakat order number prefix
     */
    const CROWD_ZAKAT_PREFIX = 'CRW';

    /**
     *  Affiliate zakat order number prefix
     */
    const RADIUZ_ZAKAT_PREFIX = 'RDZ';

    /**
     *  return URL for crowd
     */
    public static function CHANNEL()
    {
        return Yii::$app->params['payment_gateway']['zakat_fpx']['channel'];
    }

    /**
     *  return URL for crowd
     */
    public static function ID_PREFIX()
    {
        return Yii::$app->params['payment_gateway']['zakat_fpx']['id_prefix'];
    }

    /**
     *  return URL for crowd
     */
    public static function BASE_CROWD_URL()
    {
        return Yii::$app->params['payment_gateway']['zakat_fpx']['response']['base_url']['crowd'];
    }

    /**
     *  return URL for crowd
     */
    public static function BASE_RADIUZ_URL()
    {
        return Yii::$app->params['payment_gateway']['zakat_fpx']['response']['base_url']['radiuz'];
    }

    /**
     *  return URL for crowd
     */
    public static function SUCCESS_URL()
    {
        return Yii::$app->params['payment_gateway']['zakat_fpx']['response']['success_url'];
    }

    /**
     *  return URL for radiuz
     */
    public static function FAILED_URL()
    {
        return Yii::$app->params['payment_gateway']['zakat_fpx']['response']['failed_url'];
    }


    /**
     *  get response token
     */
    public static function RESPONSE_TOKEN()
    {
        return Yii::$app->params['payment_gateway']['zakat_fpx']['response']['token'];
    }

    /**
     * @inheritdoc
     */
    public function beforeAction($action)
    {
        $whitelist = ['pay-callback', 'pay', 'response-zakat-fpx'];

        if (in_array($action->id, $whitelist)) {
            $this->enableCsrfValidation = false;
        }

        return parent::beforeAction($action);
    }

    /**
     * @inheritdoc
     */
    public function behaviors()
    {
        return [
            'verbs' => [
                'class' => VerbFilter::className(),
                'actions' => [
                    'payment-redirect' => ['POST'],
                    'pay' => ['GET']
                ],
            ],
        ];
    }

    public function actionRequestZakatFpx($id)
    {
        try {
            $payment = Payment::findOne($id);

            /** check payment id, if it unused */
            if (empty($payment) || $payment->payment_status != "pendingpay") {
                # throw new \Exception('invalid payment id');
            }

            $uniqid = ZakatFpx::generateId(self::ID_PREFIX(), $id);

            $payment_proxy = new PaymentProxy(
                new ZakatFpx(
                    (new PaymentFields())
                        ->name($payment->payment_payer_name)
                        ->id_type($payment->payment_payer_id_type)
                        ->id_number($payment->payment_payer_id_no)
                        ->tel_number($payment->payment_payer_phone)
                        ->zakat_type($payment->payment_zakat_type) # Zakat on Savings
                        ->year($payment->payment_zakat_year)
                        ->email($payment->payment_payer_email)
                        ->uniqueid($uniqid)
                        ->channel(self::CHANNEL())
                        ->afcode($payment->payment_affiliate_code)
                )
            );

            $form = '';

            Yii::$app->db->transaction(function ($db) use (&$uniqid, &$payment_proxy, &$payment, &$form) {

                /*** provider models * */
                $provider_model = new PaymentProviderZakatfpx();
                $provider_model->order_number = $uniqid;
                if (!$provider_model->save()) {
                    throw new \Exception("Error saving payment data");
                }

                $form = $payment_proxy->pay(number_format((float) $payment->payment_total / 100, 2, '.', ''));

                $payment->payment_gateway_refno = $uniqid;
                $payment->payment_status = Payment::DEFAULT_PENDING_STATUS;
                $payment->payment_provider_table_name = 'zakatfpx';
                $payment->payment_provider_id = $provider_model->zakatfpx_id;

                if (!$payment->save()) {
                    throw new \Exception("Error saving payment trace");
                }
            });

            return $this->render('payment-request', compact('form'));
        } catch (\Throwable $t) {
            if (YII_ENV != 'prod')
                throw $t;

            (new ErrorHandler)->logException($t);
        }
    }

    /*
    
    public function actionRequestZakatFpxBypass($id)
    {
        try {
            $payment = Payment::findOne($id);
            if (empty($payment) || $payment->payment_status != "pendingpay") {
            }
            $uniqid = ZakatFpx::generateId(self::ID_PREFIX(), $id);
            $payment_proxy = new PaymentProxy(
                new ZakatFpx(
                    (new PaymentFields())
                        ->name($payment->payment_payer_name)
                        ->id_type($payment->payment_payer_id_type)
                        ->id_number($payment->payment_payer_id_no)
                        ->tel_number($payment->payment_payer_phone)
                        ->zakat_type($payment->payment_zakat_type) # Zakat on Savings
                        ->year($payment->payment_zakat_year)
                        ->email($payment->payment_payer_email)
                        ->uniqueid($uniqid)
                        ->channel(self::CHANNEL())
                        ->afcode('0')
                )
            );
            $form = '';
            Yii::$app->db->transaction(function ($db) use (&$uniqid, &$payment_proxy, &$payment, &$form) {
                $provider_model = new PaymentProviderZakatfpx();
                $provider_model->order_number = $uniqid;
                if (!$provider_model->save()) {
                    throw new \Exception("Error saving payment data");
                }

                $form = $payment_proxy->pay(number_format($payment->payment_total / 100, 2));
                $payment->payment_gateway_refno = $uniqid;
                #$payment->payment_status = Payment::DEFAULT_PENDING_STATUS;
                $payment->payment_status = 'verified';
                $payment->payment_provider_table_name = 'zakatfpx';
                $payment->payment_provider_id = $provider_model->zakatfpx_id;

                if (!$payment->save()) {
                    throw new \Exception("Error saving payment trace");
                }
            });
            #return $this->render('payment-request-bypass', compact('form'));
            return $this->render('payment-request-bypass', ['payment_total' => number_format($payment->payment_total / 100, 2)]);
        } catch (\Throwable $t) {
            if (YII_ENV != 'prod')
                throw $t;

            (new ErrorHandler)->logException($t);
        }
    }
    */

    /**
     * Zakat response callback
     * 
     */
    public function actionResponseZakatFpx()
    {
        try {
            # Takes raw data from the request
            $json = file_get_contents('php://input');

            # Converts it into a PHP object
            $data = json_decode($json);

            if (empty($data)) {
                throw new \Exception('Not a valid json object');
            }

            #validate response token
            if (self::RESPONSE_TOKEN() !== $data->response_token) {
                throw new \Exception('Unauthorize call to the endpoint');
            }

            # get prefix 
            $prefix = ZakatFpx::getPrefix($data->myTransID);

            /** handle the data according order number type */
            switch ($prefix) {
                case $prefix === self::CROWD_ZAKAT_PREFIX:
                    /** save response */
                    $this->saveResponse(new PaymentCallback(), $json);

                    /** update status */
                    $payment = $this->updatePaymentStatus(new Payment(), $data);

                    $payment_provider = $this->setProviderData(new PaymentProviderZakatfpx(), $data);

                    if (!empty($data->AFCode)) { # create another duplicate record on affiliate
                        $radiuz_payment_provider = $this->setRadiuzProviderData(new RadiuzPaymentProviderZakatfpx, $payment_provider);
                        
                        $this->setRadiuzData(new PaymentRadiuz, $payment, $radiuz_payment_provider);
                    }

                    /** redirect to receipt page */
                    $base = self::BASE_CROWD_URL();
                    $success_url = $base . self::SUCCESS_URL() . '?h=' . $payment->payment_hash;
                    $failed_url  = $base . self::FAILED_URL() . '?h=' . $payment->payment_hash;

                    if ($data->approvecode === ZakatFpx::SUCCESS_PAYMENT_CODE) {
                        die(<<<HTML
                            <meta http-equiv="refresh" content="0;URL='$success_url'" />
                        HTML);
                    }

                    die(<<<HTML
                        <meta http-equiv="refresh" content="0;URL='$failed_url'" />
                    HTML);

                    break;

                case $prefix === self::RADIUZ_ZAKAT_PREFIX:
                    /** save response */
                    $this->saveResponse(new RadiuzCallback(), $json);

                    /** update status */
                    $payment = $this->updatePaymentStatus(new RadiuzPayment(), $data);

                    $this->setProviderData(new RadiuzPaymentProviderZakatfpx(), $data);

                    /** redirect to receipt page */
                    $base = Yii::$app->params['payment_gateway']['zakat_fpx']['response']['base_url']['radiuz'];
                    $success_url = $base . self::SUCCESS_URL() . '?h=' . $payment->payment_hash;
                    $failed_url  = $base . self::FAILED_URL() . '?h=' . $payment->payment_hash;

                    if ($data->approvecode === ZakatFpx::SUCCESS_PAYMENT_CODE) {
                        die(<<<HTML
                            <meta http-equiv="refresh" content="0;URL='$success_url'" />
                        HTML);
                    }

                    die(<<<HTML
                        <meta http-equiv="refresh" content="0;URL='$failed_url'" />
                    HTML);

                    break;

                default:
                    throw new \Exception('Invalid number prefix received');
                    break;
            }
        } catch (\Throwable $t) {
            Yii::$app->response->setStatusCode(500);

            (new ErrorHandler)->logException($t);

            return $this->asJson([
                'success' => false,
                'message' => $t->getMessage()
            ]);
        }
    }

    /**
     * Save response get from payment gateway
     * @param  PaymentCallback $call Payment callback model object
     * @param  string $json Json receive from payment gateway
     * @return void return to the url receipt
     */
    private function saveResponse($call, $json)
    {

        $call->post = $json;
        $call->url = Url::to();
        $call->get = json_encode(Yii::$app->request->get());

        if (!$call->save()) {
            throw new \Exception("Error saving response callback");
        }
    }

    /**
     * Update payment status, and insert payment information
     * @param  Payment $payment Payment model
     * @param  Json $data Json object
     * @return Payment
     */
    private function updatePaymentStatus($payment, $data)
    {
        $payment_model = $payment::find()
            ->where(['payment_gateway_refno' => $data->myTransID])
            ->one();

        if (empty($payment_model)) {
            throw new \Exception("Error update payment status, order number is invalid");
        }

        $payment_model->payment_status = 'failed';
        $payment_model->updated_at = date('Y-m-d H:i:s');
        
        # common
        $payment_model->payment_gateway_status = $data->status ?? '';
        $payment_model->payment_gateway_code = $data->approvecode ?? '';

        # return verified 
        if (ZakatFpx::getStatus($data->approvecode) === true) {

            $d = \DateTime::createFromFormat("d/m/Y H:i:s A", $data->transDate);
            $c = \DateTime::createFromFormat("d/m/Y H:i:s A", $data->dateCreated);

            $payment_model->created_at = $c->format('Y-m-d H:i:s');
            $payment_model->payment_status = 'verified';
            $payment_model->payment_gateway_code = $data->approvecode;
            $payment_model->payment_gateway_status = $data->status;
            $payment_model->payment_approved_at = date('Y-m-d H:i:s');
            $payment_model->payment_bank = $data->buyerbank ?? '';
            $payment_model->payment_affiliate_code = $data->AFCode;
            $payment_model->payment_gateway_order_id = $data->fpxOrderNo ?? '';
            $payment_model->payment_gateway_transaction_time = $d->format("Y-m-d H:i:s");
            $payment_model->payment_gateway_transaction_id = $data->fpxtransacid;
            $payment_model->payment_receipt_no = $this->generateReceiptNumber($payment);
        }

        if (!$payment_model->save()) {
            throw new \Exception("Error update payment status");
        }

        return $payment_model;
    }

    private function setProviderData($provider, $data)
    {
        # only save record if the response is success
        if (empty($data->approvecode)) {
            return;
        }

        $payment_provider = $provider;

        $update_provider = $provider::find()
            ->where(['order_number' => $data->myTransID])
            ->one();

        if (!empty($update_provider)) {
            # throw new \Exception("Error update provider data");
            $payment_provider = $update_provider;
        }

        $d = \DateTime::createFromFormat("d/m/Y H:i:s A", $data->transDate);

        $payment_provider->bank = $data->buyerbank;
        $payment_provider->name = $data->name;
        $payment_provider->email = $data->email;
        $payment_provider->fpx_order_no = $data->fpxOrderNo;
        $payment_provider->transaction_date = $d->format("Y-m-d H:i:s");
        $payment_provider->amount = $data->amountZakat;
        $payment_provider->approve_amount = $data->apprvamount;
        $payment_provider->type = $data->desZakat;
        $payment_provider->approve_code = $data->approvecode;
        $payment_provider->transaction_id = $data->fpxtransacid;
        $payment_provider->buyer_bank = $data->buyerbank;
        $payment_provider->payer_status = $data->payerstatus;
        $payment_provider->order_number = $data->myTransID;
        $payment_provider->status = $data->status;
        $payment_provider->afcode = $data->AFCode ?? null;

        if (!$payment_provider->save()) {
            throw new \Exception("Error update payment status");
        }

        return $payment_provider;
    }

    private function setRadiuzData($provider, $payment, $carry_provider) // carry provider -> radiuz zakat fpx provider table
    {
        $payment_provider = $provider;

        $update_provider = $provider::find()
            ->where(['payment_gateway_refno' => $payment->payment_gateway_refno])
            ->one();

        if (!empty($update_provider)) {
            $payment_provider = $update_provider;
        }

        $exclude = ['payment_id', 'payment_provider_id'];

        # Set the payment channel
        $payment_provider->payment_channel = 'CROWDZAKAT';
        $payment_provider->payment_provider_id = $carry_provider->zakatfpx_id; #grab zakat provider id

        # Loop trough each attribute and set it 
        foreach (array_keys($payment->attributes) as $attrib) {
            if (in_array($attrib, $exclude)) {
                continue; # skip any excluded attribute
            }

            if (in_array($attrib, array_keys($payment_provider->attributes))) {
                $payment_provider->{$attrib} = $payment->{$attrib};
            }
        }

        if (!$payment_provider->save()) {
            throw new \Exception('Error Saving Payment to Radiuz' . json_encode($payment_provider->errors));
        }
        return;
    }

    private function setRadiuzProviderData($provider, $payment)
    {
        $payment_provider = $provider;

        $update_provider = $provider::find()
            ->where(['order_number' => $payment->order_number])
            ->one();

        if (!empty($update_provider)) {
            $payment_provider = $update_provider;
        }

        $exclude = ['zakatfpx_id'];

        # Loop trough each attribute and set it 
        foreach (array_keys($payment->attributes) as $attrib) {
            if (in_array($attrib, $exclude)) {
                continue; # skip any excluded attribute
            }

            if (in_array($attrib, array_keys($payment_provider->attributes))) {
                $payment_provider->{$attrib} = $payment->{$attrib};
            }
        }

        if (!$payment_provider->save()) {
            throw new \Exception('Error Saving Payment Provider to Radiuz' . json_encode($payment_provider->errors));
        }
        return $payment_provider;
    }

    private function generateReceiptNumber($payment)
    {
        return  ZakatFpx::generateReceiptNumber(
            $payment::find()
                ->where('LENGTH(payment_receipt_no) = 14')
                ->max(
                    <<<SQL
                        CONCAT(
                            SUBSTRING(payment_receipt_no, 1, 6),
                            SUBSTRING(payment_receipt_no, 9, 5)
                        )
                    SQL
                )
        );
    }

    public function actionTest($channel, $transaction_id)
    {
        $zakatfpx = new \common\components\payment\provider\ZakatFpx();

        $token = $zakatfpx->getTransactionDetail($channel, $transaction_id);

        // var_dump($token);
    }
}