File "StripeInlineProcessor.php"

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

<?php

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

use FluentForm\App\Helpers\Helper;
use FluentForm\App\Modules\Payments\PaymentHelper;
use FluentForm\Framework\Helpers\ArrayHelper;
use FluentForm\App\Modules\Payments\PaymentMethods\Stripe\API\SCA;
use FluentForm\App\Modules\Payments\PaymentMethods\Stripe\API\Plan;
use FluentForm\App\Modules\Payments\PaymentMethods\Stripe\API\Invoice;
use FluentForm\App\Modules\Payments\PaymentMethods\Stripe\API\Customer;

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

class StripeInlineProcessor extends StripeProcessor
{
    
    public function init()
    {
        /*
         * After form submission this hooks fire to start Making payment
         */
        add_action('fluentform/process_payment_stripe_inline', [$this, 'handlePaymentAction'], 10, 6);

        /*
         * Mainly for single payment items
         */
        add_action('wp_ajax_fluentform_sca_inline_confirm_payment', [$this, 'confirmScaPayment']);
        add_action('wp_ajax_nopriv_fluentform_sca_inline_confirm_payment', [$this, 'confirmScaPayment']);

        /*
         * For Subscription payment + maybe single payment items
         */
        add_action('wp_ajax_fluentform_sca_inline_confirm_payment_setup_intents', array($this, 'confirmScaSetupIntentsPayment'));
        add_action('wp_ajax_nopriv_fluentform_sca_inline_confirm_payment_setup_intents', array($this, 'confirmScaSetupIntentsPayment'));
    }

    public function handlePaymentAction($submissionId, $submissionData, $form, $methodSettings, $hasSubscriptions, $totalPayable)
    {
        $this->setSubmissionId($submissionId);
        $this->form = $form;
        $submission = $this->getSubmission();
        $paymentTotal = $this->getAmountTotal();
        
        if (!$paymentTotal && !$hasSubscriptions) {
            return false;
        }

        // Create the initial transaction here
        $transaction = $this->createInitialPendingTransaction($submission, $hasSubscriptions);

        $paymentMethodId = ArrayHelper::get($submissionData['response'], '__stripe_payment_method_id');
        $customerArgs = $this->customerArguments($paymentMethodId, $submission);

        $customer = Customer::createCustomer($customerArgs, $this->form->id);

        if (is_wp_error($customer)) {
            // We have errors
            $this->handlePaymentChargeError($customer->get_error_message(), $submission, $transaction);
        }

        if ($transaction->transaction_type == 'subscription') {
            $this->handleSetupIntent($submission, $paymentMethodId, $customer, $transaction, $totalPayable);
        } else {
            // Let's create the one time payment first
            // We will handle One-Time Payment Here only
            $paymentSettings = PaymentHelper::getFormSettings($form->id, 'admin');
            $intentArgs = [
                'payment_method'              => $paymentMethodId,
                'amount'                      => $transaction->payment_total,
                'currency'                    => $transaction->currency,
                'confirmation_method'          => 'manual',
                'confirm'                      => 'true',
                'description'                 => $this->getProductNames(),
                'statement_descriptor_suffix' => StripeSettings::getPaymentDescriptor($form),
                'metadata'                    => $this->getIntentMetaData($submission, $form, $transaction, $paymentSettings),
                'customer'                    => $customer->id
            ];

            $intentArgs = apply_filters('fluentform/stripe_checkout_args_inline', $intentArgs, $submission, $transaction, $form);

            // If FluentForm Pro is not installed, apply the fee 1.9% of the total amount
            if (!Helper::hasPro()) {
                $applicationFeeAmount = (int) ($totalPayable * 0.019);
                $intentArgs['application_fee_amount'] = $applicationFeeAmount;
            }
            $this->handlePaymentIntent($transaction, $submission, $intentArgs);
        }
    }

    // This is only for Subscription Payment
    protected function handleSetupIntent($submission, $paymentMethodId, $customer, $transaction, $totalPayable)
    {
        if (is_wp_error($customer)) {
            $this->handlePaymentChargeError($customer->get_error_message(), $submission, $transaction, false, 'customer');
        }

        $subscriptions = $this->getSubscriptions();

        $subscription = $subscriptions[0];

        $subscriptionTransactionArgs = Plan::getPriceIdsFromSubscriptionTransaction($subscription, $transaction);

        if(is_wp_error($subscriptionTransactionArgs)) {
            $this->handlePaymentChargeError($customer->get_error_message(), $submission, $transaction, false, 'customer');
        }

        $subscriptionArgs = [
            'customer'         => $customer->id,
            'metadata'         => $this->getIntentMetaData($submission, $this->getForm(), $transaction),
            'payment_behavior' => 'allow_incomplete',
        ];

        $subscriptionArgs['items'] = $subscriptionTransactionArgs['items'];

        if ($signupFee = $subscriptionTransactionArgs['signup_fee']) {
            Invoice::createItem([
                'amount'      => $signupFee,
                'currency'    => $submission->currency,
                'customer'    => $customer->id,
                'description' => __('Signup fee for ', 'fluentform') . $subscription->plan_name
            ], $submission->form_id);
        }

        // Maybe we have to set a cancel_at parameter to subscription args
        if ($cancelledAt = Plan::getCancelledAtTimestamp($subscription)) {
            $subscriptionArgs['cancel_at'] = $cancelledAt;
        }

        if ($subscription->trial_days) {
            $dateTime = current_datetime();
            $localtime = $dateTime->getTimestamp() + $dateTime->getOffset();
            $subscriptionArgs['trial_end'] = $localtime + $subscription->trial_days * 86400;
        }

        $subscriptionArgs = apply_filters('fluentform/stripe_subscription_args_inline', $subscriptionArgs, $submission, $transaction, $this->getForm());

        // If FluentForm Pro is not installed, apply the fee 1.9%
        if (!Helper::hasPro()) {
            $subscriptionArgs['application_fee_percent'] = 1.9;
        }

        $subscriptionPayment = Plan::subscribe($subscriptionArgs, $submission->form_id);

        if (is_wp_error($subscriptionPayment)) {
            $this->handlePaymentChargeError($subscriptionPayment->get_error_message(), $submission, $transaction, false, 'subscription');
        }

        $invoice = Invoice::retrieve(
            $subscriptionPayment->latest_invoice,
            $this->form->id,
            [
                'expand' => ['payment_intent.charges']
            ]
        );
        if (is_wp_error($invoice)) {
            $this->handlePaymentChargeError($invoice->get_error_message(), $submission, $transaction, false, 'invoice');
        }

        if (
            $invoice->payment_intent &&
            $invoice->payment_intent->status == 'requires_action' &&
            $invoice->payment_intent->next_action->type == 'use_stripe_sdk'
        ) {
            $transactionId = false;
            if ($transaction) {
                $transactionId = $transaction->id;
            }
            $this->processScaBeforeVerification($submission->form_id, $submission->id, $transactionId, $invoice->payment_intent->id);

            wp_send_json_success([
                'nextAction'             => 'payment',
                'actionName'             => 'stripeSetupIntent',
                'stripe_subscription_id' => $subscriptionPayment->id,
                'payment_method_id'      => $paymentMethodId,
                'intent'                 => $invoice->payment_intent,
                'submission_id'          => $submission->id,
                'customer_name'          => ($transaction) ? $transaction->payer_name : '',
                'customer_email'         => ($transaction) ? $transaction->payer_email : '',
                'client_secret'          => $invoice->payment_intent->client_secret,
                'message'                => __('Verifying your card details. Please wait...', 'fluentform'),
                'result'                 => [
                    'insert_id' => $submission->id
                ]
            ], 200);
        }

        // now this payment is successful. We don't need anything else
        $this->handlePaidSubscriptionInvoice($invoice, $submission);
    }

    protected function customerArguments($paymentMethodId, $submission)
    {
        $customerArgs = [
            'payment_method'   => $paymentMethodId,
            'invoice_settings' => [
                'default_payment_method' => $paymentMethodId
            ],
            'metadata'         => [
                'submission_id' => $submission->id,
                'form_id'       => $submission->form_id,
                'form_name'     => strip_tags($this->form->title)
            ]
        ];

        $receiptEmail = PaymentHelper::getCustomerEmail($submission, $this->form);

        if ($receiptEmail) {
            $customerArgs['email'] = $receiptEmail;
        }

        $receiptName = PaymentHelper::getCustomerName($submission, $this->form);

        if ($receiptName) {
            $customerArgs['name'] = $receiptName;
            $customerArgs['description'] = $receiptName;
        }

		$address = PaymentHelper::getCustomerAddress($submission);
		if ($address) {
			$customerArgs['address'] = [
				'city'        => ArrayHelper::get($address, 'city'),
				'country'     => ArrayHelper::get($address, 'country'),
				'line1'       => ArrayHelper::get($address, 'address_line_1'),
				'line2'       => ArrayHelper::get($address, 'address_line_2'),
				'postal_code' => ArrayHelper::get($address, 'zip'),
				'state'       => ArrayHelper::get($address, 'state'),
			];
		}

        return $customerArgs;
    }

    protected function handlePaidSubscriptionInvoice($invoice, $submission)
    {
        if ($invoice->status !== 'paid') {
            wp_send_json([
                'errors' => __('Stripe Error: Payment Failed! Please try again.', 'fluentform')
            ], 423);
        }

        // Submission status as paid
        $this->changeSubmissionPaymentStatus('paid');

        $subscriptions = $this->getSubscriptions();

        $this->processSubscriptionSuccess($subscriptions, $invoice, $submission);

        $transaction = $this->getLastTransaction($submission->id);

        $paymentStatus = $this->getIntentSuccessName($invoice->payment_intent);
        $this->processOnetimeSuccess($invoice, $transaction, $paymentStatus);

        $this->recalculatePaidTotal();

        $this->sendSuccess($submission);
    }

    protected function handlePaymentIntent($transaction, $submission, $intentArgs)
    {
        $formSettings = PaymentHelper::getFormSettings($submission->form_id);

        if (PaymentHelper::isZeroDecimal($transaction->currency)) {
            $intentArgs['amount'] = intval($transaction->payment_total / 100);
        }

        $receiptEmail = PaymentHelper::getCustomerEmail($submission, $this->form);

        if ($receiptEmail && ArrayHelper::get($formSettings, 'disable_stripe_payment_receipt') != 'yes') {
            $intentArgs['receipt_email'] = $receiptEmail;
        }

        $intent = SCA::createPaymentIntent($intentArgs, $this->form->id);

        if (is_wp_error($intent)) {
            $this->handlePaymentChargeError($intent->get_error_message(), $submission, $transaction, false, 'payment_intent');
        }

        if (
            $intent->status == 'requires_action' &&
            $intent->next_action &&
            $intent->next_action->type == 'use_stripe_sdk'
        ) {
            $this->processScaBeforeVerification($submission->form_id, $submission->id, $transaction->id, $intent->id);

            # Tell the client to handle the action
            wp_send_json_success([
                'nextAction'    => 'payment',
                'actionName'    => 'initStripeSCAModal',
                'submission_id' => $submission->id,
                'client_secret' => $intent->client_secret,
                'message'       => apply_filters('fluentform/stripe_strong_customer_verify_waiting_message', __('Verifying strong customer authentication. Please wait...', 'fluentform')),
                'result'        => [
                    'insert_id' => $submission->id
                ]
            ], 200);

        } else if ($intent->status == 'succeeded') {
            // Payment is succeeded here
            $charge = $intent->charges->data[0];

            $this->handlePaymentSuccess($charge, $transaction, $submission);
        } else {
            $message = __('Payment Failed! Your card may have been declined.', 'fluentform');

            if (!empty($intent->error->message)) {
                $message = $intent->error->message;
            }

            $this->handlePaymentChargeError($message, $submission, $transaction, false, 'payment_intent');
        }
    }

    protected function handlePaymentSuccess($charge, $transaction, $submission)
    {
        $transactionData = [
            'charge_id'      => $charge->payment_intent,
            'payment_method' => 'stripe',
            'payment_mode'   => $this->getPaymentMode(),
            'payment_note'   => maybe_serialize($charge)
        ];

        $methodDetails = $charge->payment_method_details;
        if ($methodDetails && !empty($methodDetails->card)) {
            $transactionData['card_brand'] = $methodDetails->card->brand;
            $transactionData['card_last_4'] = $methodDetails->card->last4;
        }

        $this->updateTransaction($transaction->id, $transactionData);

        $this->changeTransactionStatus($transaction->id, 'paid');

        $logData = [
            'parent_source_id' => $submission->form_id,
            'source_type'      => 'submission_item',
            'source_id'        => $submission->id,
            'component'        => 'Payment',
            'status'           => 'info',
            'title'            => __('Payment Status changed', 'fluentform'),
            'description'      => __('Payment status changed to paid', 'fluentform')
        ];

        do_action('fluentform/log_data', $logData);

        $this->updateSubmission($submission->id, [
            'payment_status' => 'paid',
            'payment_method' => 'stripe',
        ]);

        $logData = [
            'parent_source_id' => $submission->form_id,
            'source_type'      => 'submission_item',
            'source_id'        => $submission->id,
            'component'        => 'Payment',
            'status'           => 'success',
            'title'            => __('Payment Complete', 'fluentform'),
            'description'      => __('One time Payment Successfully made via Stripe. Charge ID: ', 'fluentform') . $charge->id
        ];

        do_action('fluentform/log_data', $logData);

        $this->recalculatePaidTotal();

        $this->sendSuccess($submission);
    }

    public function confirmScaPayment()
    {
        $formId = intval($_REQUEST['form_id']);
        $submissionId = intval($_REQUEST['submission_id']);
        $paymentMethod = sanitize_text_field($_REQUEST['payment_method']);
        $paymentIntentId = sanitize_text_field($_REQUEST['payment_intent_id']);

        $this->setSubmissionId($submissionId);
        $submission = $this->getSubmission();
        $this->form = $this->getForm();

        $transaction = $this->getLastTransaction($submissionId);

        $confirmation = SCA::confirmPayment($paymentIntentId, [
            'payment_method' => $paymentMethod
        ], $formId);

        if (is_wp_error($confirmation)) {
            $message = 'Payment has been failed. ' . $confirmation->get_error_message();
            $this->handlePaymentChargeError($message, $submission, $transaction, $confirmation, 'payment_error');
        }

        if ($confirmation->status == 'succeeded') {
            $charge = $confirmation->charges->data[0];;
            $this->handlePaymentSuccess($charge, $transaction, $submission);
        } else {
            $this->handlePaymentChargeError('We could not verify your payment. Please try again', $submission, $transaction, $confirmation, 'payment_error');
        }
    }

    public function confirmScaSetupIntentsPayment()
    {
        $formId = intval($_REQUEST['form_id']);
        $submissionId = intval($_REQUEST['submission_id']);
        $intentId = sanitize_text_field($_REQUEST['payment_intent_id']);

        $this->setSubmissionId($submissionId);
        $this->form = $this->getForm();

        $submission = $this->getSubmission();

        // Let's retrieve the intent
        $intent = SCA::retrievePaymentIntent($intentId, [
            'expand' => [
                'invoice.payment_intent'
            ]
        ], $formId);

        if (is_wp_error($intent)) {
            $this->handlePaymentChargeError($intent->get_error_message(), $submission, false, false, 'payment_intent');
        }

        $invoice = $intent->invoice;

        $this->handlePaidSubscriptionInvoice($invoice, $submission);
    }

    protected function sendSuccess($submission)
    {
        try {
            $returnData = $this->getReturnData();
            wp_send_json_success($returnData, 200);
    
        } catch (\Exception $e) {
            wp_send_json([
                'errors' => $e->getMessage()
            ], 423);
        }
      
    }

    protected function processScaBeforeVerification($formId, $submissionId, $transactionId, $chargeId)
    {
        if ($transactionId) {
            $this->updateTransaction($transactionId, [
                'charge_id'    => $chargeId,
                'payment_mode' => $this->getPaymentMode()
            ]);

            $this->changeTransactionStatus($transactionId, 'intended');
        }

        $logData = [
            'parent_source_id' => $formId,
            'source_type'      => 'submission_item',
            'source_id'        => $submissionId,
            'component'        => 'Payment',
            'status'           => 'info',
            'title'            => __('Stripe SCA Required', 'fluentform'),
            'description'      => __('SCA is required for this payment. Requested SCA info from customer', 'fluentform')
        ];

        do_action('fluentform/log_data', $logData);
    }
    
    /**
     * Products name comma separated
     * @return string
     */
    public function getProductNames()
    {
        $orderItems = $this->getOrderItems();
        $itemsHtml = '';
        foreach ($orderItems as $item) {
            $itemsHtml != "" && $itemsHtml .= ", ";
            $itemsHtml .=  $item->item_name ;
        }
        
        return $itemsHtml;
    }
    
}