Completed
Pull Request — 2.0 (#506)
by Roman
18:39
created

OrderProcessor::placeOrder()   F

Complexity

Conditions 23
Paths > 20000

Size

Total Lines 110
Code Lines 60

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 61
CRAP Score 27.069

Importance

Changes 5
Bugs 0 Features 1
Metric Value
c 5
b 0
f 1
dl 0
loc 110
ccs 61
cts 76
cp 0.8026
rs 2
cc 23
eloc 60
nc 364970
nop 0
crap 27.069

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
use SilverStripe\Omnipay\GatewayInfo;
4
use SilverStripe\Omnipay\Service\ServiceFactory;
5
use SilverStripe\Omnipay\Service\ServiceResponse;
6
7
/**
8
 * Handles tasks to be performed on orders, particularly placing and processing/fulfilment.
9
 * Placing, Emailing Reciepts, Status Updates, Printing, Payments - things you do with a completed order.
10
 *
11
 * @package shop
12
 */
13
class OrderProcessor
0 ignored issues
show
Complexity introduced by
This class has a complexity of 56 which exceeds the configured maximum of 50.

The class complexity is the sum of the complexity of all methods. A very high value is usually an indication that your class does not follow the single reponsibility principle and does more than one job.

Some resources for further reading:

You can also find more detailed suggestions for refactoring in the “Code” section of your repository.

Loading history...
14
{
15
    /**
16
     * @var Order
17
     */
18
    protected $order;
19
20
    /**
21
     * @var OrderEmailNotifier
22
     */
23
    protected $notifier;
24
25
    /**
26
     * @var string
27
     */
28
    protected $error;
29
30
    /**
31
     * Static way to create the order processor.
32
     * Makes creating a processor easier.
33
     *
34
     * @param Order $order
35
     */
36 7
    public static function create(Order $order)
37
    {
38 7
        return Injector::inst()->create('OrderProcessor', $order);
39
    }
40
41
    /**
42
     * Assign the order to a local variable
43
     *
44
     * @param Order $order
45
     */
46 9
    public function __construct(Order $order)
47
    {
48 9
        $this->order = $order;
49 9
        $this->notifier = OrderEmailNotifier::create($order);
50 9
    }
51
52
    /**
53
     * URL to display success message to the user.
54
     * Happens after any potential offsite gateway redirects.
55
     *
56
     * @return String Relative URL
57
     */
58 1
    public function getReturnUrl()
59
    {
60 1
        return $this->order->Link();
61
    }
62
63
    /**
64
     * Create a payment model, and provide link to redirect to external gateway,
65
     * or redirect to order link.
66
     *
67
     * @param string $gateway the gateway to use
68
     * @param array $gatewaydata the data that should be passed to the gateway
69
     * @param string $successUrl (optional) return URL for successful payments.
70
     *  If left blank, the default return URL will be used @see getReturnUrl
71
     * @param string $cancelUrl (optional) return URL for cancelled/failed payments
72
     *
73
     * @return ServiceResponse|null
74
     */
75 2
    public function makePayment($gateway, $gatewaydata = array(), $successUrl = null, $cancelUrl = null)
76 2
    {
77
        //create payment
78 1
        $payment = $this->createPayment($gateway);
79 1
        if (!$payment) {
80
            //errors have been stored.
81
            return null;
82
        }
83
84 1
        $payment->setSuccessUrl($successUrl ? $successUrl : $this->getReturnUrl());
85
86
        // Explicitly set the cancel URL
87 1
        if ($cancelUrl) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $cancelUrl of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
88
            $payment->setFailureUrl($cancelUrl);
89
        }
90
91
        // Create a payment service, by using the Service Factory. This will automatically choose an
92
        // AuthorizeService or PurchaseService, depending on Gateway configuration.
93
        // Set the user-facing success URL for redirects
94
        /** @var ServiceFactory $factory */
95 1
        $factory = ServiceFactory::create();
96 1
        $service = $factory->getService($payment, ServiceFactory::INTENT_PAYMENT);
97
98
        // Initiate payment, get the result back
99
        try {
100 1
            $serviceResponse = $service->initiate($this->getGatewayData($gatewaydata));
101 1
        } catch (SilverStripe\Omnipay\Exception\Exception $ex) {
102
            // error out when an exception occurs
103
            $this->error($ex->getMessage());
104
            return null;
105
        }
106
107
        // Check if the service response itself contains an error
108 1
        if ($serviceResponse->isError()) {
109 1
            if ($opResponse = $serviceResponse->getOmnipayResponse()) {
110
                $this->error($opResponse->getMessage());
111
            } else {
112 1
                $this->error('An unspecified payment error occurred. Please check the payment messages.');
113
            }
114 1
        }
115
116
        // For an OFFSITE payment, serviceResponse will now contain a redirect
117
        // For an ONSITE payment, ShopPayment::onCaptured will have been called, which will have called completePayment
118
119 1
        return $serviceResponse;
120
    }
121
122
    /**
123
     * Map shop data to omnipay fields
124
     *
125
     * @param array $customData Usually user submitted data.
126
     *
127
     * @return array
128
     */
129 1
    protected function getGatewayData($customData)
130
    {
131 1
        $shipping = $this->order->getShippingAddress();
132 1
        $billing = $this->order->getBillingAddress();
133
134 1
        $numPayments = Payment::get()
135 1
            ->filter(array('OrderID' => $this->order->ID))
136 1
            ->count() - 1;
137
138 1
        $transactionId = $this->order->Reference . ($numPayments > 0 ? "-$numPayments" : '');
139
140 1
        return array_merge(
141 1
            $customData,
142
            array(
143 1
                'transactionId'    => $transactionId,
144 1
                'firstName'        => $this->order->FirstName,
145 1
                'lastName'         => $this->order->Surname,
146 1
                'email'            => $this->order->Email,
147 1
                'company'          => $this->order->Company,
0 ignored issues
show
Documentation introduced by
The property Company does not exist on object<Order>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
148 1
                'billingAddress1'  => $billing->Address,
0 ignored issues
show
Documentation introduced by
The property Address does not exist on object<Address>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
149 1
                'billingAddress2'  => $billing->AddressLine2,
0 ignored issues
show
Documentation introduced by
The property AddressLine2 does not exist on object<Address>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
150 1
                'billingCity'      => $billing->City,
0 ignored issues
show
Documentation introduced by
The property City does not exist on object<Address>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
151 1
                'billingPostcode'  => $billing->PostalCode,
0 ignored issues
show
Documentation introduced by
The property PostalCode does not exist on object<Address>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
152 1
                'billingState'     => $billing->State,
0 ignored issues
show
Documentation introduced by
The property State does not exist on object<Address>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
153 1
                'billingCountry'   => $billing->Country,
0 ignored issues
show
Documentation introduced by
The property Country does not exist on object<Address>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
154 1
                'billingPhone'     => $billing->Phone,
0 ignored issues
show
Documentation introduced by
The property Phone does not exist on object<Address>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
155 1
                'shippingAddress1' => $shipping->Address,
0 ignored issues
show
Documentation introduced by
The property Address does not exist on object<Address>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
156 1
                'shippingAddress2' => $shipping->AddressLine2,
0 ignored issues
show
Documentation introduced by
The property AddressLine2 does not exist on object<Address>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
157 1
                'shippingCity'     => $shipping->City,
0 ignored issues
show
Documentation introduced by
The property City does not exist on object<Address>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
158 1
                'shippingPostcode' => $shipping->PostalCode,
0 ignored issues
show
Documentation introduced by
The property PostalCode does not exist on object<Address>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
159 1
                'shippingState'    => $shipping->State,
0 ignored issues
show
Documentation introduced by
The property State does not exist on object<Address>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
160 1
                'shippingCountry'  => $shipping->Country,
0 ignored issues
show
Documentation introduced by
The property Country does not exist on object<Address>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
161 1
                'shippingPhone'    => $shipping->Phone,
0 ignored issues
show
Documentation introduced by
The property Phone does not exist on object<Address>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
162
            )
163 1
        );
164
    }
165
166
    /**
167
     * Create a new payment for an order
168
     */
169 2
    public function createPayment($gateway)
170
    {
171 2
        if (!GatewayInfo::isSupported($gateway)) {
172
            $this->error(
173
                _t(
174
                    "PaymentProcessor.InvalidGateway",
175
                    "`{gateway}` isn't a valid payment gateway.",
176
                    'gateway is the name of the payment gateway',
177
                    array('gateway' => $gateway)
0 ignored issues
show
Documentation introduced by
array('gateway' => $gateway) is of type array<string,?,{"gateway":"?"}>, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
178
                )
179
            );
180
            return false;
181
        }
182 2
        if (!$this->order->canPay(Member::currentUser())) {
183
            $this->error(_t("PaymentProcessor.CantPay", "Order can't be paid for."));
184
            return false;
185
        }
186 2
        $payment = Payment::create()
187 2
            ->init($gateway, $this->order->TotalOutstanding(true), ShopConfig::get_base_currency());
188 2
        $this->order->Payments()->add($payment);
0 ignored issues
show
Documentation Bug introduced by
The method Payments does not exist on object<Order>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
189 2
        return $payment;
190
    }
191
192
    /**
193
     * Complete payment processing
194
     *    - send receipt
195
     *    - update order status accordingling
196
     *    - fire event hooks
197
     */
198
    public function completePayment()
199
    {
200
        if (!$this->order->Paid) {
201
            // recalculate order to be sure we have the correct total
202
            $this->order->calculate();
203
204
            $this->order->extend('onPayment'); //a payment has been made
205
            //place the order, if not already placed
206
            if ($this->canPlace($this->order)) {
207
                $this->placeOrder();
208
            } else {
209
                if ($this->order->Locale) {
210
                    ShopTools::install_locale($this->order->Locale);
211
                }
212
            }
213
214
            if (
215
                // Standard order. Only change to 'Paid' once all payments are captured
216
                ($this->order->GrandTotal() > 0 && $this->order->TotalOutstanding(false) <= 0)
217
                // Zero-dollar order (e.g. paid with loyalty points)
218
                || ($this->order->GrandTotal() == 0 && Order::config()->allow_zero_order_total)
219
            ) {
220
                //set order as paid
221
                $this->order->Status = 'Paid';
222
                $this->order->Paid = SS_Datetime::now()->Rfc2822();
223
                $this->order->write();
224
                foreach ($this->order->Items() as $item) {
0 ignored issues
show
Documentation Bug introduced by
The method Items does not exist on object<Order>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
225
                    $item->onPayment();
226
                }
227
                //all payment is settled
228
                $this->order->extend('onPaid');
229
            }
230
            if (!$this->order->ReceiptSent) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->order->ReceiptSent of type string|null is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
231
                $this->notifier->sendReceipt();
232
            }
233
        }
234
    }
235
236
    /**
237
     * Determine if an order can be placed.
238
     *
239
     * @param boolean $order
240
     */
241 5
    public function canPlace(Order $order)
242
    {
243 5
        if (!$order) {
244
            $this->error(_t("OrderProcessor.NoOrder", "Order does not exist."));
245
            return false;
246
        }
247
        //order status is applicable
248 5
        if (!$order->IsCart()) {
249 1
            $this->error(_t("OrderProcessor.NotCart", "Order is not a cart."));
250 1
            return false;
251
        }
252
        //order has products
253 4
        if ($order->Items()->Count() <= 0) {
0 ignored issues
show
Documentation Bug introduced by
The method Items does not exist on object<Order>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
254
            $this->error(_t("OrderProcessor.NoItems", "Order has no items."));
255
            return false;
256
        }
257
258 4
        return true;
259
    }
260
261
    /**
262
     * Takes an order from being a cart to awaiting payment.
263
     *
264
     * @param Member $member - assign a member to the order
0 ignored issues
show
Bug introduced by
There is no parameter named $member. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
265
     *
266
     * @return boolean - success/failure
267
     */
268 5
    public function placeOrder()
0 ignored issues
show
Complexity introduced by
This operation has 165888 execution paths which exceeds the configured maximum of 200.

A high number of execution paths generally suggests many nested conditional statements and make the code less readible. This can usually be fixed by splitting the method into several smaller methods.

You can also find more information in the “Code” section of your repository.

Loading history...
269
    {
270 5
        if (!$this->order) {
271
            $this->error(_t("OrderProcessor.NoOrderStarted", "A new order has not yet been started."));
272
            return false;
273
        }
274 5
        if (!$this->canPlace($this->order)) { //final cart validation
275 1
            return false;
276
        }
277
278 4
        if ($this->order->Locale) {
279 4
            ShopTools::install_locale($this->order->Locale);
280 4
        }
281
282
        // recalculate order to be sure we have the correct total
283 4
        $this->order->calculate();
284
285 4
        if (DB::get_conn()->supportsTransactions()) {
286 4
            DB::get_conn()->transactionStart();
287 4
        }
288
289
        //update status
290 4
        if ($this->order->TotalOutstanding(false)) {
291 4
            $this->order->Status = 'Unpaid';
292 4
        } else {
293
            $this->order->Status = 'Paid';
294
        }
295 4
        if (!$this->order->Placed) {
296 4
            $this->order->Placed = SS_Datetime::now()->Rfc2822(); //record placed order datetime
297 4
            if ($request = Controller::curr()->getRequest()) {
298 4
                $this->order->IPAddress = $request->getIP(); //record client IP
299 4
            }
300 4
        }
301
302
        // Add an error handler that throws an exception upon error, so that we can catch errors as exceptions
303
        // in the following block.
304 4
        set_error_handler(function ($severity, $message, $file, $line) {
305 1
            throw new ErrorException($message, 0, $severity, $file, $line);
306 4
        }, E_ALL & ~(E_STRICT | E_NOTICE));
307
308
        try {
309
            //re-write all attributes and modifiers to make sure they are up-to-date before they can't be changed again
310 4
            $items = $this->order->Items();
0 ignored issues
show
Documentation Bug introduced by
The method Items does not exist on object<Order>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
311 4
            if ($items->exists()) {
312 4
                foreach ($items as $item) {
313 4
                    $item->onPlacement();
314 4
                    $item->write();
315 4
                }
316 4
            }
317 4
            $modifiers = $this->order->Modifiers();
0 ignored issues
show
Documentation Bug introduced by
The method Modifiers does not exist on object<Order>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
318 4
            if ($modifiers->exists()) {
319
                foreach ($modifiers as $modifier) {
320
                    $modifier->write();
321
                }
322
            }
323
            //add member to order & customers group
324 4
            if ($member = Member::currentUser()) {
325 3
                if (!$this->order->MemberID) {
326
                    $this->order->MemberID = $member->ID;
327 4
                }
328 3
                $cgroup = ShopConfig::current()->CustomerGroup();
329 3
                if ($cgroup->exists()) {
330
                    $member->Groups()->add($cgroup);
331
                }
332 3
            }
333
            //allow decorators to do stuff when order is saved.
334 4
            $this->order->extend('onPlaceOrder');
335 4
            $this->order->write();
336 4
        } catch (Exception $ex) {
337
            // Rollback the transaction if an error occurred
338 1
            if (DB::get_conn()->supportsTransactions()) {
339 1
                DB::get_conn()->transactionRollback();
340 1
            }
341 1
            $this->error($ex->getMessage());
342 1
            return false;
343 3
        } finally {
344
            // restore the error handler, no matter what
345 4
            restore_error_handler();
346
        }
347
348
        // Everything went through fine, complete the transaction
349 3
        if (DB::get_conn()->supportsTransactions()) {
350 3
            DB::get_conn()->transactionEnd();
351 3
        }
352
353
        //remove from session
354 3
        $cart = ShoppingCart::curr();
355 3
        if ($cart && $cart->ID == $this->order->ID) {
356
            // clear the cart, but don't write the order in the process (order is finalized and should NOT be overwritten)
357 3
            ShoppingCart::singleton()->clear(false);
358 3
        }
359
360
        //send confirmation if configured and receipt hasn't been sent
361
        if (
362 3
            self::config()->send_confirmation
363 3
            && !$this->order->ReceiptSent
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->order->ReceiptSent of type string|null is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
364 3
        ) {
365
            $this->notifier->sendConfirmation();
366
        }
367
368
        //notify admin, if configured
369 3
        if (self::config()->send_admin_notification) {
370
            $this->notifier->sendAdminNotification();
371
        }
372
373
        // Save order reference to session
374 3
        OrderManipulation::add_session_order($this->order);
375
376 3
        return true; //report success
377
    }
378
379
    /**
380
     * @return Order
381
     */
382
    public function getOrder()
383
    {
384
        return $this->order;
385
    }
386
387 2
    public function getError()
388
    {
389 2
        return $this->error;
390
    }
391
392 3
    protected function error($message)
393
    {
394 3
        $this->error = $message;
395 3
    }
396
397 3
    public static function config()
398
    {
399 3
        return new Config_ForClass("OrderProcessor");
400
    }
401
}
402