Completed
Push — 2.0 ( 7f87f2...afdd14 )
by Roman
16:48
created

OrderProcessor   F

Complexity

Total Complexity 54

Size/Duplication

Total Lines 367
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 20

Test Coverage

Coverage 62.7%

Importance

Changes 12
Bugs 6 Features 2
Metric Value
wmc 54
c 12
b 6
f 2
lcom 1
cbo 20
dl 0
loc 367
ccs 116
cts 185
cp 0.627
rs 2.6679

13 Methods

Rating   Name   Duplication   Size   Complexity  
A create() 0 4 1
A __construct() 0 5 1
A getReturnUrl() 0 4 1
C makePayment() 0 53 9
B getGatewayData() 0 36 2
A createPayment() 0 22 3
D completePayment() 0 37 10
A canPlace() 0 19 4
F placeOrder() 0 81 19
A getOrder() 0 4 1
A getError() 0 4 1
A error() 0 4 1
A config() 0 4 1

How to fix   Complexity   

Complex Class

Complex classes like OrderProcessor often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use OrderProcessor, and based on these observations, apply Extract Interface, too.

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 54 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 6
    public static function create(Order $order)
37
    {
38 6
        return Injector::inst()->create('OrderProcessor', $order);
39
    }
40
41
    /**
42
     * Assign the order to a local variable
43
     *
44
     * @param Order $order
45
     */
46 8
    public function __construct(Order $order)
47
    {
48 8
        $this->order = $order;
49 8
        $this->notifier = OrderEmailNotifier::create($order);
50 8
    }
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)
0 ignored issues
show
Complexity introduced by
This operation has 360 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...
76 2
    {
77
        //create payment
78 1
        $payment = $this->createPayment($gateway);
79 1
        if (!$payment) {
80
            //errors have been stored.
81
            return null;
82 1
        }
83
84
        // Create a payment service, by using the Service Factory. This will automatically choose an
85
        // AuthorizeService or PurchaseService, depending on Gateway configuration.
86
        // Set the user-facing success URL for redirects
87
        /** @var ServiceFactory $factory */
88 1
        $factory = ServiceFactory::create();
89 1
        $service = $factory->getService($payment, ServiceFactory::INTENT_PAYMENT);
90
91 1
        $service->setReturnUrl($successUrl ? $successUrl : $this->getReturnUrl());
92
93
        // Explicitly set the cancel URL
94 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...
95
            $service->setCancelUrl($cancelUrl);
96
        }
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
115 1
            return $serviceResponse;
116
        }
117
118
        // The order should be placed if the response isn't a redirect and if the payment isn't captured yet
119
        if (!$serviceResponse->isRedirect() && $serviceResponse->getPayment()->Status != 'Captured') {
120
            $this->placeOrder();
121
        }
122
123
        // For an OFFSITE payment, serviceResponse will now contain a redirect
124
        // For an ONSITE payment, ShopPayment::onCaptured will have been called, which will have called completePayment
125
126
        return $serviceResponse;
127
    }
128
129
    /**
130
     * Map shop data to omnipay fields
131
     *
132
     * @param array $customData Usually user submitted data.
133
     *
134
     * @return array
135
     */
136 1
    protected function getGatewayData($customData)
137
    {
138 1
        $shipping = $this->order->getShippingAddress();
139 1
        $billing = $this->order->getBillingAddress();
140
141 1
        $numPayments = Payment::get()
142 1
            ->filter(array('OrderID' => $this->order->ID))
143 1
            ->count() - 1;
144
145 1
        $transactionId = $this->order->Reference . ($numPayments > 0 ? "-$numPayments" : '');
146
147 1
        return array_merge(
148 1
            $customData,
149
            array(
150 1
                'transactionId'    => $transactionId,
151 1
                'firstName'        => $this->order->FirstName,
152 1
                'lastName'         => $this->order->Surname,
153 1
                'email'            => $this->order->Email,
154 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...
155 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...
156 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...
157 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...
158 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...
159 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...
160 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...
161 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...
162 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...
163 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...
164 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...
165 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...
166 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...
167 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...
168 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...
169
            )
170 1
        );
171
    }
172
173
    /**
174
     * Create a new payment for an order
175
     */
176 2
    public function createPayment($gateway)
177
    {
178 2
        if (!GatewayInfo::isSupported($gateway)) {
179
            $this->error(
180
                _t(
181
                    "PaymentProcessor.InvalidGateway",
182
                    "`{gateway}` isn't a valid payment gateway.",
183
                    'gateway is the name of the payment gateway',
184
                    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...
185
                )
186
            );
187
            return false;
188
        }
189 2
        if (!$this->order->canPay(Member::currentUser())) {
190
            $this->error(_t("PaymentProcessor.CantPay", "Order can't be paid for."));
191
            return false;
192
        }
193 2
        $payment = Payment::create()
194 2
            ->init($gateway, $this->order->TotalOutstanding(true), ShopConfig::get_base_currency());
195 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...
196 2
        return $payment;
197
    }
198
199
    /**
200
     * Complete payment processing
201
     *    - send receipt
202
     *    - update order status accordingling
203
     *    - fire event hooks
204
     */
205
    public function completePayment()
206
    {
207
        if (!$this->order->Paid) {
208
            // recalculate order to be sure we have the correct total
209
            $this->order->calculate();
210
211
            $this->order->extend('onPayment'); //a payment has been made
212
            //place the order, if not already placed
213
            if ($this->canPlace($this->order)) {
214
                $this->placeOrder();
215
            } else {
216
                if ($this->order->Locale) {
217
                    ShopTools::install_locale($this->order->Locale);
218
                }
219
            }
220
221
            if (
222
                // Standard order. Only change to 'Paid' once all payments are captured
223
                ($this->order->GrandTotal() > 0 && $this->order->TotalOutstanding(false) <= 0)
224
                // Zero-dollar order (e.g. paid with loyalty points)
225
                || ($this->order->GrandTotal() == 0 && Order::config()->allow_zero_order_total)
226
            ) {
227
                //set order as paid
228
                $this->order->Status = 'Paid';
229
                $this->order->Paid = SS_Datetime::now()->Rfc2822();
230
                $this->order->write();
231
                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...
232
                    $item->onPayment();
233
                }
234
                //all payment is settled
235
                $this->order->extend('onPaid');
236
            }
237
            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...
238
                $this->notifier->sendReceipt();
239
            }
240
        }
241
    }
242
243
    /**
244
     * Determine if an order can be placed.
245
     *
246
     * @param boolean $order
247
     */
248 4
    public function canPlace(Order $order)
249
    {
250 4
        if (!$order) {
251
            $this->error(_t("OrderProcessor.NoOrder", "Order does not exist."));
252
            return false;
253
        }
254
        //order status is applicable
255 4
        if (!$order->IsCart()) {
256 1
            $this->error(_t("OrderProcessor.NotCart", "Order is not a cart."));
257 1
            return false;
258
        }
259
        //order has products
260 3
        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...
261
            $this->error(_t("OrderProcessor.NoItems", "Order has no items."));
262
            return false;
263
        }
264
265 3
        return true;
266
    }
267
268
    /**
269
     * Takes an order from being a cart to awaiting payment.
270
     *
271
     * @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...
272
     *
273
     * @return boolean - success/failure
274
     */
275 4
    public function placeOrder()
0 ignored issues
show
Complexity introduced by
This operation has 38880 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...
276
    {
277 4
        if (!$this->order) {
278
            $this->error(_t("OrderProcessor.NoOrderStarted", "A new order has not yet been started."));
279
            return false;
280
        }
281 4
        if (!$this->canPlace($this->order)) { //final cart validation
282 1
            return false;
283
        }
284
285 3
        if ($this->order->Locale) {
286 3
            ShopTools::install_locale($this->order->Locale);
287 3
        }
288
289
        // recalculate order to be sure we have the correct total
290 3
        $this->order->calculate();
291
292
        //remove from session
293 3
        $cart = ShoppingCart::curr();
294 3
        if ($cart && $cart->ID == $this->order->ID) {
295 3
            ShoppingCart::singleton()->clear();
296 3
        }
297
298
        //update status
299 3
        if ($this->order->TotalOutstanding(false)) {
300 3
            $this->order->Status = 'Unpaid';
301 3
        } else {
302
            $this->order->Status = 'Paid';
303
        }
304 3
        if (!$this->order->Placed) {
305 3
            $this->order->Placed = SS_Datetime::now()->Rfc2822(); //record placed order datetime
306 3
            if ($request = Controller::curr()->getRequest()) {
307 3
                $this->order->IPAddress = $request->getIP(); //record client IP
308 3
            }
309 3
        }
310
        //re-write all attributes and modifiers to make sure they are up-to-date before they can't be changed again
311 3
        $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...
312 3
        if ($items->exists()) {
313 3
            foreach ($items as $item) {
314 3
                $item->onPlacement();
315 3
                $item->write();
316 3
            }
317 3
        }
318 3
        $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...
319 3
        if ($modifiers->exists()) {
320
            foreach ($modifiers as $modifier) {
321
                $modifier->write();
322
            }
323
        }
324
        //add member to order & customers group
325 3
        if ($member = Member::currentUser()) {
326 3
            if (!$this->order->MemberID) {
327
                $this->order->MemberID = $member->ID;
328
            }
329 2
            $cgroup = ShopConfig::current()->CustomerGroup();
330 2
            if ($cgroup->exists()) {
331
                $member->Groups()->add($cgroup);
332
            }
333 2
        }
334
        //allow decorators to do stuff when order is saved.
335 3
        $this->order->extend('onPlaceOrder');
336 3
        $this->order->write();
337
338
        //send confirmation if configured and receipt hasn't been sent
339
        if (
340 3
            self::config()->send_confirmation
341 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...
342 3
        ) {
343
            $this->notifier->sendConfirmation();
344
        }
345
346
        //notify admin, if configured
347 3
        if (self::config()->send_admin_notification) {
348
            $this->notifier->sendAdminNotification();
349
        }
350
351
        // Save order reference to session
352 3
        OrderManipulation::add_session_order($this->order);
353
354 3
        return true; //report success
355
    }
356
357
    /**
358
     * @return Order
359
     */
360
    public function getOrder()
361
    {
362
        return $this->order;
363
    }
364
365 2
    public function getError()
366
    {
367 2
        return $this->error;
368
    }
369
370 2
    protected function error($message)
371
    {
372 2
        $this->error = $message;
373 2
    }
374
375 3
    public static function config()
376
    {
377 3
        return new Config_ForClass("OrderProcessor");
378
    }
379
}
380