| 1 |
<?php |
| 2 |
|
| 3 |
namespace FluentForm\App\Modules\Payments\PaymentMethods\Stripe; |
| 4 |
|
| 5 |
if (!defined('ABSPATH')) { |
| 6 |
exit; // Exit if accessed directly. |
| 7 |
} |
| 8 |
|
| 9 |
use FluentForm\App\Helpers\Helper; |
| 10 |
use FluentForm\App\Models\Submission; |
| 11 |
use FluentForm\App\Models\Transaction; |
| 12 |
use FluentForm\App\Services\FormBuilder\ShortCodeParser; |
| 13 |
use FluentForm\Framework\Helpers\ArrayHelper; |
| 14 |
use FluentForm\App\Modules\Payments\PaymentHelper; |
| 15 |
use FluentForm\App\Modules\Payments\PaymentMethods\BaseProcessor; |
| 16 |
use FluentForm\App\Modules\Payments\PaymentMethods\Stripe\API\CheckoutSession; |
| 17 |
use FluentForm\App\Modules\Payments\PaymentMethods\Stripe\API\Plan; |
| 18 |
|
| 19 |
class StripeProcessor extends BaseProcessor |
| 20 |
{ |
| 21 |
public $method = 'stripe'; |
| 22 |
|
| 23 |
protected $form; |
| 24 |
|
| 25 |
public function init() |
| 26 |
{ |
| 27 |
add_action('fluentform/process_payment_stripe_hosted', array($this, 'handlePaymentAction'), 10, 6); |
| 28 |
add_action('fluentform/payment_frameless_' . $this->method, array($this, 'handleSessionRedirectBack')); |
| 29 |
} |
| 30 |
|
| 31 |
public function handlePaymentAction($submissionId, $submissionData, $form, $methodSettings, $hasSubscriptions, $totalPayable) |
| 32 |
{ |
| 33 |
$this->setSubmissionId($submissionId); |
| 34 |
$this->form = $form; |
| 35 |
$submission = $this->getSubmission(); |
| 36 |
$paymentTotal = $this->getAmountTotal(); |
| 37 |
|
| 38 |
if (!$paymentTotal && !$hasSubscriptions) { |
| 39 |
return false; |
| 40 |
} |
| 41 |
|
| 42 |
// Create the initial transaction here |
| 43 |
$transaction = $this->createInitialPendingTransaction($submission, $hasSubscriptions); |
| 44 |
|
| 45 |
$this->handleCheckoutSession($transaction, $submission, $form, $methodSettings); |
| 46 |
} |
| 47 |
|
| 48 |
public function handleCheckoutSession($transaction, $submission, $form, $methodSettings) |
| 49 |
{ |
| 50 |
|
| 51 |
$formSettings = PaymentHelper::getFormSettings($form->id); |
| 52 |
|
| 53 |
$args = [ |
| 54 |
'fluentform_payment' => $submission->id, |
| 55 |
'payment_method' => $this->method, |
| 56 |
'transaction_hash' => $transaction->transaction_hash, |
| 57 |
'type' => 'success' |
| 58 |
]; |
| 59 |
|
| 60 |
|
| 61 |
$successUrl = add_query_arg($args, site_url('index.php')); |
| 62 |
|
| 63 |
$cancelUrl = $submission->source_url; |
| 64 |
|
| 65 |
if (!wp_http_validate_url($cancelUrl)) { |
| 66 |
$cancelUrl = site_url($cancelUrl); |
| 67 |
} |
| 68 |
|
| 69 |
|
| 70 |
$checkoutArgs = [ |
| 71 |
'cancel_url' => wp_sanitize_redirect($cancelUrl), |
| 72 |
'success_url' => wp_sanitize_redirect($successUrl), |
| 73 |
'locale' => 'auto', |
| 74 |
'billing_address_collection' => 'auto', |
| 75 |
'client_reference_id' => $submission->id, |
| 76 |
'customer_email' => $transaction->payer_email, |
| 77 |
'metadata' => [ |
| 78 |
'submission_id' => $submission->id, |
| 79 |
'form_id' => $form->id, |
| 80 |
'transaction_id' => ($transaction) ? $transaction->id : '' |
| 81 |
] |
| 82 |
]; |
| 83 |
|
| 84 |
if (ArrayHelper::get($methodSettings, 'settings.require_billing_info.value') == 'yes') { |
| 85 |
$checkoutArgs['billing_address_collection'] = 'required'; |
| 86 |
} |
| 87 |
|
| 88 |
if (ArrayHelper::get($methodSettings, 'settings.require_shipping_info.value') == 'yes') { |
| 89 |
$checkoutArgs['shipping_address_collection'] = [ |
| 90 |
'allowed_countries' => StripeSettings::supportedShippingCountries() |
| 91 |
]; |
| 92 |
} |
| 93 |
|
| 94 |
if ($lineItems = $this->getFormattedItems($submission->currency)) { |
| 95 |
$checkoutArgs['line_items'] = $lineItems; |
| 96 |
} |
| 97 |
|
| 98 |
$receiptEmail = PaymentHelper::getCustomerEmail($submission, $form); |
| 99 |
|
| 100 |
if ($receiptEmail) { |
| 101 |
$checkoutArgs['customer_email'] = $receiptEmail; |
| 102 |
} |
| 103 |
|
| 104 |
if ($transaction->transaction_type == 'subscription') { |
| 105 |
unset($checkoutArgs['submit_type']); |
| 106 |
|
| 107 |
$subscriptions = $this->getSubscriptions(); |
| 108 |
$subscription = $subscriptions[0]; |
| 109 |
|
| 110 |
if ($subscription->initial_amount) { |
| 111 |
if (!isset($checkoutArgs['line_items'])) { |
| 112 |
$checkoutArgs['line_items'] = []; |
| 113 |
} |
| 114 |
$price = $subscription->initial_amount; |
| 115 |
if (PaymentHelper::isZeroDecimal($transaction->currency)) { |
| 116 |
$price = intval($price / 100); |
| 117 |
} |
| 118 |
$checkoutArgs['line_items'][] = [ |
| 119 |
'amount' => $price, |
| 120 |
'currency' => $transaction->currency, |
| 121 |
/* translators: %s is the plan name */ |
| 122 |
'name' => sprintf(__('Signup fee for %s', 'fluentform'), $subscription->plan_name), |
| 123 |
'quantity' => 1 |
| 124 |
]; |
| 125 |
} |
| 126 |
|
| 127 |
$stripePlan = Plan::getSubscriptionPlanBySubscription($subscription, $transaction->currency); |
| 128 |
|
| 129 |
if(is_wp_error($stripePlan)) { |
| 130 |
$this->handlePaymentChargeError($stripePlan->get_error_message(), $submission, $transaction); |
| 131 |
} |
| 132 |
|
| 133 |
$this->updateSubscription($subscription->id, [ |
| 134 |
'vendor_plan_id' => $stripePlan->id |
| 135 |
]); |
| 136 |
|
| 137 |
$subsArgs = [ |
| 138 |
'items' => [ |
| 139 |
[ |
| 140 |
'plan' => $stripePlan->id, |
| 141 |
'quantity' => $subscription->quantity ? $subscription->quantity : 1 |
| 142 |
] |
| 143 |
] |
| 144 |
]; |
| 145 |
|
| 146 |
if ($subscription->trial_days) { |
| 147 |
$subsArgs['trial_period_days'] = $subscription->trial_days; |
| 148 |
} |
| 149 |
|
| 150 |
$subsArgs['metadata'] = $this->getIntentMetaData($submission, $form, $transaction); |
| 151 |
|
| 152 |
$checkoutArgs['subscription_data'] = $subsArgs; |
| 153 |
|
| 154 |
} else { |
| 155 |
$checkoutArgs['submit_type'] = 'auto'; |
| 156 |
$checkoutArgs['payment_intent_data'] = $this->getPaymentIntentData($transaction, $submission, $form); |
| 157 |
if ($receiptEmail && ArrayHelper::get($formSettings, 'disable_stripe_payment_receipt') != 'yes') { |
| 158 |
$checkoutArgs['payment_intent_data']['receipt_email'] = $receiptEmail; |
| 159 |
} |
| 160 |
} |
| 161 |
if ($formSettings['transaction_type'] == 'donation' && $transaction->transaction_type != 'subscription') { |
| 162 |
$checkoutArgs['submit_type'] = 'donate'; |
| 163 |
} |
| 164 |
|
| 165 |
$checkoutArgs = apply_filters_deprecated( |
| 166 |
'fluentform_stripe_checkout_args', |
| 167 |
[ |
| 168 |
$checkoutArgs, |
| 169 |
$submission, |
| 170 |
$transaction, |
| 171 |
$form |
| 172 |
], |
| 173 |
FLUENTFORM_FRAMEWORK_UPGRADE, |
| 174 |
'fluentform/stripe_checkout_args', |
| 175 |
'Use fluentform/stripe_checkout_args instead of fluentform_stripe_checkout_args.' |
| 176 |
); |
| 177 |
|
| 178 |
$checkoutArgs = apply_filters('fluentform/stripe_checkout_args', $checkoutArgs, $submission, $transaction, $form); |
| 179 |
|
| 180 |
// If FluentForm Pro is not installed, apply the fee 1.9% |
| 181 |
if (!Helper::hasPro()) { |
| 182 |
if ($transaction->transaction_type == 'subscription') { |
| 183 |
$checkoutArgs['subscription_data']['application_fee_percent'] = 1.9; // 1.9% |
| 184 |
} else { |
| 185 |
// Total amount of 1.9% |
| 186 |
$applicationFeeAmount = (int) ($transaction->payment_total * 0.019); |
| 187 |
$checkoutArgs['payment_intent_data']['application_fee_amount'] = $applicationFeeAmount; |
| 188 |
} |
| 189 |
} |
| 190 |
|
| 191 |
$session = CheckoutSession::create($checkoutArgs); |
| 192 |
|
| 193 |
if (!empty($session->error) || is_wp_error($session)) { |
| 194 |
$error = __('Something is wrong', 'fluentform'); |
| 195 |
if (is_wp_error($session)) { |
| 196 |
$error = $session->get_error_message(); |
| 197 |
} else if (!empty($session->error->message)) { |
| 198 |
$error = $session->error->message; |
| 199 |
} |
| 200 |
$errorMessage = __('Stripe Error: ', 'fluentform') . $error; |
| 201 |
wp_send_json([ |
| 202 |
'errors' => apply_filters('fluentform/payment_error_message', $errorMessage, $submission, $this->form) |
| 203 |
], 423); |
| 204 |
} |
| 205 |
|
| 206 |
$this->setMetaData('stripe_session_id', $session->id); |
| 207 |
|
| 208 |
$logData = [ |
| 209 |
'parent_source_id' => $submission->form_id, |
| 210 |
'source_type' => 'submission_item', |
| 211 |
'source_id' => $submission->id, |
| 212 |
'component' => 'Payment', |
| 213 |
'status' => 'info', |
| 214 |
'title' => __('Redirect to Stripe', 'fluentform'), |
| 215 |
'description' => __('User redirect to Stripe for completing the payment', 'fluentform') |
| 216 |
]; |
| 217 |
do_action('fluentform/log_data', $logData); |
| 218 |
|
| 219 |
wp_send_json_success([ |
| 220 |
'nextAction' => 'payment', |
| 221 |
'actionName' => 'stripeRedirectToCheckout', |
| 222 |
'sessionId' => $session->id, |
| 223 |
'message' => __('You are redirecting to stripe.com to complete the purchase. Please wait while you are redirecting....', 'fluentform'), |
| 224 |
'result' => [ |
| 225 |
'insert_id' => $submission->id |
| 226 |
] |
| 227 |
], 200); |
| 228 |
} |
| 229 |
|
| 230 |
protected function getPaymentIntentData($transaction, $submission, $form) |
| 231 |
{ |
| 232 |
$data = [ |
| 233 |
'capture_method' => 'automatic', |
| 234 |
'description' => $form->title, |
| 235 |
'statement_descriptor_suffix' => StripeSettings::getPaymentDescriptor($form), |
| 236 |
]; |
| 237 |
|
| 238 |
$paymentSettings = PaymentHelper::getFormSettings($form->id, 'admin'); |
| 239 |
$intentMeta = $this->getIntentMetaData($submission, $form, $transaction, $paymentSettings); |
| 240 |
|
| 241 |
$data['metadata'] = $intentMeta; |
| 242 |
return $data; |
| 243 |
} |
| 244 |
|
| 245 |
public function getFormattedItems($currency) |
| 246 |
{ |
| 247 |
$orderItems = $this->getOrderItems(); |
| 248 |
|
| 249 |
$discountItems = $this->getDiscountItems(); |
| 250 |
|
| 251 |
$discountTotal = 0; |
| 252 |
foreach ($discountItems as $item) { |
| 253 |
$discountTotal += $item->line_total; |
| 254 |
} |
| 255 |
|
| 256 |
$orderTotal = 0; |
| 257 |
foreach ($orderItems as $orderItem) { |
| 258 |
$orderTotal += $orderItem->line_total; |
| 259 |
} |
| 260 |
|
| 261 |
$formattedItems = []; |
| 262 |
$discountDistributed = 0; |
| 263 |
$itemCount = count($orderItems); |
| 264 |
|
| 265 |
foreach ($orderItems as $index => $item) { |
| 266 |
$price = $item->item_price; |
| 267 |
$quantity = $item->quantity ?: 1; |
| 268 |
|
| 269 |
if ($discountTotal && $orderTotal) { |
| 270 |
if ($index === $itemCount - 1) { |
| 271 |
// Last item absorbs any remaining discount not yet distributed. |
| 272 |
// intval floors the per-unit price, so (unitPrice * quantity) may be |
| 273 |
// less than lineTotal. The difference (remainder) is 0..quantity-1 cents. |
| 274 |
// When remainder > 0 we split into two Stripe line items: |
| 275 |
// (quantity - remainder) units at the floor price, and |
| 276 |
// remainder units at floor + 1 cent, |
| 277 |
// so the total adds up exactly. e.g. $28.00 / 3 → 2×$9.33 + 1×$9.34 = $28.00 |
| 278 |
$remainingDiscount = $discountTotal - $discountDistributed; |
| 279 |
$lineTotal = ($price * $quantity) - $remainingDiscount; |
| 280 |
$unitPrice = intval($lineTotal / $quantity); |
| 281 |
$remainder = $lineTotal - ($unitPrice * $quantity); |
| 282 |
|
| 283 |
if ($remainder > 0 && $quantity > 1) { |
| 284 |
$basePrice = $unitPrice; |
| 285 |
$extraPrice = $unitPrice + 1; |
| 286 |
|
| 287 |
if (PaymentHelper::isZeroDecimal($currency)) { |
| 288 |
$basePrice = intval($basePrice / 100); |
| 289 |
$extraPrice = intval($extraPrice / 100); |
| 290 |
} |
| 291 |
|
| 292 |
$formattedItems[] = [ |
| 293 |
'amount' => $basePrice, |
| 294 |
'currency' => $currency, |
| 295 |
'name' => $item->item_name, |
| 296 |
'quantity' => $quantity - $remainder |
| 297 |
]; |
| 298 |
$formattedItems[] = [ |
| 299 |
'amount' => $extraPrice, |
| 300 |
'currency' => $currency, |
| 301 |
'name' => $item->item_name, |
| 302 |
'quantity' => $remainder |
| 303 |
]; |
| 304 |
continue; |
| 305 |
} |
| 306 |
|
| 307 |
$price = $unitPrice; |
| 308 |
} else { |
| 309 |
$itemDiscount = (int) round(($discountTotal / $orderTotal) * $price); |
| 310 |
$discountDistributed += $itemDiscount * $quantity; |
| 311 |
$price = $price - $itemDiscount; |
| 312 |
} |
| 313 |
} |
| 314 |
|
| 315 |
if (PaymentHelper::isZeroDecimal($currency)) { |
| 316 |
$price = intval($price / 100); |
| 317 |
} |
| 318 |
|
| 319 |
$stripeLine = [ |
| 320 |
'amount' => $price, |
| 321 |
'currency' => $currency, |
| 322 |
'name' => $item->item_name, |
| 323 |
'quantity' => $quantity |
| 324 |
]; |
| 325 |
|
| 326 |
$formattedItems[] = $stripeLine; |
| 327 |
} |
| 328 |
|
| 329 |
return $formattedItems; |
| 330 |
} |
| 331 |
|
| 332 |
public function handleSessionRedirectBack($data) |
| 333 |
{ |
| 334 |
$type = sanitize_text_field($data['type']); |
| 335 |
$submissionId = intval($data['fluentform_payment']); |
| 336 |
$this->setSubmissionId($submissionId); |
| 337 |
|
| 338 |
$submission = $this->getSubmission(); |
| 339 |
|
| 340 |
if (!$submission) { |
| 341 |
return; |
| 342 |
} |
| 343 |
|
| 344 |
if ($type == 'success') { |
| 345 |
if ($this->getMetaData('is_form_action_fired') == 'yes') { |
| 346 |
$returnData = $this->getReturnData(); |
| 347 |
} else { |
| 348 |
$sessionId = $this->getMetaData('stripe_session_id'); |
| 349 |
$session = CheckoutSession::retrieve($sessionId, [ |
| 350 |
'expand' => [ |
| 351 |
'subscription.latest_invoice.payment_intent', |
| 352 |
'payment_intent' |
| 353 |
] |
| 354 |
], $submission->form_id); |
| 355 |
|
| 356 |
if ($session && !is_wp_error($session) && $session->customer) { |
| 357 |
$transactionHash = sanitize_text_field($data['transaction_hash']); |
| 358 |
$transaction = $this->getTransaction($transactionHash, 'transaction_hash'); |
| 359 |
$returnData = $this->processStripeSession($session, $submission, $transaction); |
| 360 |
} else { |
| 361 |
$error = __('Sorry! No Valid payment session found. Please try again','fluentform'); |
| 362 |
if (is_wp_error($session)) { |
| 363 |
$error = $session->get_error_message(); |
| 364 |
} |
| 365 |
$returnData = [ |
| 366 |
'insert_id' => $submission->id, |
| 367 |
'title' => __('Failed to retrieve session data', 'fluentform'), |
| 368 |
'result' => false, |
| 369 |
'error' => $error |
| 370 |
]; |
| 371 |
} |
| 372 |
} |
| 373 |
} else { |
| 374 |
$returnData = [ |
| 375 |
'insert_id' => $submission->id, |
| 376 |
'result' => false, |
| 377 |
'error' => apply_filters('fluentform/stripe_payment_cancelled_message', __('Looks like you have cancelled the payment. Please try again!', 'fluentform'), $submission, $this->form) |
| 378 |
]; |
| 379 |
} |
| 380 |
|
| 381 |
$returnData['type'] = $type; |
| 382 |
|
| 383 |
if (!isset($returnData['is_new'])) { |
| 384 |
$returnData['is_new'] = false; |
| 385 |
} |
| 386 |
|
| 387 |
$this->showPaymentView($returnData); |
| 388 |
} |
| 389 |
|
| 390 |
public function processStripeSession($session, $submission, $transaction) |
| 391 |
{ |
| 392 |
$this->setSubmissionId($submission->id); |
| 393 |
|
| 394 |
if ($transaction->status == 'paid' && $submission->payment_status == 'paid') { |
| 395 |
return $this->getReturnData(); |
| 396 |
} |
| 397 |
|
| 398 |
$invoice = empty($session->subscription->latest_invoice) ? $session : $session->subscription->latest_invoice; |
| 399 |
|
| 400 |
$paymentStatus = $this->getIntentSuccessName($invoice->payment_intent); |
| 401 |
|
| 402 |
$this->changeSubmissionPaymentStatus($paymentStatus); |
| 403 |
|
| 404 |
if($transaction->transaction_type == 'subscription') { |
| 405 |
$subscriptions = $this->getSubscriptions(); |
| 406 |
|
| 407 |
if($subscriptions) { |
| 408 |
$this->processSubscriptionSuccess($subscriptions, $invoice, $submission); |
| 409 |
|
| 410 |
$vendorSubscription = $session->subscription; |
| 411 |
if($vendorSubscription) { |
| 412 |
Plan::maybeSetCancelAt($subscriptions[0], $vendorSubscription); |
| 413 |
} |
| 414 |
} |
| 415 |
} |
| 416 |
|
| 417 |
$this->processOneTimeSuccess($invoice, $transaction, $paymentStatus); |
| 418 |
|
| 419 |
$returnData = $this->completePaymentSubmission(false); |
| 420 |
$this->recalculatePaidTotal(); |
| 421 |
$returnData['is_new'] = $this->getMetaData('is_form_action_fired') === 'yes'; |
| 422 |
|
| 423 |
return $returnData; |
| 424 |
} |
| 425 |
|
| 426 |
protected function getIntentSuccessName($intent) |
| 427 |
{ |
| 428 |
if (!$intent || !$intent->status) { |
| 429 |
return false; |
| 430 |
} |
| 431 |
|
| 432 |
$successStatuses = [ |
| 433 |
'succeeded' => 'paid', |
| 434 |
'requires_capture' => 'requires_capture' |
| 435 |
]; |
| 436 |
|
| 437 |
if (isset($successStatuses[$intent->status])) { |
| 438 |
return $successStatuses[$intent->status]; |
| 439 |
} |
| 440 |
|
| 441 |
return false; |
| 442 |
} |
| 443 |
|
| 444 |
protected function getDescriptor($title) |
| 445 |
{ |
| 446 |
$illegal = array('<', '>', '"', "'"); |
| 447 |
// Remove slashes |
| 448 |
$descriptor = stripslashes($title); |
| 449 |
// Remove illegal characters |
| 450 |
$descriptor = str_replace($illegal, '', $descriptor); |
| 451 |
// Trim to 22 characters max |
| 452 |
return substr($descriptor, 0, 22); |
| 453 |
} |
| 454 |
|
| 455 |
protected function formatAddress($address) |
| 456 |
{ |
| 457 |
$addressArray = [ |
| 458 |
'line1' => $address->line1, |
| 459 |
'line2' => $address->line2, |
| 460 |
'city' => $address->city, |
| 461 |
'state' => $address->state, |
| 462 |
'postal_code' => $address->postal_code, |
| 463 |
'country' => $address->country, |
| 464 |
]; |
| 465 |
return implode(', ', array_filter($addressArray)); |
| 466 |
} |
| 467 |
|
| 468 |
public function handleRefund($event) |
| 469 |
{ |
| 470 |
$data = $event->data->object; |
| 471 |
|
| 472 |
$chargeId = $data->payment_intent; |
| 473 |
// Get the Transaction from database |
| 474 |
$transaction = Transaction::where('charge_id', $chargeId) |
| 475 |
->where('payment_method', 'stripe') |
| 476 |
->first(); |
| 477 |
|
| 478 |
if (!$transaction) { |
| 479 |
// Not our transaction |
| 480 |
return; |
| 481 |
} |
| 482 |
|
| 483 |
$submission = Submission::find($transaction->submission_id); |
| 484 |
|
| 485 |
if (!$submission) { |
| 486 |
return; |
| 487 |
} |
| 488 |
|
| 489 |
$this->setSubmissionId($submission->id); |
| 490 |
$amountRefunded = $data->amount_refunded; |
| 491 |
if (PaymentHelper::isZeroDecimal($data->currency)) { |
| 492 |
$amountRefunded = $amountRefunded * 100; |
| 493 |
} |
| 494 |
|
| 495 |
// Remove All Existing Refunds |
| 496 |
Transaction::bySubmission($submission->id)->refunds()->delete(); |
| 497 |
|
| 498 |
$this->refund($amountRefunded, $transaction, $submission, 'stripe', $chargeId, 'Refund from Stripe'); |
| 499 |
|
| 500 |
} |
| 501 |
|
| 502 |
public function getPaymentMode() |
| 503 |
{ |
| 504 |
$stripeSettings = StripeSettings::getSettings(); |
| 505 |
return $stripeSettings['payment_mode']; |
| 506 |
} |
| 507 |
|
| 508 |
public function processSubscriptionSuccess($subscriptions, $invoice, $submission) |
| 509 |
{ |
| 510 |
foreach ($subscriptions as $subscription) { |
| 511 |
$subscriptionStatus = 'active'; |
| 512 |
|
| 513 |
if ($subscription->trial_days) { |
| 514 |
$subscriptionStatus = 'trialling'; |
| 515 |
} |
| 516 |
|
| 517 |
$updateData = [ |
| 518 |
'vendor_customer_id' => $invoice->customer, |
| 519 |
'vendor_response' => maybe_serialize($invoice), |
| 520 |
]; |
| 521 |
|
| 522 |
if(!$subscription->bill_count && $subscriptionStatus == 'active') { |
| 523 |
$updateData['bill_count'] = 1; |
| 524 |
} |
| 525 |
|
| 526 |
if (!$subscription->vendor_subscription_id) { |
| 527 |
$updateData['vendor_subscription_id'] = $invoice->subscription; |
| 528 |
} |
| 529 |
|
| 530 |
$this->updateSubscription($subscription->id, $updateData); |
| 531 |
$subscription = fluentFormApi('submissions')->getSubscription($subscription->id); |
| 532 |
$this->updateSubscriptionStatus($subscription, $subscriptionStatus); |
| 533 |
} |
| 534 |
|
| 535 |
$logData = [ |
| 536 |
'parent_source_id' => $submission->form_id, |
| 537 |
'source_type' => 'submission_item', |
| 538 |
'source_id' => $submission->id, |
| 539 |
'component' => 'Payment', |
| 540 |
'status' => 'success', |
| 541 |
'title' => __('Stripe Subscription Charged', 'fluentform'), |
| 542 |
'description' => __('Stripe recurring subscription successfully initiated', 'fluentform') |
| 543 |
]; |
| 544 |
|
| 545 |
do_action('fluentform/log_data', $logData); |
| 546 |
|
| 547 |
if (!empty($invoice->payment_intent->charges->data[0])) { |
| 548 |
$charge = $invoice->payment_intent->charges->data[0]; |
| 549 |
$this->recordStripeBillingAddress($charge, $submission); |
| 550 |
} |
| 551 |
} |
| 552 |
|
| 553 |
protected function recordStripeBillingAddress($charge, $submission) |
| 554 |
{ |
| 555 |
if(!is_array($submission->response)) { |
| 556 |
$submission->response = json_decode($submission->response, true); |
| 557 |
} |
| 558 |
|
| 559 |
if (isset($submission->response['__checkout_billing_address_details'])) { |
| 560 |
return; |
| 561 |
} |
| 562 |
|
| 563 |
if (empty($charge->billing_details)) { |
| 564 |
return; |
| 565 |
} |
| 566 |
|
| 567 |
$billingDetails = $charge->billing_details; |
| 568 |
if (!empty($billingDetails->address)) { |
| 569 |
$submission->response['__checkout_billing_address_details'] = $billingDetails->address; |
| 570 |
} |
| 571 |
if (!empty($billingDetails->phone)) { |
| 572 |
$submission->response['__stripe_phone'] = $billingDetails->phone; |
| 573 |
} |
| 574 |
if (!empty($billingDetails->name)) { |
| 575 |
$submission->response['__stripe_name'] = $billingDetails->name; |
| 576 |
} |
| 577 |
if (!empty($billingDetails->email)) { |
| 578 |
$submission->response['__stripe_email'] = $billingDetails->email; |
| 579 |
} |
| 580 |
|
| 581 |
$this->updateSubmission($submission->id, [ |
| 582 |
'response' => json_encode($submission->response) |
| 583 |
]); |
| 584 |
|
| 585 |
$logData = [ |
| 586 |
'parent_source_id' => $submission->form_id, |
| 587 |
'source_type' => 'submission_item', |
| 588 |
'source_id' => $submission->id, |
| 589 |
'component' => 'Payment', |
| 590 |
'status' => 'info', |
| 591 |
'title' => __('Stripe Billing Address Logged', 'fluentform'), |
| 592 |
'description' => __('Billing address from stripe has been logged in the submission data', 'fluentform') |
| 593 |
]; |
| 594 |
|
| 595 |
do_action('fluentform/log_data', $logData); |
| 596 |
} |
| 597 |
|
| 598 |
protected function processOneTimeSuccess($invoice, $transaction, $paymentStatus) |
| 599 |
{ |
| 600 |
if ($transaction) { |
| 601 |
$updateData = [ |
| 602 |
'charge_id' => $invoice->payment_intent ? $invoice->payment_intent->id : null, |
| 603 |
'status' => 'paid', |
| 604 |
'payment_note' => maybe_serialize($invoice->payment_intent) |
| 605 |
]; |
| 606 |
|
| 607 |
$updateData = array_merge($updateData, $this->retrieveCustomerDetailsFromInvoice($invoice)); |
| 608 |
|
| 609 |
$this->updateTransaction($transaction->id, $updateData); |
| 610 |
|
| 611 |
$this->changeTransactionStatus($transaction->id, $paymentStatus); |
| 612 |
} |
| 613 |
} |
| 614 |
|
| 615 |
protected function getIntentMetaData($submission, $form, $transaction, $paymentSettings = false) |
| 616 |
{ |
| 617 |
if (!$paymentSettings) { |
| 618 |
$paymentSettings = PaymentHelper::getFormSettings($form->id, 'admin'); |
| 619 |
} |
| 620 |
|
| 621 |
$intentMeta = [ |
| 622 |
'submission_id' => $submission->id, |
| 623 |
'form_id' => $form->id, |
| 624 |
'transaction_id' => $transaction->id, |
| 625 |
'wp_plugin' => 'Fluent Forms Pro', |
| 626 |
'form_title' => $form->title |
| 627 |
]; |
| 628 |
|
| 629 |
$metaItems = ArrayHelper::get($paymentSettings, 'stripe_meta_data', []); |
| 630 |
if ((ArrayHelper::get($paymentSettings, 'push_meta_to_stripe') == 'yes') && !empty($metaItems)) { |
| 631 |
|
| 632 |
foreach ($metaItems as $metaItem) { |
| 633 |
if ($itemValue = ArrayHelper::get($metaItem, 'item_value')) { |
| 634 |
$metaData[ArrayHelper::get($metaItem, 'label', 'item')] = $itemValue; |
| 635 |
} |
| 636 |
} |
| 637 |
|
| 638 |
$metaData = ShortCodeParser::parse($metaData, $submission->id, $submission->response); |
| 639 |
|
| 640 |
$metaData = array_filter($metaData); |
| 641 |
|
| 642 |
foreach ($metaData as $itemKey => $value) { |
| 643 |
if (is_string($value) || is_numeric($value)) { |
| 644 |
$metaData[$itemKey] = wp_strip_all_tags($value); |
| 645 |
} |
| 646 |
} |
| 647 |
|
| 648 |
$intentMeta = array_merge($intentMeta, $metaData); |
| 649 |
} |
| 650 |
|
| 651 |
return $intentMeta; |
| 652 |
} |
| 653 |
|
| 654 |
protected function retrieveCustomerDetailsFromInvoice($invoice) |
| 655 |
{ |
| 656 |
$customer = []; |
| 657 |
|
| 658 |
if (!empty($invoice->payment_intent->charges->data[0])) { |
| 659 |
$charge = $invoice->payment_intent->charges->data[0]; |
| 660 |
|
| 661 |
$customer = $this->retrieveCustomerDetailsFromCharge($charge); |
| 662 |
|
| 663 |
$customerName = isset($charge->billing_details->name) && |
| 664 |
!empty($charge->billing_details->name) ? |
| 665 |
$charge->billing_details->name : ( |
| 666 |
isset($invoice->customer_name) && |
| 667 |
!empty($invoice->customer_name) ? |
| 668 |
$invoice->customer_name : '' |
| 669 |
); |
| 670 |
|
| 671 |
$customer = array_merge($customer, [ |
| 672 |
'payer_email' => $invoice->customer_email, |
| 673 |
'payer_name' => $customerName, |
| 674 |
]); |
| 675 |
} |
| 676 |
|
| 677 |
return $customer; |
| 678 |
} |
| 679 |
|
| 680 |
protected function retrieveCustomerDetailsFromCharge($charge) |
| 681 |
{ |
| 682 |
$customer = []; |
| 683 |
|
| 684 |
if (!empty($charge->billing_details)) { |
| 685 |
$customer['billing_address'] = $this->formatAddress($charge->billing_details->address); |
| 686 |
} |
| 687 |
|
| 688 |
if (!empty($charge->shipping) && !empty($charge->shipping->address)) { |
| 689 |
$customer['shipping_address'] = $this->formatAddress($charge->shipping->address); |
| 690 |
} |
| 691 |
|
| 692 |
if (!empty($charge->payment_method_details->card)) { |
| 693 |
$card = $charge->payment_method_details->card; |
| 694 |
$customer['card_brand'] = $card->brand; |
| 695 |
$customer['card_last_4'] = $card->last4; |
| 696 |
} |
| 697 |
|
| 698 |
return $customer; |
| 699 |
} |
| 700 |
|
| 701 |
protected function handlePaymentChargeError($message, $submission, $transaction, $charge = false, $type = 'general') |
| 702 |
{ |
| 703 |
do_action_deprecated( |
| 704 |
'fluentform_payment_stripe_failed', |
| 705 |
[ |
| 706 |
$submission, |
| 707 |
$transaction, |
| 708 |
$this->form->id, |
| 709 |
$charge, |
| 710 |
$type |
| 711 |
], |
| 712 |
FLUENTFORM_FRAMEWORK_UPGRADE, |
| 713 |
'fluentform/payment_stripe_failed', |
| 714 |
'Use fluentform/payment_stripe_failed instead of fluentform_payment_stripe_failed.' |
| 715 |
); |
| 716 |
|
| 717 |
do_action('fluentform/payment_stripe_failed', $submission, $transaction, $this->form->id, $charge, $type); |
| 718 |
|
| 719 |
do_action_deprecated( |
| 720 |
'fluentform_payment_failed', |
| 721 |
[ |
| 722 |
$submission, |
| 723 |
$transaction, |
| 724 |
$this->form->id, |
| 725 |
$charge, |
| 726 |
$type |
| 727 |
], |
| 728 |
FLUENTFORM_FRAMEWORK_UPGRADE, |
| 729 |
'fluentform/payment_failed', |
| 730 |
'Use fluentform/payment_failed instead of fluentform_payment_failed.' |
| 731 |
); |
| 732 |
do_action('fluentform/payment_failed', $submission, $transaction, $this->form->id, $charge, $type); |
| 733 |
|
| 734 |
if ($transaction) { |
| 735 |
$this->changeTransactionStatus($transaction->id, 'failed'); |
| 736 |
} |
| 737 |
|
| 738 |
$this->changeSubmissionPaymentStatus('failed'); |
| 739 |
|
| 740 |
if ($message) { |
| 741 |
$logData = [ |
| 742 |
'parent_source_id' => $submission->form_id, |
| 743 |
'source_type' => 'submission_item', |
| 744 |
'source_id' => $submission->id, |
| 745 |
'component' => 'Payment', |
| 746 |
'status' => 'error', |
| 747 |
'title' => __('Stripe Payment Error', 'fluentform'), |
| 748 |
'description' => $message |
| 749 |
]; |
| 750 |
|
| 751 |
do_action('fluentform/log_data', $logData); |
| 752 |
} |
| 753 |
|
| 754 |
wp_send_json([ |
| 755 |
'errors' => __('Stripe Error: ', 'fluentform') . $message, |
| 756 |
'append_data' => [ |
| 757 |
'__entry_intermediate_hash' => Helper::getSubmissionMeta($submission->id, '__entry_intermediate_hash') |
| 758 |
] |
| 759 |
], 423); |
| 760 |
} |
| 761 |
|
| 762 |
public function recordSubscriptionCharge($subscription, $transactionData) |
| 763 |
{ |
| 764 |
$this->setSubmissionId($subscription->submission_id); |
| 765 |
return $this->maybeInsertSubscriptionCharge($transactionData); |
| 766 |
} |
| 767 |
} |
| 768 |
|