|
1
|
|
|
<?php |
|
2
|
|
|
|
|
3
|
|
|
/** |
|
4
|
|
|
* CheckoutPage is a CMS page-type that shows the order |
|
5
|
|
|
* details to the customer for their current shopping |
|
6
|
|
|
* cart on the site. |
|
7
|
|
|
* |
|
8
|
|
|
* @see CheckoutPage_Controller->Order() |
|
9
|
|
|
* |
|
10
|
|
|
* @package shop |
|
11
|
|
|
*/ |
|
12
|
|
|
class CheckoutPage extends Page |
|
13
|
|
|
{ |
|
14
|
|
|
private static $db = array( |
|
15
|
|
|
'PurchaseComplete' => 'HTMLText', |
|
16
|
|
|
); |
|
17
|
|
|
|
|
18
|
|
|
private static $icon = 'silvershop/images/icons/money'; |
|
19
|
|
|
|
|
20
|
|
|
/** |
|
21
|
|
|
* Returns the link to the checkout page on this site |
|
22
|
|
|
* |
|
23
|
|
|
* @param boolean $urlSegment If set to TRUE, only returns the URLSegment field |
|
24
|
|
|
* |
|
25
|
|
|
* @return string Link to checkout page |
|
26
|
|
|
*/ |
|
27
|
4 |
|
public static function find_link($urlSegment = false, $action = null, $id = null) |
|
28
|
|
|
{ |
|
29
|
4 |
|
$base = CheckoutPage_Controller::config()->url_segment; |
|
30
|
4 |
|
if ($page = self::get()->first()) { |
|
31
|
4 |
|
$base = $page->Link(); |
|
32
|
4 |
|
} |
|
33
|
4 |
|
return Controller::join_links($base, $action, $id); |
|
34
|
|
|
} |
|
35
|
|
|
|
|
36
|
|
|
public function getCMSFields() |
|
37
|
|
|
{ |
|
38
|
|
|
$this->beforeUpdateCMSFields(function(FieldList $fields) { |
|
39
|
|
|
$fields->addFieldsToTab( |
|
40
|
|
|
'Root.Main', |
|
41
|
|
|
array( |
|
42
|
|
|
HtmlEditorField::create( |
|
43
|
|
|
'PurchaseComplete', |
|
44
|
|
|
_t('CheckoutPage.db_PurchaseComplete', 'Purchase Complete'), |
|
45
|
|
|
4 |
|
46
|
|
|
) |
|
47
|
|
|
->setDescription( |
|
48
|
|
|
_t( |
|
49
|
|
|
'CheckoutPage.PurchaseCompleteDescription', |
|
50
|
|
|
"This message is included in reciept email, after the customer submits the checkout" |
|
51
|
|
|
) |
|
52
|
|
|
), |
|
53
|
|
|
), |
|
54
|
|
|
'Metadata' |
|
55
|
|
|
); |
|
56
|
|
|
}); |
|
57
|
|
|
|
|
58
|
|
|
return parent::getCMSFields(); |
|
59
|
|
|
} |
|
60
|
|
|
|
|
61
|
|
|
/** |
|
62
|
|
|
* This module always requires a page model. |
|
63
|
|
|
*/ |
|
64
|
|
|
public function requireDefaultRecords() |
|
65
|
|
|
{ |
|
66
|
|
|
parent::requireDefaultRecords(); |
|
67
|
|
|
if (!self::get()->exists() && $this->config()->create_default_pages) { |
|
68
|
|
|
$page = self::create( |
|
69
|
|
|
array( |
|
70
|
|
|
'Title' => 'Checkout', |
|
71
|
|
|
'URLSegment' => CheckoutPage_Controller::config()->url_segment, |
|
72
|
|
|
'ShowInMenus' => 0, |
|
73
|
|
|
) |
|
74
|
|
|
); |
|
75
|
|
|
$page->write(); |
|
76
|
|
|
$page->publish('Stage', 'Live'); |
|
77
|
|
|
$page->flushCache(); |
|
78
|
|
|
DB::alteration_message('Checkout page created', 'created'); |
|
79
|
|
|
} |
|
80
|
|
|
} |
|
81
|
|
|
} |
|
82
|
|
|
|
|
83
|
|
|
/** |
|
84
|
|
|
* @package shop |
|
85
|
|
|
* @mixin CheckoutPage |
|
86
|
|
|
* @mixin SteppedCheckout |
|
87
|
|
|
* @mixin CheckoutStep_Address |
|
88
|
|
|
* @mixin CheckoutStep_AddressBook |
|
89
|
|
|
* @mixin CheckoutStep_ContactDetails |
|
90
|
|
|
* @mixin CheckoutStep_Membership |
|
91
|
|
|
* @mixin CheckoutStep_PaymentMethod |
|
92
|
|
|
* @mixin CheckoutStep_Summary |
|
93
|
|
|
*/ |
|
94
|
|
|
class CheckoutPage_Controller extends Page_Controller |
|
95
|
|
|
{ |
|
96
|
|
|
private static $url_segment = 'checkout'; |
|
97
|
|
|
|
|
98
|
|
|
private static $allowed_actions = array( |
|
99
|
|
|
'OrderForm', |
|
100
|
|
|
'payment', |
|
101
|
|
|
'PaymentForm', |
|
102
|
|
|
); |
|
103
|
|
|
|
|
104
|
6 |
|
public function Title() |
|
105
|
|
|
{ |
|
106
|
6 |
|
if ($this->Title) { |
|
107
|
3 |
|
return $this->Title; |
|
108
|
|
|
} |
|
109
|
|
|
|
|
110
|
6 |
|
return _t('CheckoutPage.DefaultTitle', "Checkout"); |
|
111
|
|
|
} |
|
112
|
|
|
|
|
113
|
|
|
public function OrderForm() |
|
114
|
|
|
{ |
|
115
|
|
|
if (!(bool)$this->Cart()) { |
|
116
|
|
|
return false; |
|
117
|
|
|
} |
|
118
|
|
|
|
|
119
|
|
|
/** @var CheckoutComponentConfig $config */ |
|
120
|
|
|
$config = Injector::inst()->create("CheckoutComponentConfig", ShoppingCart::curr()); |
|
121
|
|
|
$form = PaymentForm::create($this, 'OrderForm', $config); |
|
122
|
|
|
|
|
123
|
|
|
// Normally, the payment is on a second page, either offsite or through /checkout/payment |
|
124
|
|
|
// If the site has customised the checkout component config to include an onsite payment |
|
125
|
|
|
// component, we should honor that and change the button label. PaymentForm::checkoutSubmit |
|
126
|
|
|
// will also check this and process payment if needed. |
|
127
|
|
|
if ($config->getComponentByType('OnsitePaymentCheckoutComponent')) { |
|
128
|
|
|
$form->setActions( |
|
129
|
|
|
FieldList::create( |
|
130
|
|
|
FormAction::create('checkoutSubmit', _t('CheckoutForm.SubmitPayment', 'Submit Payment')) |
|
131
|
|
|
) |
|
132
|
|
|
); |
|
133
|
|
|
} |
|
134
|
|
|
|
|
135
|
|
|
$form->Cart = $this->Cart(); |
|
136
|
|
|
$this->extend('updateOrderForm', $form); |
|
137
|
|
|
|
|
138
|
|
|
return $form; |
|
139
|
|
|
} |
|
140
|
|
|
|
|
141
|
|
|
/** |
|
142
|
|
|
* Action for making on-site payments |
|
143
|
|
|
*/ |
|
144
|
|
|
public function payment() |
|
145
|
|
|
{ |
|
146
|
|
|
if (!$this->Cart()) { |
|
147
|
|
|
return $this->redirect($this->Link()); |
|
148
|
|
|
} |
|
149
|
|
|
|
|
150
|
|
|
return array( |
|
151
|
|
|
'Title' => 'Make Payment', |
|
152
|
|
|
'OrderForm' => $this->PaymentForm(), |
|
153
|
|
|
); |
|
154
|
|
|
} |
|
155
|
|
|
|
|
156
|
|
|
public function PaymentForm() |
|
157
|
|
|
{ |
|
158
|
|
|
if (!(bool)$this->Cart()) { |
|
159
|
|
|
return false; |
|
160
|
|
|
} |
|
161
|
|
|
|
|
162
|
|
|
$config = new CheckoutComponentConfig(ShoppingCart::curr(), false); |
|
163
|
|
|
$config->addComponent(OnsitePaymentCheckoutComponent::create()); |
|
164
|
|
|
|
|
165
|
|
|
$form = PaymentForm::create($this, "PaymentForm", $config); |
|
166
|
|
|
|
|
167
|
|
|
$form->setActions( |
|
168
|
|
|
FieldList::create( |
|
169
|
|
|
FormAction::create("submitpayment", _t('CheckoutPage.SubmitPayment', "Submit Payment")) |
|
170
|
|
|
) |
|
171
|
|
|
); |
|
172
|
|
|
|
|
173
|
|
|
$form->setFailureLink($this->Link()); |
|
174
|
|
|
$this->extend('updatePaymentForm', $form); |
|
175
|
|
|
|
|
176
|
|
|
return $form; |
|
177
|
|
|
} |
|
178
|
|
|
|
|
179
|
|
|
/** |
|
180
|
|
|
* Retrieves error messages for the latest payment (if existing). |
|
181
|
|
|
* This can originate e.g. from an earlier offsite gateway API response. |
|
182
|
|
|
* |
|
183
|
|
|
* @return string |
|
184
|
|
|
*/ |
|
185
|
|
|
public function PaymentErrorMessage() |
|
186
|
|
|
{ |
|
187
|
|
|
$order = $this->Cart(); |
|
188
|
|
|
if (!$order) { |
|
189
|
|
|
return false; |
|
|
|
|
|
|
190
|
|
|
} |
|
191
|
|
|
|
|
192
|
|
|
$lastPayment = $order->Payments()->sort('Created', 'DESC')->first(); |
|
193
|
|
|
if (!$lastPayment) { |
|
194
|
|
|
return false; |
|
|
|
|
|
|
195
|
|
|
} |
|
196
|
|
|
|
|
197
|
|
|
$errorMessages = $lastPayment->Messages()->exclude('Message', '')->sort('Created', 'DESC'); |
|
198
|
|
|
$lastErrorMessage = null; |
|
199
|
|
|
foreach ($errorMessages as $errorMessage) { |
|
200
|
|
|
if ($errorMessage instanceof GatewayErrorMessage) { |
|
201
|
|
|
$lastErrorMessage = $errorMessage; |
|
202
|
|
|
break; |
|
203
|
|
|
} |
|
204
|
|
|
} |
|
205
|
|
|
if (!$lastErrorMessage) { |
|
206
|
|
|
return false; |
|
|
|
|
|
|
207
|
|
|
} |
|
208
|
|
|
|
|
209
|
|
|
return $lastErrorMessage->Message; |
|
210
|
|
|
} |
|
211
|
|
|
} |
|
212
|
|
|
|
If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.
Let’s take a look at an example:
Our function
my_functionexpects aPostobject, and outputs the author of the post. The base classPostreturns a simple string and outputting a simple string will work just fine. However, the child classBlogPostwhich is a sub-type ofPostinstead decided to return anobject, and is therefore violating the SOLID principles. If aBlogPostwere passed tomy_function, PHP would not complain, but ultimately fail when executing thestrtouppercall in its body.