| @@ -6,8 +6,9 @@ | ||
| 6 | 6 | use FluentCart\Api\StoreSettings; |
| 7 | 7 | use FluentCart\App\App; |
| 8 | 8 | use FluentCart\App\Helpers\AddressHelper; |
| 9 | 9 | use FluentCart\App\Helpers\Helper; |
| 10 | +use FluentCart\App\Helpers\Status; | |
| 10 | 11 | use FluentCart\App\Models\Model; |
| 11 | 12 | use FluentCart\App\Models\Order; |
| 12 | 13 | use FluentCart\App\Models\OrderItem; |
| 13 | 14 | use FluentCart\App\Models\OrderMeta; |
| @@ -13,8 +14,11 @@ | ||
| 13 | 14 | use FluentCart\App\Models\OrderMeta; |
| 14 | 15 | use FluentCart\App\Models\OrderTransaction; |
| 15 | 16 | use FluentCart\App\Models\ProductVariation; |
| 16 | 17 | use FluentCart\App\Models\Subscription; |
| 18 | +use FluentCart\App\Modules\Subscriptions\Services\SystemChargeService; | |
| 19 | +use FluentCart\App\Services\Payments\PaymentHelper; | |
| 20 | +use FluentCart\App\Services\Renderer\CheckoutFieldsSchema; | |
| 17 | 21 | use FluentCart\Framework\Support\Arr; |
| 18 | 22 | |
| 19 | 23 | class OrderService |
| 20 | 24 | { |
| @@ -25,8 +29,14 @@ | ||
| 25 | 29 | * @return array |
| 26 | 30 | */ |
| 27 | 31 | public static function groupSanitizedData($order_data = []): array |
| 28 | 32 | { |
| 33 | + // fct_billing_tax_id is the checkout field name for the VAT number; normalise it to | |
| 34 | + // billing_vat_number so extractAddressData maps it into billing_address.vat_number | |
| 35 | + if (isset($order_data['fct_billing_tax_id'])) { | |
| 36 | + $order_data['billing_vat_number'] = $order_data['fct_billing_tax_id']; | |
| 37 | + unset($order_data['fct_billing_tax_id']); | |
| 38 | + } | |
| 29 | 39 | |
| 30 | 40 | $billingAddress = static::extractAddressData($order_data, 'billing_'); |
| 31 | 41 | $shippingAddress = static::extractAddressData($order_data, 'shipping_'); |
| 32 | 42 | $others = static::extractOtherData($order_data); |
| @@ -72,13 +82,24 @@ | ||
| 72 | 82 | private static function finalizeAddress($address, $type): array |
| 73 | 83 | { |
| 74 | 84 | $fullName = Arr::get($address, 'full_name', ''); |
| 75 | 85 | $name = Arr::get($address, 'name', ''); |
| 86 | + $explicitFirstName = trim((string) Arr::get($address, 'first_name', '')); | |
| 87 | + $explicitLastName = trim((string) Arr::get($address, 'last_name', '')); | |
| 76 | 88 | $address['full_name'] = $fullName ?? $name; |
| 77 | 89 | $address['first_name'] = AddressHelper::guessFirstNameAndLastName($address['full_name']); |
| 78 | 90 | $address['type'] = $type; |
| 79 | 91 | $name = $fullName ?? $name; |
| 80 | - $address = array_merge($address, AddressHelper::guessFirstNameAndLastName($name)); | |
| 92 | + | |
| 93 | + // In First/Last mode the customer typed both names, so keep them as | |
| 94 | + // typed. Guessing from the joined name would turn "Alex" + "de Silva" | |
| 95 | + // into "Alex de" + "Silva". Full-name-only addresses are still guessed. | |
| 96 | + if (!CheckoutFieldsSchema::isFullNameRequired() && $explicitFirstName !== '') { | |
| 97 | + $address['first_name'] = $explicitFirstName; | |
| 98 | + $address['last_name'] = $explicitLastName; | |
| 99 | + } else { | |
| 100 | + $address = array_merge($address, AddressHelper::guessFirstNameAndLastName($name)); | |
| 101 | + } | |
| 81 | 102 | $address['name'] = Arr::get($address, 'first_name', '') . ' ' . Arr::get($address, 'last_name', ''); |
| 82 | 103 | return $address; |
| 83 | 104 | } |
| 84 | 105 | |
| @@ -379,12 +400,12 @@ | ||
| 379 | 400 | if ($paymentType == 'subscription') { |
| 380 | 401 | if ((int)Arr::get($orderItem, 'other_info.trial_days', 0) > 0) { |
| 381 | 402 | return (int)Arr::get($orderItem, 'other_info.signup_fee', 0); |
| 382 | 403 | } |
| 383 | - return intval(($orderItem['unit_price'] * $orderItem['quantity'])) + (int)Arr::get($orderItem, 'other_info.signup_fee', 0); | |
| 404 | + return (int) Arr::get($orderItem, 'subtotal', intval($orderItem['unit_price'] * $orderItem['quantity'])) + (int)Arr::get($orderItem, 'other_info.signup_fee', 0); | |
| 384 | 405 | } |
| 385 | 406 | |
| 386 | - return intval(($orderItem['unit_price'] * $orderItem['quantity'])); | |
| 407 | + return (int) Arr::get($orderItem, 'subtotal', intval($orderItem['unit_price'] * $orderItem['quantity'])); | |
| 387 | 408 | } |
| 388 | 409 | |
| 389 | 410 | /** |
| 390 | 411 | * Get the total amount of items. |
| @@ -612,12 +633,33 @@ | ||
| 612 | 633 | 'reactivate_url' => $subscription->getReactivateUrl(), |
| 613 | 634 | 'can_upgrade' => $subscription->canUpgrade(), |
| 614 | 635 | 'can_switch_payment_method' => $subscription->canSwitchPaymentMethod(), |
| 615 | 636 | 'can_update_payment_method' => $subscription->canUpdatePaymentMethod(), |
| 616 | - 'item_name' => $subscription->item_name | |
| 637 | + 'collection_method' => $subscription->collection_method, | |
| 638 | + 'is_auto_charged' => $subscription->isSystem(), | |
| 639 | + 'item_name' => $subscription->display_item_name | |
| 617 | 640 | ]; |
| 618 | 641 | } |
| 619 | 642 | |
| 643 | + /** | |
| 644 | + * A system renewal order still inside its auto-charge retry window has no | |
| 645 | + * Pay Now — the charge is coming automatically. Other renewals unaffected. | |
| 646 | + */ | |
| 647 | + protected static function renewalPayNowAllowed(Order $order): bool | |
| 648 | + { | |
| 649 | + if (!$order->parent_id) { | |
| 650 | + return true; | |
| 651 | + } | |
| 652 | + | |
| 653 | + $subscription = Subscription::query()->where('parent_order_id', $order->parent_id)->first(); | |
| 654 | + | |
| 655 | + if (!$subscription || !$subscription->isSystem()) { | |
| 656 | + return true; | |
| 657 | + } | |
| 658 | + | |
| 659 | + return SystemChargeService::isExhausted($subscription, $order); | |
| 660 | + } | |
| 661 | + | |
| 620 | 662 | public static function transformTransaction(OrderTransaction $transaction) |
| 621 | 663 | { |
| 622 | 664 | $data = [ |
| 623 | 665 | 'uuid' => $transaction->uuid, |
| @@ -638,13 +680,27 @@ | ||
| 638 | 680 | if ($transaction->order && self::canGenerateReceiptPdf()) { |
| 639 | 681 | $data['receipt_view_url'] = $transaction->order->getReceiptViewUrl(); |
| 640 | 682 | } |
| 641 | 683 | |
| 684 | + if ($transaction->order | |
| 685 | + && $transaction->order->type === Status::ORDER_TYPE_RENEWAL | |
| 686 | + && $transaction->status === Status::TRANSACTION_PENDING | |
| 687 | + && self::renewalPayNowAllowed($transaction->order) | |
| 688 | + ) { | |
| 689 | + $data['custom_checkout_url'] = add_query_arg([ | |
| 690 | + 'fluent-cart' => 'custom_checkout', | |
| 691 | + 'order_hash' => $transaction->order->uuid, | |
| 692 | + ], home_url('/')); | |
| 693 | + } | |
| 694 | + | |
| 642 | 695 | return $data; |
| 643 | 696 | } |
| 644 | 697 | |
| 645 | 698 | public static function canGenerateReceiptPdf(): bool |
| 646 | 699 | { |
| 647 | - return App::isProActive() && defined('FLUENT_PDF'); | |
| 700 | + return (bool) apply_filters( | |
| 701 | + 'fluent_cart/pdf/can_generate_receipt', | |
| 702 | + App::isProActive() && defined('FLUENT_PDF') | |
| 703 | + ); | |
| 648 | 704 | } |
| 649 | 705 | |
| 650 | 706 | } |