1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
use Omnipay\Common\CreditCard; |
4
|
|
|
use Omnipay\Common\Message\ResponseInterface; |
5
|
|
|
|
6
|
|
|
class PurchaseService extends PaymentService |
7
|
|
|
{ |
8
|
|
|
|
9
|
|
|
/** |
10
|
|
|
* Attempt to make a payment. |
11
|
|
|
* |
12
|
|
|
* @param array $data returnUrl/cancelUrl + customer creditcard and billing/shipping details. |
13
|
|
|
* Some keys (e.g. "amount") are overwritten with data from the associated {@link $payment}. |
14
|
|
|
* If this array is constructed from user data (e.g. a form submission), please take care |
15
|
|
|
* to whitelist accepted fields, in order to ensure sensitive gateway parameters like "freeShipping" can't be set. |
16
|
|
|
* If using {@link Form->getData()}, only fields which exist in the form are returned, |
17
|
|
|
* effectively whitelisting against arbitrary user input. |
18
|
|
|
* @return ResponseInterface omnipay's response class, specific to the chosen gateway. |
19
|
|
|
*/ |
20
|
|
|
public function purchase($data = array()) { |
|
|
|
|
21
|
|
|
if ($this->payment->Status !== "Created") { |
|
|
|
|
22
|
|
|
return null; //could be handled better? send payment response? |
23
|
|
|
} |
24
|
|
|
if (!$this->payment->isInDB()) { |
25
|
|
|
$this->payment->write(); |
26
|
|
|
} |
27
|
|
|
//update success/fail urls |
28
|
|
|
$this->update($data); |
29
|
|
|
|
30
|
|
|
//set the client IP address, if not already set |
31
|
|
|
if(!isset($data['clientIp'])){ |
32
|
|
|
$data['clientIp'] = Controller::curr()->getRequest()->getIP(); |
33
|
|
|
} |
34
|
|
|
|
35
|
|
|
$gatewaydata = array_merge($data, array( |
36
|
|
|
'amount' => (float) $this->payment->MoneyAmount, |
|
|
|
|
37
|
|
|
'currency' => $this->payment->MoneyCurrency, |
|
|
|
|
38
|
|
|
//set all gateway return/cancel/notify urls to PaymentGatewayController endpoint |
39
|
|
|
'returnUrl' => $this->getEndpointURL("complete", $this->payment->Identifier), |
|
|
|
|
40
|
|
|
'cancelUrl' => $this->getEndpointURL("cancel", $this->payment->Identifier), |
|
|
|
|
41
|
|
|
'notifyUrl' => $this->getEndpointURL("notify", $this->payment->Identifier) |
|
|
|
|
42
|
|
|
)); |
43
|
|
|
|
44
|
|
|
// Often, the shop will want to pass in a transaction ID (order #, etc), but if there's |
45
|
|
|
// not one we need to set it as Ominpay requires this. |
46
|
|
|
if(!isset($gatewaydata['transactionId'])){ |
47
|
|
|
$gatewaydata['transactionId'] = $this->payment->Identifier; |
|
|
|
|
48
|
|
|
} |
49
|
|
|
|
50
|
|
|
// We only look for a card if we aren't already provided with a token |
51
|
|
|
// Increasingly we can expect tokens or nonce's to be more common (e.g. Stripe and Braintree) |
52
|
|
|
$tokenKey = Payment::config()->token_key ?: 'token'; |
53
|
|
|
if (empty($gatewaydata[$tokenKey])) { |
54
|
|
|
$gatewaydata['card'] = $this->getCreditCard($data); |
55
|
|
|
} elseif ($tokenKey !== 'token') { |
56
|
|
|
// some gateways (eg. braintree) use a different key but we need |
57
|
|
|
// to normalize that for omnipay |
58
|
|
|
$gatewaydata['token'] = $gatewaydata[$tokenKey]; |
59
|
|
|
unset($gatewaydata[$tokenKey]); |
60
|
|
|
} |
61
|
|
|
|
62
|
|
|
$this->extend('onBeforePurchase', $gatewaydata); |
63
|
|
|
$request = $this->oGateway()->purchase($gatewaydata); |
|
|
|
|
64
|
|
|
$this->extend('onAfterPurchase', $request); |
65
|
|
|
|
66
|
|
|
$message = $this->createMessage('PurchaseRequest', $request); |
67
|
|
|
$message->SuccessURL = $this->returnurl; |
68
|
|
|
$message->FailureURL = $this->cancelurl; |
69
|
|
|
$message->write(); |
70
|
|
|
|
71
|
|
|
$gatewayresponse = $this->createGatewayResponse(); |
72
|
|
|
try { |
73
|
|
|
$response = $this->response = $request->send(); |
74
|
|
|
$this->extend('onAfterSendPurchase', $request, $response); |
75
|
|
|
$gatewayresponse->setOmnipayResponse($response); |
76
|
|
|
//update payment model |
77
|
|
|
if (GatewayInfo::isManual($this->payment->Gateway)) { |
|
|
|
|
78
|
|
|
//initiate manual payment |
79
|
|
|
$this->createMessage('AuthorizedResponse', $response); |
80
|
|
|
$this->payment->Status = 'Authorized'; |
|
|
|
|
81
|
|
|
$this->payment->write(); |
82
|
|
|
$gatewayresponse->setMessage("Manual payment authorised"); |
83
|
|
View Code Duplication |
} elseif ($response->isSuccessful()) { |
|
|
|
|
84
|
|
|
//successful payment |
85
|
|
|
$this->createMessage('PurchasedResponse', $response); |
86
|
|
|
$this->payment->Status = 'Captured'; |
|
|
|
|
87
|
|
|
$gatewayresponse->setMessage("Payment successful"); |
88
|
|
|
$this->payment->write(); |
89
|
|
|
$this->payment->extend('onCaptured', $gatewayresponse); |
90
|
|
|
} elseif ($response->isRedirect()) { |
91
|
|
|
// redirect to off-site payment gateway |
92
|
|
|
$this->createMessage('PurchaseRedirectResponse', $response); |
93
|
|
|
$this->payment->Status = 'Authorized'; |
|
|
|
|
94
|
|
|
$this->payment->write(); |
95
|
|
|
$gatewayresponse->setMessage("Redirecting to gateway"); |
96
|
|
View Code Duplication |
} else { |
|
|
|
|
97
|
|
|
//handle error |
98
|
|
|
$this->createMessage('PurchaseError', $response); |
99
|
|
|
$gatewayresponse->setMessage( |
100
|
|
|
"Error (".$response->getCode()."): ".$response->getMessage() |
101
|
|
|
); |
102
|
|
|
} |
103
|
|
|
} catch (Omnipay\Common\Exception\OmnipayException $e) { |
104
|
|
|
$this->createMessage('PurchaseError', $e); |
|
|
|
|
105
|
|
|
$gatewayresponse->setMessage($e->getMessage()); |
106
|
|
|
} |
107
|
|
|
$gatewayresponse->setRedirectURL($this->getRedirectURL()); |
108
|
|
|
|
109
|
|
|
return $gatewayresponse; |
110
|
|
|
} |
111
|
|
|
|
112
|
|
|
/** |
113
|
|
|
* Finalise this payment, after off-site external processing. |
114
|
|
|
* This is ususally only called by PaymentGatewayController. |
115
|
|
|
* @return GatewayResponse encapsulated response info |
116
|
|
|
*/ |
117
|
|
|
public function completePurchase($data = array()) { |
118
|
|
|
$gatewayresponse = $this->createGatewayResponse(); |
119
|
|
|
|
120
|
|
|
//set the client IP address, if not already set |
121
|
|
|
if(!isset($data['clientIp'])){ |
122
|
|
|
$data['clientIp'] = Controller::curr()->getRequest()->getIP(); |
123
|
|
|
} |
124
|
|
|
|
125
|
|
|
$gatewaydata = array_merge($data, array( |
126
|
|
|
'amount' => (float) $this->payment->MoneyAmount, |
|
|
|
|
127
|
|
|
'currency' => $this->payment->MoneyCurrency |
|
|
|
|
128
|
|
|
)); |
129
|
|
|
|
130
|
|
|
$this->payment->extend('onBeforeCompletePurchase', $gatewaydata); |
131
|
|
|
$request = $this->oGateway()->completePurchase($gatewaydata); |
|
|
|
|
132
|
|
|
$this->payment->extend('onAfterCompletePurchase', $request); |
133
|
|
|
|
134
|
|
|
$this->createMessage('CompletePurchaseRequest', $request); |
135
|
|
|
$response = null; |
136
|
|
|
try { |
137
|
|
|
$response = $this->response = $request->send(); |
138
|
|
|
$this->extend('onAfterSendCompletePurchase', $request, $response); |
139
|
|
|
$gatewayresponse->setOmnipayResponse($response); |
140
|
|
View Code Duplication |
if ($response->isSuccessful()) { |
|
|
|
|
141
|
|
|
$this->createMessage('PurchasedResponse', $response); |
142
|
|
|
$this->payment->Status = 'Captured'; |
|
|
|
|
143
|
|
|
$this->payment->write(); |
144
|
|
|
$this->payment->extend('onCaptured', $gatewayresponse); |
145
|
|
|
} else { |
146
|
|
|
$this->createMessage('CompletePurchaseError', $response); |
147
|
|
|
} |
148
|
|
|
} catch (Omnipay\Common\Exception\OmnipayException $e) { |
149
|
|
|
$this->createMessage("CompletePurchaseError", $e); |
|
|
|
|
150
|
|
|
} |
151
|
|
|
|
152
|
|
|
return $gatewayresponse; |
153
|
|
|
} |
154
|
|
|
|
155
|
|
|
public function cancelPurchase() { |
156
|
|
|
//TODO: do lookup? / try to complete purchase? |
157
|
|
|
//TODO: omnipay void call |
158
|
|
|
$this->payment->Status = 'Void'; |
|
|
|
|
159
|
|
|
$this->payment->write(); |
160
|
|
|
$this->createMessage('VoidRequest', array( |
161
|
|
|
"Message" => "The payment was cancelled." |
162
|
|
|
)); |
163
|
|
|
|
164
|
|
|
//return response |
165
|
|
|
} |
166
|
|
|
|
167
|
|
|
/** |
168
|
|
|
* @return \Omnipay\Common\CreditCard |
169
|
|
|
*/ |
170
|
|
|
protected function getCreditCard($data) { |
171
|
|
|
return new CreditCard($data); |
172
|
|
|
} |
173
|
|
|
|
174
|
|
|
} |
175
|
|
|
|
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.