File "StripeProcessor.php"
Full Path: /home/romayxjt/public_html/wp-content/plugins/fluentform/app/Modules/Payments/PaymentMethods/Stripe/StripeProcessor.php
File size: 25.51 KB
MIME-type: text/x-php
Charset: utf-8
<?php
namespace FluentForm\App\Modules\Payments\PaymentMethods\Stripe;
if (!defined('ABSPATH')) {
exit; // Exit if accessed directly.
}
use FluentForm\App\Helpers\Helper;
use FluentForm\App\Services\FormBuilder\ShortCodeParser;
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\API\CheckoutSession;
use FluentForm\App\Modules\Payments\PaymentMethods\Stripe\API\Plan;
class StripeProcessor extends BaseProcessor
{
public $method = 'stripe';
protected $form;
public function init()
{
add_action('fluentform/process_payment_stripe_hosted', array($this, 'handlePaymentAction'), 10, 6);
add_action('fluentform/payment_frameless_' . $this->method, array($this, 'handleSessionRedirectBack'));
}
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);
$this->handleCheckoutSession($transaction, $submission, $form, $methodSettings);
}
public function handleCheckoutSession($transaction, $submission, $form, $methodSettings)
{
$formSettings = PaymentHelper::getFormSettings($form->id);
$args = [
'fluentform_payment' => $submission->id,
'payment_method' => $this->method,
'transaction_hash' => $transaction->transaction_hash,
'type' => 'success'
];
$successUrl = add_query_arg($args, site_url('index.php'));
$cancelUrl = $submission->source_url;
if (!wp_http_validate_url($cancelUrl)) {
$cancelUrl = site_url($cancelUrl);
}
$checkoutArgs = [
'cancel_url' => wp_sanitize_redirect($cancelUrl),
'success_url' => wp_sanitize_redirect($successUrl),
'locale' => 'auto',
'billing_address_collection' => 'auto',
'client_reference_id' => $submission->id,
'customer_email' => $transaction->payer_email,
'metadata' => [
'submission_id' => $submission->id,
'form_id' => $form->id,
'transaction_id' => ($transaction) ? $transaction->id : ''
]
];
if (ArrayHelper::get($methodSettings, 'settings.require_billing_info.value') == 'yes') {
$checkoutArgs['billing_address_collection'] = 'required';
}
if (ArrayHelper::get($methodSettings, 'settings.require_shipping_info.value') == 'yes') {
$checkoutArgs['shipping_address_collection'] = [
'allowed_countries' => StripeSettings::supportedShippingCountries()
];
}
if ($lineItems = $this->getFormattedItems($submission->currency)) {
$checkoutArgs['line_items'] = $lineItems;
}
$receiptEmail = PaymentHelper::getCustomerEmail($submission, $form);
if ($receiptEmail) {
$checkoutArgs['customer_email'] = $receiptEmail;
}
if ($transaction->transaction_type == 'subscription') {
unset($checkoutArgs['submit_type']);
$subscriptions = $this->getSubscriptions();
$subscription = $subscriptions[0];
if ($subscription->initial_amount) {
if (!isset($checkoutArgs['line_items'])) {
$checkoutArgs['line_items'] = [];
}
$price = $subscription->initial_amount;
if (PaymentHelper::isZeroDecimal($transaction->currency)) {
$price = intval($price / 100);
}
$checkoutArgs['line_items'][] = [
'amount' => $price,
'currency' => $transaction->currency,
'name' => __('Signup fee for ', 'fluentform') . $subscription->plan_name,
'quantity' => 1
];
}
$stripePlan = Plan::getSubscriptionPlanBySubscription($subscription, $transaction->currency);
if(is_wp_error($stripePlan)) {
$this->handlePaymentChargeError($stripePlan->get_error_message(), $submission, $transaction);
}
$this->updateSubscription($subscription->id, [
'vendor_plan_id' => $stripePlan->id
]);
$subsArgs = [
'items' => [
[
'plan' => $stripePlan->id,
'quantity' => $subscription->quantity ? $subscription->quantity : 1
]
]
];
if ($subscription->trial_days) {
$subsArgs['trial_period_days'] = $subscription->trial_days;
}
$subsArgs['metadata'] = $this->getIntentMetaData($submission, $form, $transaction);
$checkoutArgs['subscription_data'] = $subsArgs;
} else {
$checkoutArgs['submit_type'] = 'auto';
$checkoutArgs['payment_intent_data'] = $this->getPaymentIntentData($transaction, $submission, $form);
if ($receiptEmail && ArrayHelper::get($formSettings, 'disable_stripe_payment_receipt') != 'yes') {
$checkoutArgs['payment_intent_data']['receipt_email'] = $receiptEmail;
}
}
if ($formSettings['transaction_type'] == 'donation' && $transaction->transaction_type != 'subscription') {
$checkoutArgs['submit_type'] = 'donate';
}
$checkoutArgs = apply_filters_deprecated(
'fluentform_stripe_checkout_args',
[
$checkoutArgs,
$submission,
$transaction,
$form
],
FLUENTFORM_FRAMEWORK_UPGRADE,
'fluentform/stripe_checkout_args',
'Use fluentform/stripe_checkout_args instead of fluentform_stripe_checkout_args.'
);
$checkoutArgs = apply_filters('fluentform/stripe_checkout_args', $checkoutArgs, $submission, $transaction, $form);
// If FluentForm Pro is not installed, apply the fee 1.9%
if (!Helper::hasPro()) {
if ($transaction->transaction_type == 'subscription') {
$checkoutArgs['subscription_data']['application_fee_percent'] = 1.9; // 1.9%
} else {
// Total amount of 1.9%
$applicationFeeAmount = (int) ($transaction->payment_total * 0.019);
$checkoutArgs['payment_intent_data']['application_fee_amount'] = $applicationFeeAmount;
}
}
$session = CheckoutSession::create($checkoutArgs);
if (!empty($session->error) || is_wp_error($session)) {
$error = __('Something is wrong', 'fluentform');
if (is_wp_error($session)) {
$error = $session->get_error_message();
} else if (!empty($session->error->message)) {
$error = $session->error->message;
}
wp_send_json([
'errors' => __('Stripe Error: ', 'fluentform') . $error
], 423);
}
$this->setMetaData('stripe_session_id', $session->id);
$logData = [
'parent_source_id' => $submission->form_id,
'source_type' => 'submission_item',
'source_id' => $submission->id,
'component' => 'Payment',
'status' => 'info',
'title' => __('Redirect to Stripe', 'fluentform'),
'description' => __('User redirect to Stripe for completing the payment', 'fluentform')
];
do_action('fluentform/log_data', $logData);
wp_send_json_success([
'nextAction' => 'payment',
'actionName' => 'stripeRedirectToCheckout',
'sessionId' => $session->id,
'message' => __('You are redirecting to stripe.com to complete the purchase. Please wait while you are redirecting....', 'fluentform'),
'result' => [
'insert_id' => $submission->id
]
], 200);
}
protected function getPaymentIntentData($transaction, $submission, $form)
{
$data = [
'capture_method' => 'automatic',
'description' => $form->title,
'statement_descriptor_suffix' => StripeSettings::getPaymentDescriptor($form),
];
$paymentSettings = PaymentHelper::getFormSettings($form->id, 'admin');
$intentMeta = $this->getIntentMetaData($submission, $form, $transaction, $paymentSettings);
$data['metadata'] = $intentMeta;
return $data;
}
public function getFormattedItems($currency)
{
$orderItems = $this->getOrderItems();
$discountItems = $this->getDiscountItems();
$discountTotal = 0;
foreach ($discountItems as $item) {
$discountTotal += $item->line_total;
}
$orderTotal = 0;
foreach ($orderItems as $orderItem) {
$orderTotal += $orderItem->line_total;
}
$formattedItems = [];
foreach ($orderItems as $item) {
$price = $item->item_price;
if($discountTotal) {
$price = intval($price - ($discountTotal / $orderTotal) * $price);
}
if (PaymentHelper::isZeroDecimal($currency)) {
$price = intval($price / 100);
}
$quantity = $item->quantity ?: 1;
$stripeLine = [
'amount' => $price,
'currency' => $currency,
'name' => $item->item_name,
'quantity' => $quantity
];
$formattedItems[] = $stripeLine;
}
return $formattedItems;
}
public function handleSessionRedirectBack($data)
{
$type = sanitize_text_field($data['type']);
$submissionId = intval($data['fluentform_payment']);
$this->setSubmissionId($submissionId);
$submission = $this->getSubmission();
if (!$submission) {
return;
}
if ($type == 'success') {
if ($this->getMetaData('is_form_action_fired') == 'yes') {
$returnData = $this->getReturnData();
} else {
$sessionId = $this->getMetaData('stripe_session_id');
$session = CheckoutSession::retrieve($sessionId, [
'expand' => [
'subscription.latest_invoice.payment_intent',
'payment_intent'
]
], $submission->form_id);
if ($session && !is_wp_error($session) && $session->customer) {
$transactionHash = sanitize_text_field($data['transaction_hash']);
$transaction = $this->getTransaction($transactionHash, 'transaction_hash');
$returnData = $this->processStripeSession($session, $submission, $transaction);
} else {
$error = __('Sorry! No Valid payment session found. Please try again');
if (is_wp_error($session)) {
$error = $session->get_error_message();
}
$returnData = [
'insert_id' => $submission->id,
'title' => __('Failed to retrieve session data', 'fluentform'),
'result' => false,
'error' => $error
];
}
}
} else {
$returnData = [
'insert_id' => $submission->id,
'result' => false,
'error' => __('Looks like you have cancelled the payment. Please try again!', 'fluentform')
];
}
$returnData['type'] = $type;
if (!isset($returnData['is_new'])) {
$returnData['is_new'] = false;
}
$this->showPaymentView($returnData);
}
public function processStripeSession($session, $submission, $transaction)
{
$this->setSubmissionId($submission->id);
if ($transaction->status == 'paid' && $submission->payment_status == 'paid') {
return $this->getReturnData();
}
$invoice = empty($session->subscription->latest_invoice) ? $session : $session->subscription->latest_invoice;
$paymentStatus = $this->getIntentSuccessName($invoice->payment_intent);
$this->changeSubmissionPaymentStatus($paymentStatus);
if($transaction->transaction_type == 'subscription') {
$subscriptions = $this->getSubscriptions();
if($subscriptions) {
$this->processSubscriptionSuccess($subscriptions, $invoice, $submission);
$vendorSubscription = $session->subscription;
if($vendorSubscription) {
Plan::maybeSetCancelAt($subscriptions[0], $vendorSubscription);
}
}
}
$this->processOneTimeSuccess($invoice, $transaction, $paymentStatus);
$returnData = $this->completePaymentSubmission(false);
$this->recalculatePaidTotal();
$returnData['is_new'] = $this->getMetaData('is_form_action_fired') === 'yes';
return $returnData;
}
protected function getIntentSuccessName($intent)
{
if (!$intent || !$intent->status) {
return false;
}
$successStatuses = [
'succeeded' => 'paid',
'requires_capture' => 'requires_capture'
];
if (isset($successStatuses[$intent->status])) {
return $successStatuses[$intent->status];
}
return false;
}
protected function getDescriptor($title)
{
$illegal = array('<', '>', '"', "'");
// Remove slashes
$descriptor = stripslashes($title);
// Remove illegal characters
$descriptor = str_replace($illegal, '', $descriptor);
// Trim to 22 characters max
return substr($descriptor, 0, 22);
}
protected function formatAddress($address)
{
$addressArray = [
'line1' => $address->line1,
'line2' => $address->line2,
'city' => $address->city,
'state' => $address->state,
'postal_code' => $address->postal_code,
'country' => $address->country,
];
return implode(', ', array_filter($addressArray));
}
public function handleRefund($event)
{
$data = $event->data->object;
$chargeId = $data->payment_intent;
// Get the Transaction from database
$transaction = wpFluent()->table('fluentform_transactions')
->where('charge_id', $chargeId)
->where('payment_method', 'stripe')
->first();
if (!$transaction) {
// Not our transaction
return;
}
$submission = wpFluent()->table('fluentform_submissions')
->find($transaction->submission_id);
if (!$submission) {
return;
}
$this->setSubmissionId($submission->id);
$amountRefunded = $data->amount_refunded;
if (PaymentHelper::isZeroDecimal($data->currency)) {
$amountRefunded = $amountRefunded * 100;
}
// Remove All Existing Refunds
wpFluent()->table('fluentform_transactions')
->where('submission_id', $submission->id)
->where('transaction_type', 'refund')
->delete();
$this->refund($amountRefunded, $transaction, $submission, 'stripe', $chargeId, 'Refund from Stripe');
}
public function getPaymentMode()
{
$stripeSettings = StripeSettings::getSettings();
return $stripeSettings['payment_mode'];
}
public function processSubscriptionSuccess($subscriptions, $invoice, $submission)
{
foreach ($subscriptions as $subscription) {
$subscriptionStatus = 'active';
if ($subscription->trial_days) {
$subscriptionStatus = 'trialling';
}
$updateData = [
'vendor_customer_id' => $invoice->customer,
'vendor_response' => maybe_serialize($invoice),
];
if(!$subscription->bill_count && $subscriptionStatus == 'active') {
$updateData['bill_count'] = 1;
}
if (!$subscription->vendor_subscription_id) {
$updateData['vendor_subscription_id'] = $invoice->subscription;
}
$this->updateSubscription($subscription->id, $updateData);
$subscription = fluentFormApi('submissions')->getSubscription($subscription->id);
$this->updateSubscriptionStatus($subscription, $subscriptionStatus);
}
$logData = [
'parent_source_id' => $submission->form_id,
'source_type' => 'submission_item',
'source_id' => $submission->id,
'component' => 'Payment',
'status' => 'success',
'title' => __('Stripe Subscription Charged', 'fluentform'),
'description' => __('Stripe recurring subscription successfully initiated', 'fluentform')
];
do_action('fluentform/log_data', $logData);
if (!empty($invoice->payment_intent->charges->data[0])) {
$charge = $invoice->payment_intent->charges->data[0];
$this->recordStripeBillingAddress($charge, $submission);
}
}
protected function recordStripeBillingAddress($charge, $submission)
{
if(!is_array($submission->response)) {
$submission->response = json_decode($submission->response, true);
}
if (isset($submission->response['__checkout_billing_address_details'])) {
return;
}
if (empty($charge->billing_details)) {
return;
}
$billingDetails = $charge->billing_details;
if (!empty($billingDetails->address)) {
$submission->response['__checkout_billing_address_details'] = $billingDetails->address;
}
if (!empty($billingDetails->phone)) {
$submission->response['__stripe_phone'] = $billingDetails->phone;
}
if (!empty($billingDetails->name)) {
$submission->response['__stripe_name'] = $billingDetails->name;
}
if (!empty($billingDetails->email)) {
$submission->response['__stripe_email'] = $billingDetails->email;
}
$this->updateSubmission($submission->id, [
'response' => json_encode($submission->response)
]);
$logData = [
'parent_source_id' => $submission->form_id,
'source_type' => 'submission_item',
'source_id' => $submission->id,
'component' => 'Payment',
'status' => 'info',
'title' => __('Stripe Billing Address Logged', 'fluentform'),
'description' => __('Billing address from stripe has been logged in the submission data', 'fluentform')
];
do_action('fluentform/log_data', $logData);
}
protected function processOneTimeSuccess($invoice, $transaction, $paymentStatus)
{
if ($transaction) {
$updateData = [
'charge_id' => $invoice->payment_intent ? $invoice->payment_intent->id : null,
'status' => 'paid',
'payment_note' => maybe_serialize($invoice->payment_intent)
];
$updateData = array_merge($updateData, $this->retrieveCustomerDetailsFromInvoice($invoice));
$this->updateTransaction($transaction->id, $updateData);
$this->changeTransactionStatus($transaction->id, $paymentStatus);
}
}
protected function getIntentMetaData($submission, $form, $transaction, $paymentSettings = false)
{
if (!$paymentSettings) {
$paymentSettings = PaymentHelper::getFormSettings($form->id, 'admin');
}
$intentMeta = [
'submission_id' => $submission->id,
'form_id' => $form->id,
'transaction_id' => $transaction->id,
'wp_plugin' => 'Fluent Forms Pro',
'form_title' => $form->title
];
$metaItems = ArrayHelper::get($paymentSettings, 'stripe_meta_data', []);
if ((ArrayHelper::get($paymentSettings, 'push_meta_to_stripe') == 'yes') && !empty($metaItems)) {
foreach ($metaItems as $metaItem) {
if ($itemValue = ArrayHelper::get($metaItem, 'item_value')) {
$metaData[ArrayHelper::get($metaItem, 'label', 'item')] = $itemValue;
}
}
$metaData = ShortCodeParser::parse($metaData, $submission->id, $submission->response);
$metaData = array_filter($metaData);
foreach ($metaData as $itemKey => $value) {
if (is_string($value) || is_numeric($value)) {
$metaData[$itemKey] = strip_tags($value);
}
}
$intentMeta = array_merge($intentMeta, $metaData);
}
return $intentMeta;
}
protected function retrieveCustomerDetailsFromInvoice($invoice)
{
$customer = [];
if (!empty($invoice->payment_intent->charges->data[0])) {
$charge = $invoice->payment_intent->charges->data[0];
$customer = $this->retrieveCustomerDetailsFromCharge($charge);
$customerName = isset($charge->billing_details->name) &&
!empty($charge->billing_details->name) ?
$charge->billing_details->name : (
isset($invoice->customer_name) &&
!empty($invoice->customer_name) ?
$invoice->customer_name : ''
);
$customer = array_merge($customer, [
'payer_email' => $invoice->customer_email,
'payer_name' => $customerName,
]);
}
return $customer;
}
protected function retrieveCustomerDetailsFromCharge($charge)
{
$customer = [];
if (!empty($charge->billing_details)) {
$customer['billing_address'] = $this->formatAddress($charge->billing_details->address);
}
if (!empty($charge->shipping) && !empty($charge->shipping->address)) {
$customer['shipping_address'] = $this->formatAddress($charge->shipping->address);
}
if (!empty($charge->payment_method_details->card)) {
$card = $charge->payment_method_details->card;
$customer['card_brand'] = $card->brand;
$customer['card_last_4'] = $card->last4;
}
return $customer;
}
protected function handlePaymentChargeError($message, $submission, $transaction, $charge = false, $type = 'general')
{
do_action_deprecated(
'fluentform_payment_stripe_failed',
[
$submission,
$transaction,
$this->form->id,
$charge,
$type
],
FLUENTFORM_FRAMEWORK_UPGRADE,
'fluentform/payment_stripe_failed',
'Use fluentform/payment_stripe_failed instead of fluentform_payment_stripe_failed.'
);
do_action('fluentform/payment_stripe_failed', $submission, $transaction, $this->form->id, $charge, $type);
do_action_deprecated(
'fluentform_payment_failed',
[
$submission,
$transaction,
$this->form->id,
$charge,
$type
],
FLUENTFORM_FRAMEWORK_UPGRADE,
'fluentform/payment_failed',
'Use fluentform/payment_failed instead of fluentform_payment_failed.'
);
do_action('fluentform/payment_failed', $submission, $transaction, $this->form->id, $charge, $type);
if ($transaction) {
$this->changeTransactionStatus($transaction->id, 'failed');
}
$this->changeSubmissionPaymentStatus('failed');
if ($message) {
$logData = [
'parent_source_id' => $submission->form_id,
'source_type' => 'submission_item',
'source_id' => $submission->id,
'component' => 'Payment',
'status' => 'error',
'title' => __('Stripe Payment Error', 'fluentform'),
'description' => __($message, 'fluentform')
];
do_action('fluentform/log_data', $logData);
}
wp_send_json([
'errors' => __('Stripe Error: ', 'fluentform') . $message,
'append_data' => [
'__entry_intermediate_hash' => Helper::getSubmissionMeta($submission->id, '__entry_intermediate_hash')
]
], 423);
}
public function recordSubscriptionCharge($subscription, $transactionData)
{
$this->setSubmissionId($subscription->submission_id);
return $this->maybeInsertSubscriptionCharge($transactionData);
}
}