Completed
Push — 2.0 ( 6c824d...3cf5ce )
by Roman
18:14 queued 10s
created

OrderProcessor::completePayment()   C

Complexity

Conditions 8
Paths 7

Size

Total Lines 28
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 72

Importance

Changes 5
Bugs 0 Features 1
Metric Value
c 5
b 0
f 1
dl 0
loc 28
ccs 0
cts 18
cp 0
rs 5.3846
cc 8
eloc 14
nc 7
nop 0
crap 72
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 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 5
    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 5
        $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->IsPaid()) {
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->write();
223
            }
224
        }
225
    }
226
227
    /**
228
     * Determine if an order can be placed.
229
     *
230
     * @param boolean $order
231
     */
232 5
    public function canPlace(Order $order)
233
    {
234 5
        if (!$order) {
235
            $this->error(_t("OrderProcessor.NoOrder", "Order does not exist."));
236
            return false;
237
        }
238
        //order status is applicable
239 5
        if (!$order->IsCart()) {
240 1
            $this->error(_t("OrderProcessor.NotCart", "Order is not a cart."));
241 1
            return false;
242
        }
243
        //order has products
244 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...
245
            $this->error(_t("OrderProcessor.NoItems", "Order has no items."));
246
            return false;
247
        }
248
249 4
        return true;
250
    }
251
252
    /**
253
     * Takes an order from being a cart to awaiting payment.
254
     *
255
     * @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...
256
     *
257
     * @return boolean - success/failure
258
     */
259 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...
260
    {
261 5
        if (!$this->order) {
262
            $this->error(_t("OrderProcessor.NoOrderStarted", "A new order has not yet been started."));
263
            return false;
264
        }
265 5
        if (!$this->canPlace($this->order)) { //final cart validation
266 1
            return false;
267
        }
268
269 4
        if ($this->order->Locale) {
270 4
            ShopTools::install_locale($this->order->Locale);
271 4
        }
272
273
        // recalculate order to be sure we have the correct total
274 4
        $this->order->calculate();
275
276 4
        if (ShopTools::DBConn()->supportsTransactions()) {
277 4
            ShopTools::DBConn()->transactionStart();
278 4
        }
279
280
        //update status
281 4
        if ($this->order->TotalOutstanding(false)) {
282 4
            $this->order->Status = 'Unpaid';
283 4
        } else {
284
            $this->order->Status = 'Paid';
285
        }
286 4
        if (!$this->order->Placed) {
287 4
            $this->order->Placed = SS_Datetime::now()->Rfc2822(); //record placed order datetime
288 4
            if ($request = Controller::curr()->getRequest()) {
289 4
                $this->order->IPAddress = $request->getIP(); //record client IP
290 4
            }
291 4
        }
292
293
        // Add an error handler that throws an exception upon error, so that we can catch errors as exceptions
294
        // in the following block.
295 4
        set_error_handler(function ($severity, $message, $file, $line) {
296 1
            throw new ErrorException($message, 0, $severity, $file, $line);
297 4
        }, E_ALL & ~(E_STRICT | E_NOTICE));
298
299
        try {
300
            //re-write all attributes and modifiers to make sure they are up-to-date before they can't be changed again
301 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...
302 4
            if ($items->exists()) {
303 4
                foreach ($items as $item) {
304 4
                    $item->onPlacement();
305 4
                    $item->write();
306 4
                }
307 4
            }
308 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...
309 4
            if ($modifiers->exists()) {
310
                foreach ($modifiers as $modifier) {
311
                    $modifier->write();
312
                }
313
            }
314
            //add member to order & customers group
315 4
            if ($member = Member::currentUser()) {
316 3
                if (!$this->order->MemberID) {
317
                    $this->order->MemberID = $member->ID;
318 3
                }
319 3
                $cgroup = ShopConfig::current()->CustomerGroup();
320 3
                if ($cgroup->exists()) {
321
                    $member->Groups()->add($cgroup);
322
                }
323 3
            }
324
            //allow decorators to do stuff when order is saved.
325 4
            $this->order->extend('onPlaceOrder');
326 4
            $this->order->write();
327 4
        } catch (Exception $ex) {
328
            // Rollback the transaction if an error occurred
329 1
            if (ShopTools::DBConn()->supportsTransactions()) {
330 1
                ShopTools::DBConn()->transactionRollback();
331 1
            }
332 1
            $this->error($ex->getMessage());
333 1
            return false;
334 3
        } finally {
335
            // restore the error handler, no matter what
336 4
            restore_error_handler();
337
        }
338
339
        // Everything went through fine, complete the transaction
340 3
        if (ShopTools::DBConn()->supportsTransactions()) {
341 3
            ShopTools::DBConn()->transactionEnd();
342 3
        }
343
344
        //remove from session
345 3
        $cart = ShoppingCart::curr();
346 3
        if ($cart && $cart->ID == $this->order->ID) {
347
            // clear the cart, but don't write the order in the process (order is finalized and should NOT be overwritten)
348 3
            ShoppingCart::singleton()->clear(false);
349 3
        }
350
351
        //send confirmation if configured and receipt hasn't been sent
352
        if (
353 3
            self::config()->send_confirmation
354 3
            && !$this->order->ReceiptSent
355 3
        ) {
356
            $this->notifier->sendConfirmation();
357
        }
358
359
        //notify admin, if configured
360 3
        if (self::config()->send_admin_notification) {
361
            $this->notifier->sendAdminNotification();
362
        }
363
364
        // Save order reference to session
365 3
        OrderManipulation::add_session_order($this->order);
366
367 4
        return true; //report success
368
    }
369
370
    /**
371
     * @return Order
372
     */
373
    public function getOrder()
374
    {
375
        return $this->order;
376
    }
377
378 6
    public function getError()
379
    {
380 2
        return $this->error;
381 6
    }
382
383 3
    protected function error($message)
384
    {
385 3
        $this->error = $message;
386 3
    }
387
388 3
    public static function config()
389
    {
390 3
        return new Config_ForClass("OrderProcessor");
391
    }
392
}
393