|
1
|
|
|
<?php |
|
2
|
|
|
|
|
3
|
|
|
namespace Coyote\Http\Controllers\Job; |
|
4
|
|
|
|
|
5
|
|
|
use Coyote\Events\PaymentPaid; |
|
6
|
|
|
use Coyote\Http\Controllers\Controller; |
|
7
|
|
|
use Coyote\Http\Forms\Job\PaymentForm; |
|
8
|
|
|
use Coyote\Payment; |
|
9
|
|
|
use Coyote\Repositories\Contracts\CountryRepositoryInterface as CountryRepository; |
|
10
|
|
|
use Coyote\Repositories\Contracts\CouponRepositoryInterface as CouponRepository; |
|
11
|
|
|
use Coyote\Services\Invoice\Calculator; |
|
12
|
|
|
use Coyote\Services\Invoice\CalculatorFactory; |
|
13
|
|
|
use Coyote\Services\UrlBuilder\UrlBuilder; |
|
14
|
|
|
use Coyote\Services\Invoice\Generator as InvoiceGenerator; |
|
15
|
|
|
use Illuminate\Database\Connection as Db; |
|
16
|
|
|
use Illuminate\Http\Request; |
|
17
|
|
|
use Braintree\Configuration; |
|
18
|
|
|
use Braintree\ClientToken; |
|
19
|
|
|
use Braintree\Transaction; |
|
20
|
|
|
use Braintree\Exception; |
|
21
|
|
|
|
|
22
|
|
|
class PaymentController extends Controller |
|
23
|
|
|
{ |
|
24
|
|
|
/** |
|
25
|
|
|
* @var InvoiceGenerator |
|
26
|
|
|
*/ |
|
27
|
|
|
private $invoice; |
|
28
|
|
|
|
|
29
|
|
|
/** |
|
30
|
|
|
* @var CountryRepository |
|
31
|
|
|
*/ |
|
32
|
|
|
private $country; |
|
33
|
|
|
|
|
34
|
|
|
/** |
|
35
|
|
|
* @var Db |
|
36
|
|
|
*/ |
|
37
|
|
|
private $db; |
|
38
|
|
|
|
|
39
|
|
|
/** |
|
40
|
|
|
* @var CouponRepository |
|
41
|
|
|
*/ |
|
42
|
|
|
private $coupon; |
|
43
|
|
|
|
|
44
|
|
|
/** |
|
45
|
|
|
* @var array |
|
46
|
|
|
*/ |
|
47
|
|
|
private $vatRates; |
|
48
|
|
|
|
|
49
|
|
|
/** |
|
50
|
|
|
* @param InvoiceGenerator $invoice |
|
51
|
|
|
* @param Db $db |
|
52
|
|
|
* @param CountryRepository $country |
|
53
|
|
|
* @param CouponRepository $coupon |
|
54
|
|
|
*/ |
|
55
|
|
|
public function __construct(InvoiceGenerator $invoice, Db $db, CountryRepository $country, CouponRepository $coupon) |
|
56
|
|
|
{ |
|
57
|
|
|
parent::__construct(); |
|
58
|
|
|
|
|
59
|
|
|
$this->invoice = $invoice; |
|
60
|
|
|
$this->db = $db; |
|
61
|
|
|
$this->country = $country; |
|
62
|
|
|
$this->coupon = $coupon; |
|
63
|
|
|
|
|
64
|
|
|
$this->middleware(function (Request $request, $next) { |
|
65
|
|
|
/** @var \Coyote\Payment $payment */ |
|
66
|
|
|
$payment = $request->route('payment'); |
|
67
|
|
|
|
|
68
|
|
|
abort_if($payment->status_id == Payment::PAID, 404); |
|
69
|
|
|
|
|
70
|
|
|
return $next($request); |
|
71
|
|
|
}); |
|
72
|
|
|
|
|
73
|
|
|
$this->breadcrumb->push('Praca', route('job.home')); |
|
74
|
|
|
$this->vatRates = $this->country->vatRatesList(); |
|
75
|
|
|
|
|
76
|
|
|
Configuration::environment(config('services.braintree.env')); |
|
77
|
|
|
Configuration::merchantId(config('services.braintree.merchant_id')); |
|
78
|
|
|
Configuration::publicKey(config('services.braintree.public_key')); |
|
79
|
|
|
Configuration::privateKey(config('services.braintree.private_key')); |
|
80
|
|
|
} |
|
81
|
|
|
|
|
82
|
|
|
/** |
|
83
|
|
|
* @param \Coyote\Payment $payment |
|
84
|
|
|
* @return \Illuminate\View\View |
|
85
|
|
|
*/ |
|
86
|
|
|
public function index($payment) |
|
87
|
|
|
{ |
|
88
|
|
|
$this->breadcrumb->push($payment->job->title, UrlBuilder::job($payment->job)); |
|
89
|
|
|
$this->breadcrumb->push('Promowanie ogłoszenia'); |
|
90
|
|
|
|
|
91
|
|
|
/** @var PaymentForm $form */ |
|
92
|
|
|
$form = $this->getForm($payment); |
|
93
|
|
|
|
|
94
|
|
|
// calculate price based on payment details |
|
95
|
|
|
$calculator = CalculatorFactory::payment($payment); |
|
96
|
|
|
$calculator->vatRate = $this->vatRates[$form->get('invoice')->get('country_id')->getValue()] ?? $calculator->vatRate; |
|
97
|
|
|
|
|
98
|
|
|
$coupon = $this->coupon->firstOrNew(['code' => $form->get('coupon')->getValue()]); |
|
99
|
|
|
|
|
100
|
|
|
$this->request->attributes->set('validate_coupon_url', route('job.coupon')); |
|
101
|
|
|
|
|
102
|
|
|
return $this->view('job.payment', [ |
|
103
|
|
|
'form' => $form, |
|
104
|
|
|
'payment' => $payment, |
|
105
|
|
|
'vat_rates' => $this->vatRates, |
|
106
|
|
|
'calculator' => $calculator->toArray(), |
|
107
|
|
|
'default_vat_rate' => $payment->plan->vat_rate, |
|
108
|
|
|
'client_token' => ClientToken::generate(), |
|
109
|
|
|
'coupon' => $coupon->toArray() |
|
110
|
|
|
]); |
|
111
|
|
|
} |
|
112
|
|
|
|
|
113
|
|
|
/** |
|
114
|
|
|
* @param \Coyote\Payment $payment |
|
115
|
|
|
* @return \Illuminate\Http\RedirectResponse |
|
116
|
|
|
* @throws \Exception |
|
117
|
|
|
*/ |
|
118
|
|
|
public function process($payment) |
|
119
|
|
|
{ |
|
120
|
|
|
/** @var PaymentForm $form */ |
|
121
|
|
|
$form = $this->getForm($payment); |
|
122
|
|
|
$form->validate(); |
|
123
|
|
|
|
|
124
|
|
|
$calculator = CalculatorFactory::payment($payment); |
|
125
|
|
|
$calculator->vatRate = $this->vatRates[$form->get('invoice')->getValue()['country_id']] ?? $calculator->vatRate; |
|
126
|
|
|
|
|
127
|
|
|
$coupon = $this->coupon->findBy('code', $form->get('coupon')->getValue()); |
|
128
|
|
|
$calculator->setCoupon($coupon); |
|
129
|
|
|
|
|
130
|
|
|
return $this->handlePayment(function () use ($payment, $form, $calculator, $coupon) { |
|
131
|
|
|
$this->makeTransaction($payment, $calculator); |
|
132
|
|
|
|
|
133
|
|
|
// save invoice data. keep in mind that we do not setup invoice number until payment is done. |
|
134
|
|
|
/** @var \Coyote\Invoice $invoice */ |
|
135
|
|
|
$invoice = $this->invoice->create( |
|
136
|
|
|
array_merge($form->get('enable_invoice')->isChecked() ? $form->all()['invoice'] : [], ['user_id' => $this->auth->id]), |
|
137
|
|
|
$payment, |
|
138
|
|
|
$calculator |
|
139
|
|
|
); |
|
140
|
|
|
|
|
141
|
|
|
if ($coupon) { |
|
142
|
|
|
$payment->coupon_id = $coupon->id; |
|
143
|
|
|
|
|
144
|
|
|
$coupon->delete(); |
|
145
|
|
|
} |
|
146
|
|
|
|
|
147
|
|
|
// associate invoice with payment |
|
148
|
|
|
$payment->invoice()->associate($invoice); |
|
149
|
|
|
$payment->save(); |
|
150
|
|
|
|
|
151
|
|
|
// boost job offer, send invoice and reindex |
|
152
|
|
|
event(new PaymentPaid($payment, $this->auth)); |
|
153
|
|
|
|
|
154
|
|
|
return redirect() |
|
155
|
|
|
->to(UrlBuilder::job($payment->job)) |
|
156
|
|
|
->with('success', trans('payment.success')); |
|
157
|
|
|
}); |
|
158
|
|
|
} |
|
159
|
|
|
|
|
160
|
|
|
/** |
|
161
|
|
|
* @param \Coyote\Payment $payment |
|
162
|
|
|
* @return \Coyote\Services\FormBuilder\Form |
|
163
|
|
|
*/ |
|
164
|
|
|
private function getForm($payment) |
|
165
|
|
|
{ |
|
166
|
|
|
return $this->createForm(PaymentForm::class, $payment); |
|
167
|
|
|
} |
|
168
|
|
|
|
|
169
|
|
|
/** |
|
170
|
|
|
* @param Payment $payment |
|
171
|
|
|
* @param Calculator $calculator |
|
172
|
|
|
* @throws Exception\ValidationsFailed |
|
173
|
|
|
*/ |
|
174
|
|
|
private function makeTransaction(Payment $payment, Calculator $calculator) |
|
175
|
|
|
{ |
|
176
|
|
|
if (!$calculator->grossPrice()) { |
|
177
|
|
|
return; |
|
178
|
|
|
} |
|
179
|
|
|
|
|
180
|
|
|
/** @var mixed $result */ |
|
181
|
|
|
$result = Transaction::sale([ |
|
182
|
|
|
'amount' => number_format($calculator->grossPrice(), 2, '.', ''), |
|
183
|
|
|
'orderId' => $payment->id, |
|
184
|
|
|
'paymentMethodNonce' => $this->request->input("payment_method_nonce"), |
|
185
|
|
|
'options' => [ |
|
186
|
|
|
'submitForSettlement' => true |
|
187
|
|
|
] |
|
188
|
|
|
]); |
|
189
|
|
|
|
|
190
|
|
|
/** @var $result \Braintree\Result\Error */ |
|
191
|
|
|
if (!$result->success || is_null($result->transaction)) { |
|
192
|
|
|
/** @var \Braintree\Error\Validation $error */ |
|
193
|
|
|
$error = array_first($result->errors->deepAll()); |
|
194
|
|
|
logger()->error(var_export($result, true)); |
|
195
|
|
|
|
|
196
|
|
|
if (is_null($error)) { |
|
197
|
|
|
throw new Exception\ValidationsFailed(); |
|
198
|
|
|
} |
|
199
|
|
|
|
|
200
|
|
|
throw new Exception\ValidationsFailed($error->message, $error->code); |
|
201
|
|
|
} |
|
202
|
|
|
|
|
203
|
|
|
logger()->debug('Successfully payment', ['result' => $result]); |
|
204
|
|
|
} |
|
205
|
|
|
|
|
206
|
|
|
/** |
|
207
|
|
|
* @param \Exception $exception |
|
208
|
|
|
* @param string $message |
|
209
|
|
|
* @return \Illuminate\Http\RedirectResponse |
|
210
|
|
|
*/ |
|
211
|
|
|
private function handlePaymentException($exception, $message) |
|
212
|
|
|
{ |
|
213
|
|
|
$back = back()->withInput()->with('error', $message); |
|
214
|
|
|
|
|
215
|
|
|
// remove sensitive data |
|
216
|
|
|
$this->request->merge(['number' => '***', 'cvc' => '***']); |
|
217
|
|
|
$_POST['number'] = $_POST['cvc'] = '***'; |
|
218
|
|
|
|
|
219
|
|
|
$this->db->rollBack(); |
|
220
|
|
|
// log error. sensitive data won't be saved (we removed them) |
|
221
|
|
|
logger()->error($exception); |
|
222
|
|
|
|
|
223
|
|
|
if (app()->environment('production')) { |
|
224
|
|
|
app('sentry')->captureException($exception); |
|
225
|
|
|
} |
|
226
|
|
|
|
|
227
|
|
|
return $back; |
|
228
|
|
|
} |
|
229
|
|
|
|
|
230
|
|
|
/** |
|
231
|
|
|
* @param \Closure $callback |
|
232
|
|
|
* @return \Illuminate\Http\RedirectResponse|mixed |
|
233
|
|
|
*/ |
|
234
|
|
|
private function handlePayment(\Closure $callback) |
|
235
|
|
|
{ |
|
236
|
|
|
$this->db->beginTransaction(); |
|
237
|
|
|
|
|
238
|
|
|
try { |
|
239
|
|
|
$result = $callback(); |
|
240
|
|
|
$this->db->commit(); |
|
241
|
|
|
|
|
242
|
|
|
return $result; |
|
243
|
|
|
} catch (Exception\Authentication $e) { |
|
244
|
|
|
return $this->handlePaymentException($e, trans('payment.forbidden')); |
|
|
|
|
|
|
245
|
|
|
} catch (Exception\Authorization $e) { |
|
246
|
|
|
return $this->handlePaymentException($e, trans('payment.unauthorized')); |
|
|
|
|
|
|
247
|
|
|
} catch (Exception\Timeout $e) { |
|
248
|
|
|
return $this->handlePaymentException($e, trans('payment.timeout')); |
|
|
|
|
|
|
249
|
|
|
} catch (Exception\ServerError $e) { |
|
250
|
|
|
return $this->handlePaymentException($e, trans('payment.unauthorized')); |
|
|
|
|
|
|
251
|
|
|
} catch (Exception\ValidationsFailed $e) { |
|
252
|
|
|
return $this->handlePaymentException($e, $e->getMessage() ?: trans('payment.validation')); |
|
253
|
|
|
} catch (\Exception $e) { |
|
254
|
|
|
return $this->handlePaymentException($e, trans('payment.unhandled')); |
|
|
|
|
|
|
255
|
|
|
} |
|
256
|
|
|
} |
|
257
|
|
|
} |
|
258
|
|
|
|
This check looks at variables that are passed out again to other methods.
If the outgoing method call has stricter type requirements than the method itself, an issue is raised.
An additional type check may prevent trouble.