File "StripeListener.php"

Full Path: /home/romayxjt/public_html/wp-content/plugins/fluentform/app/Modules/Payments/PaymentMethods/Stripe/API/StripeListener.php
File size: 13.28 KB
MIME-type: text/x-php
Charset: utf-8

<?php

namespace FluentForm\App\Modules\Payments\PaymentMethods\Stripe\API;

if (!defined('ABSPATH')) {
    exit; // Exit if accessed directly.
}

use FluentForm\App\Helpers\Helper;
use FluentForm\Framework\Helpers\ArrayHelper;
use FluentForm\App\Modules\Payments\PaymentHelper;
use FluentForm\App\Modules\Payments\PaymentMethods\BaseProcessor;
use FluentForm\App\Modules\Payments\PaymentMethods\Stripe\StripeProcessor;
use FluentForm\App\Modules\Payments\PaymentMethods\Stripe\StripeSettings;

class StripeListener
{

    public function verifyIPN()
    {
        // $signature = $_SERVER['HTTP_STRIPE_SIGNATURE'];

        // retrieve the request's body and parse it as JSON
        $body = @file_get_contents('php://input');

        $event = json_decode($body);

        if (!$event || empty($event->id)) {
            return;
        }

        $eventId = $event->id;

        if ($eventId) {
            status_header(200);
            try {
                $formId = StripeSettings::guessFormIdFromEvent($event);

                $event = $this->retrive($eventId, $formId);

                if ($event && !is_wp_error($event)) {
                    $eventType = $event->type;

                    if ($eventType == 'charge.succeeded' || $eventType == 'charge.captured') {
                        $this->handleChargeSucceeded($event);
                    } else if ($eventType == 'invoice.payment_succeeded') {
                        $this->maybeHandleSubscriptionPayment($event);
                    } else if ($eventType == 'charge.refunded') {
                        $this->handleChargeRefund($event);
                    } else if ($eventType == 'customer.subscription.deleted') {
                        $this->handleSubscriptionCancelled($event);
                    } else if ($eventType == 'checkout.session.completed') {
                        $this->handleCheckoutSessionCompleted($event);
                    } else if($eventType == 'customer.subscription.updated') {
                        // maybe we have to handle the
                    }
                }
            } catch (\Exception $e) {
                return; // No event found for this account
            }
        } else {
            status_header(500);
            die('-1'); // Failed
        }
        die('1');
    }

    // This is an onetime payment success
    private function handleChargeSucceeded($event)
    {
        $charge = $event->data->object;

        $meta = (array) $charge->metadata;

        $transactionId = ArrayHelper::get($meta, 'transaction_id');

        if(!$transactionId) {
            $transaction = wpFluent()->table('fluentform_transactions')
                ->where('charge_id', $charge->payment_intent)
                ->first();
            if(!$transaction) {
                return;
            }
        } else {
            $submissionId = ArrayHelper::get($meta, 'submission_id');
            $transaction = wpFluent()->table('fluentform_transactions')
                ->where('submission_id', $submissionId)
                ->where('id', $transactionId)
                ->where('payment_method', 'stripe')
                ->first();
        }

        if (!$transaction) {
            return;
        }

        if($transaction->status == 'paid') {
            return; // Already paid we don't have to do anything here
        }

        // We have the transaction so we have to update some fields
        $updateData = array(
            'status' => 'paid'
        );

        if (!$transaction->card_last_4) {
            if (!empty($charge->source->last4)) {
                $updateData['card_last_4'] = $charge->source->last4;
            } else if (!empty($charge->payment_method_details->card->last4)) {
                $updateData['card_last_4'] = $charge->payment_method_details->card->last4;
            }
        }
        if (!$transaction->card_brand) {
            if (!empty($charge->source->brand)) {
                $updateData['card_brand'] = $charge->source->brand;
            } else if (!empty($charge->payment_method_details->card->network)) {
                $updateData['card_brand'] = $charge->payment_method_details->card->network;
            }
        }

        if(!$transaction->charge_id) {
            $updateData['charge_id'] = $charge->payment_intent;
        }

        wpFluent()->table('fluentform_transactions')
            ->where('id', $transaction->id)
            ->update($updateData);

        // We have to fire transaction paid hook here

    }

    /*
     * Handle Subscription Payment IPN
     * Refactored in version 2.0
     */
    private function maybeHandleSubscriptionPayment($event)
    {
        $data = $event->data->object;

        $subscriptionId = false;
        if (property_exists($data, 'subscription')) {
            $subscriptionId = $data->subscription;
        }

        if (!$subscriptionId) {
            return;
        }

        $subscription = wpFluent()->table('fluentform_subscriptions')
            ->where('vendor_subscription_id', $subscriptionId)
            ->where('vendor_customer_id', $data->customer)
            ->first();

        if (!$subscription) {
            return;
        }

        $submission = wpFluent()->table('fluentform_submissions')
            ->where('id', $subscription->submission_id)
            ->first();

        if (!$submission) {
            return;
        }

        $transactionData = $this->createSubsTransactionDataFromInvoice($data, $subscription, $submission);

        // We may have an already exist session charge that we have to update
        $pendingTransaction = wpFluent()->table('fluentform_transactions')
            ->whereNull('charge_id')
            ->where('submission_id', $submission->id)
            ->where('status', 'pending')
            ->first();

        if($pendingTransaction) {
            unset($transactionData['transaction_hash']);
            unset($transactionData['created_at']);

            wpFluent()->table('fluentform_transactions')
                ->where('id', $pendingTransaction->id)
                ->update($transactionData);
        } else {
            (new StripeProcessor())->recordSubscriptionCharge($subscription, $transactionData);
        }
    }

    /*
     * Refactored at version 2.0
     * We are logging refunds now for both subscription and
     * One time payments
     */
    private function handleChargeRefund($event)
    {
        (new StripeProcessor())->handleRefund($event);
    }

    /*
     * Handle Subscription Canceled
     */
    private function handleSubscriptionCancelled($event)
    {
        $data = $event->data->object;
        $subscriptionId = $data->id;

        $subscription = wpFluent()->table('fluentform_subscriptions')
            ->where('vendor_subscription_id', $subscriptionId)
            ->where('status', '!=', 'completed')
            ->first();

        if (!$subscription || $subscription->status == 'cancelled') {
            return;
        }

        do_action_deprecated(
            'fluentform_form_submission_activity_start',
            [
                $subscription->form_id
            ],
            FLUENTFORM_FRAMEWORK_UPGRADE,
            'fluentform/form_submission_activity_start',
            'Use fluentform/form_submission_activity_start instead of fluentform_form_submission_activity_start.'
        );

        do_action('fluentform/form_submission_activity_start', $subscription->form_id);

        PaymentHelper::recordSubscriptionCancelled($subscription, $data);
    }


    private function handleCheckoutSessionCompleted($event)
    {
        $data = $event->data->object;

        $metaData = (array)$data->metadata;

        $formId = ArrayHelper::get($metaData, 'form_id');

        $session = CheckoutSession::retrieve($data->id, [
            'expand' => [
                'subscription.latest_invoice.payment_intent',
                'payment_intent'
            ]
        ], $formId);

        if (!$session || is_wp_error($session)) {
            return;
        }

        $submissionId = intval($session->client_reference_id);
        if (!$session || !$submissionId) {
            return;
        }

        if (Helper::getSubmissionMeta($submissionId, 'is_form_action_fired') == 'yes') {
            return;
        }

        // let's get the pending submission
        $submission = wpFluent()->table('fluentform_submissions')
            ->where('id', $submissionId)
            ->first();

        if (!$submission) {
            return;
        }

        $transactionId = ArrayHelper::get($metaData, 'transaction_id');

        if(!$transactionId) {
            return;
        }

        $transaction = wpFluent()->table('fluentform_transactions')
            ->where('form_id', $submission->form_id)
            ->where('id', $transactionId)
            ->where('submission_id', $submission->id)
            ->first();

        if(!$transaction) {
            return; // not our transaction or transaction_status already paid
        }

        $returnData = (new StripeProcessor())->processStripeSession($session, $submission, $transaction);
    }

    /*
     *
     */
    public function retrive($eventId, $formId = false)
    {
        $api = new ApiRequest();
        $api::set_secret_key(StripeSettings::getSecretKey($formId));
        return $api::request([], 'events/' . $eventId, 'GET');
    }

    public function verifySignature($payload, $signature)
    {
        // Extract timestamp and signatures from header
        $timestamp = self::getTimestamp($signature);
        $signatures = self::getSignatures($signature);

        if (-1 === $timestamp) {
            return false;
        }
        if (empty($signatures)) {
            return false;
        }

        $signedPayload = "{$timestamp}.{$payload}";

        if (!function_exists('hash_hmac')) {
            return false;
        }

        $secret = 'whsec_NsNZNMSnWVPLt8GErz3SVZ97pWu8eb6D';

        $expectedSignature = \hash_hmac('sha256', $payload, $secret);

        foreach ($signatures as $signature) {
            if ($this->secureCompare($signature, $expectedSignature)) {
                return true;
            }
        }

        return false;
    }

    protected function getTimeStamp($signature)
    {
        $items = \explode(',', $signature);

        foreach ($items as $item) {
            $itemParts = \explode('=', $item, 2);
            if ('t' === $itemParts[0]) {
                if (!\is_numeric($itemParts[1])) {
                    return -1;
                }

                return (int)($itemParts[1]);
            }
        }

        return -1;
    }

    private function getSignatures($header, $scheme = 'v1')
    {
        $signatures = [];
        $items = \explode(',', $header);

        foreach ($items as $item) {
            $itemParts = \explode('=', $item, 2);
            if (\trim($itemParts[0]) === $scheme) {
                $signatures[] = $itemParts[1];
            }
        }

        return $signatures;
    }

    protected function secureCompare($a, $b)
    {
        if (function_exists('hash_equals')) {
            return \hash_equals($a, $b);
        }

        if (\strlen($a) !== \strlen($b)) {
            return false;
        }

        $result = 0;
        for ($i = 0; $i < \strlen($a); ++$i) {
            $result |= \ord($a[$i]) ^ \ord($b[$i]);
        }

        return 0 === $result;
    }

    protected function createSubsTransactionDataFromInvoice($invoice, $subscription, $submission)
    {
        $paymentIntent = false;
        if(!is_object($invoice->payment_intent)) {
            ApiRequest::set_secret_key(StripeSettings::getSecretKey($subscription->form_id));
            $paymentIntent = ApiRequest::request([], 'payment_intents/'.$invoice->payment_intent, 'GET');
            if(is_wp_error($paymentIntent)) {
                $paymentIntent = false;
            }
            $chargeId = $invoice->payment_intent;
        } else {
            $chargeId = $invoice->payment_intent->id;
            $paymentIntent = $invoice->payment_intent;
        }

        $paymentTotal = $invoice->amount_paid;
        if(PaymentHelper::isZeroDecimal($invoice->currency)) {
            $paymentTotal = $paymentTotal * 100;
        }

        $data = [
            'subscription_id' => $subscription->id,
            'form_id' => $subscription->form_id,
            'transaction_hash' => md5('subscription_trans_'.wp_generate_uuid4().time()),
            'user_id' => $submission->user_id,
            'submission_id' => $submission->id,
            'transaction_type' => 'subscription',
            'payment_method' => 'stripe',
            'card_last_4' => '',
            'card_brand' => '',
            'payer_name' => $invoice->customer_name,
            'payer_email' => $invoice->customer_email,
            'charge_id' => $chargeId,
            'payment_total' => $paymentTotal,
            'status' => 'paid',
            'currency' => strtoupper($invoice->currency),
            'payment_mode' => ($invoice->livemode) ? 'live' : 'test',
            'payment_note' => maybe_serialize($invoice),
            'created_at' => current_time('mysql'),
            'updated_at' => current_time('mysql'),
        ];

        if($paymentIntent && !empty($paymentIntent->charges->data[0]->payment_method_details->card)) {
            $card = $paymentIntent->charges->data[0]->payment_method_details->card;
            $data['card_brand'] = $card->brand;
            $data['card_last_4'] = $card->last4;
        }

        return $data;
    }
}