|
1
|
|
|
<?php namespace BB\Repo; |
|
2
|
|
|
|
|
3
|
|
|
use BB\Entities\Payment; |
|
4
|
|
|
use BB\Events\MemberBalanceChanged; |
|
5
|
|
|
use BB\Exceptions\NotImplementedException; |
|
6
|
|
|
use BB\Exceptions\PaymentException; |
|
7
|
|
|
use Carbon\Carbon; |
|
8
|
|
|
|
|
9
|
|
|
class PaymentRepository extends DBRepository |
|
10
|
|
|
{ |
|
11
|
|
|
|
|
12
|
|
|
/** |
|
13
|
|
|
* @var Payment |
|
14
|
|
|
*/ |
|
15
|
|
|
protected $model; |
|
16
|
|
|
|
|
17
|
|
|
public static $SUBSCRIPTION = 'subscription'; |
|
18
|
|
|
public static $INDUCTION = 'induction'; |
|
19
|
|
|
|
|
20
|
|
|
private $reason = null; |
|
21
|
|
|
private $source = null; |
|
22
|
|
|
|
|
23
|
|
|
/** |
|
24
|
|
|
* @param Payment $model |
|
25
|
|
|
*/ |
|
26
|
|
|
public function __construct(Payment $model) |
|
27
|
|
|
{ |
|
28
|
|
|
$this->model = $model; |
|
29
|
|
|
$this->perPage = 10; |
|
30
|
|
|
} |
|
31
|
|
|
|
|
32
|
|
|
|
|
33
|
|
|
public function getPaginated(array $params) |
|
34
|
|
|
{ |
|
35
|
|
|
$model = $this->model; |
|
36
|
|
|
|
|
37
|
|
View Code Duplication |
if ($this->hasDateFilter()) { |
|
|
|
|
|
|
38
|
|
|
$model = $model->where('created_at', '>=', $this->startDate)->where('created_at', '<=', $this->endDate); |
|
|
|
|
|
|
39
|
|
|
} |
|
40
|
|
|
|
|
41
|
|
|
if ($this->hasMemberFilter()) { |
|
42
|
|
|
$model = $model->where('user_id', $this->memberId); |
|
43
|
|
|
} |
|
44
|
|
|
|
|
45
|
|
|
if ($this->hasReasonFilter()) { |
|
46
|
|
|
$model = $model->where('reason', $this->reason); |
|
47
|
|
|
} |
|
48
|
|
|
|
|
49
|
|
|
if ($this->hasSourceFilter()) { |
|
50
|
|
|
$model = $model->where('source', $this->source); |
|
51
|
|
|
} |
|
52
|
|
|
|
|
53
|
|
View Code Duplication |
if ($this->isSortable($params)) { |
|
|
|
|
|
|
54
|
|
|
return $model->orderBy($params['sortBy'], $params['direction'])->paginate($this->perPage); |
|
55
|
|
|
} |
|
56
|
|
|
return $model->paginate($this->perPage); |
|
57
|
|
|
} |
|
58
|
|
|
|
|
59
|
|
|
|
|
60
|
|
|
public function getTotalAmount() |
|
61
|
|
|
{ |
|
62
|
|
|
$model = $this->model; |
|
63
|
|
|
|
|
64
|
|
View Code Duplication |
if ($this->hasDateFilter()) { |
|
|
|
|
|
|
65
|
|
|
$model = $model->where('created_at', '>=', $this->startDate)->where('created_at', '<=', $this->endDate); |
|
|
|
|
|
|
66
|
|
|
} |
|
67
|
|
|
|
|
68
|
|
|
if ($this->hasMemberFilter()) { |
|
69
|
|
|
$model = $model->where('user_id', $this->memberId); |
|
70
|
|
|
} |
|
71
|
|
|
|
|
72
|
|
|
if ($this->hasReasonFilter()) { |
|
73
|
|
|
$model = $model->where('reason', $this->reason); |
|
74
|
|
|
} |
|
75
|
|
|
|
|
76
|
|
|
if ($this->hasSourceFilter()) { |
|
77
|
|
|
$model = $model->where('source', $this->source); |
|
78
|
|
|
} |
|
79
|
|
|
|
|
80
|
|
|
return $model->get()->sum('amount'); |
|
81
|
|
|
} |
|
82
|
|
|
|
|
83
|
|
|
|
|
84
|
|
|
/** |
|
85
|
|
|
* Record a payment against a user record |
|
86
|
|
|
* |
|
87
|
|
|
* @param string $reason What was the reason. subscription, induction, etc... |
|
88
|
|
|
* @param int $userId The users ID |
|
89
|
|
|
* @param string $source gocardless, paypal |
|
90
|
|
|
* @param string $sourceId A reference for the source |
|
91
|
|
|
* @param double $amount Amount received before a fee |
|
92
|
|
|
* @param string $status paid, pending, cancelled, refunded |
|
93
|
|
|
* @param double $fee The fee charged by the payment provider |
|
94
|
|
|
* @param string $ref |
|
95
|
|
|
* @param Carbon $paidDate |
|
96
|
|
|
* @return int The ID of the payment record |
|
97
|
|
|
*/ |
|
98
|
|
|
public function recordPayment($reason, $userId, $source, $sourceId, $amount, $status = 'paid', $fee = 0.0, $ref = '', Carbon $paidDate = null) |
|
99
|
|
|
{ |
|
100
|
|
|
if ($paidDate == null) { |
|
101
|
|
|
$paidDate = new Carbon(); |
|
102
|
|
|
} |
|
103
|
|
|
//If we have an existing similer record dont create another, except for when there is no source id |
|
104
|
|
|
$existingRecord = $this->model->where('source', $source)->where('source_id', $sourceId)->where('user_id', $userId)->first(); |
|
|
|
|
|
|
105
|
|
|
if ( ! $existingRecord || empty($sourceId)) { |
|
106
|
|
|
$record = new $this->model; |
|
107
|
|
|
$record->user_id = $userId; |
|
108
|
|
|
$record->reason = $reason; |
|
109
|
|
|
$record->source = $source; |
|
110
|
|
|
$record->source_id = $sourceId; |
|
111
|
|
|
$record->amount = $amount; |
|
112
|
|
|
$record->amount_minus_fee = ($amount - $fee); |
|
113
|
|
|
$record->fee = $fee; |
|
114
|
|
|
$record->status = $status; |
|
115
|
|
|
$record->reference = $ref; |
|
116
|
|
|
if ($status == 'paid') { |
|
117
|
|
|
$record->paid_at = $paidDate; |
|
118
|
|
|
} |
|
119
|
|
|
$record->save(); |
|
120
|
|
|
} else { |
|
121
|
|
|
$record = $existingRecord; |
|
122
|
|
|
} |
|
123
|
|
|
|
|
124
|
|
|
//Emit an event so that things like the balance updater can run |
|
125
|
|
|
\Event::fire('payment.create', array($userId, $reason, $ref, $record->id, $status)); |
|
126
|
|
|
|
|
127
|
|
|
return $record->id; |
|
128
|
|
|
} |
|
129
|
|
|
|
|
130
|
|
|
/** |
|
131
|
|
|
* Record a subscription payment |
|
132
|
|
|
* |
|
133
|
|
|
* @param int $userId The users ID |
|
134
|
|
|
* @param string $source gocardless, paypal |
|
135
|
|
|
* @param string $sourceId A reference for the source |
|
136
|
|
|
* @param double $amount Amount received before a fee |
|
137
|
|
|
* @param string $status paid, pending, cancelled, refunded |
|
138
|
|
|
* @param double $fee The fee charged by the payment provider |
|
139
|
|
|
* @param string|null $ref |
|
140
|
|
|
* @param Carbon $paidDate |
|
141
|
|
|
* @return int The ID of the payment record |
|
142
|
|
|
*/ |
|
143
|
|
|
public function recordSubscriptionPayment($userId, $source, $sourceId, $amount, $status = 'paid', $fee = 0.0, $ref = '', Carbon $paidDate = null) |
|
144
|
|
|
{ |
|
145
|
|
|
return $this->recordPayment('subscription', $userId, $source, $sourceId, $amount, $status, $fee, $ref, $paidDate); |
|
146
|
|
|
} |
|
147
|
|
|
|
|
148
|
|
|
/** |
|
149
|
|
|
* An existing payment has been set to paid |
|
150
|
|
|
* |
|
151
|
|
|
* @param $paymentId |
|
152
|
|
|
* @param Carbon $paidDate |
|
153
|
|
|
*/ |
|
154
|
|
|
public function markPaymentPaid($paymentId, $paidDate) |
|
155
|
|
|
{ |
|
156
|
|
|
$payment = $this->getById($paymentId); |
|
157
|
|
|
$payment->status = 'paid'; |
|
158
|
|
|
$payment->paid_at = $paidDate; |
|
159
|
|
|
$payment->save(); |
|
160
|
|
|
|
|
161
|
|
|
\Event::fire('payment.paid', array($payment->user_id, $paymentId, $payment->reason, $payment->reference, $paidDate)); |
|
162
|
|
|
} |
|
163
|
|
|
|
|
164
|
|
|
/** |
|
165
|
|
|
* Record a payment failure or cancellation |
|
166
|
|
|
* |
|
167
|
|
|
* @param int $paymentId |
|
168
|
|
|
* @param string $status |
|
169
|
|
|
*/ |
|
170
|
|
|
public function recordPaymentFailure($paymentId, $status = 'failed') |
|
171
|
|
|
{ |
|
172
|
|
|
$this->update($paymentId, ['status' => $status]); |
|
173
|
|
|
|
|
174
|
|
|
$payment = $this->getById($paymentId); |
|
175
|
|
|
|
|
176
|
|
|
\Event::fire('payment.cancelled', array($paymentId, $payment->user_id, $payment->reason, $payment->reference, $status)); |
|
177
|
|
|
} |
|
178
|
|
|
|
|
179
|
|
|
/** |
|
180
|
|
|
* Assign an unassigned payment to a user |
|
181
|
|
|
* |
|
182
|
|
|
* @param int $paymentId |
|
183
|
|
|
* @param int $userId |
|
184
|
|
|
* |
|
185
|
|
|
* @throws PaymentException |
|
186
|
|
|
*/ |
|
187
|
|
|
public function assignPaymentToUser($paymentId, $userId) |
|
188
|
|
|
{ |
|
189
|
|
|
$payment = $this->getById($paymentId); |
|
190
|
|
|
|
|
191
|
|
|
if (!empty($payment->user_id)) { |
|
192
|
|
|
throw new PaymentException('Payment already assigned to user'); |
|
193
|
|
|
} |
|
194
|
|
|
|
|
195
|
|
|
$this->update($paymentId, ['user_id' => $userId]); |
|
196
|
|
|
} |
|
197
|
|
|
|
|
198
|
|
|
public function refundPaymentToBalance($paymentId) |
|
199
|
|
|
{ |
|
200
|
|
|
$payment = $this->getById($paymentId); |
|
201
|
|
|
|
|
202
|
|
|
if ($payment->reason !== 'donation') { |
|
203
|
|
|
throw new NotImplementedException('This hasn\'t been built yet'); |
|
204
|
|
|
} |
|
205
|
|
|
|
|
206
|
|
|
$this->update($paymentId, ['reason' => 'balance']); |
|
207
|
|
|
|
|
208
|
|
|
event(new MemberBalanceChanged($payment->user_id)); |
|
209
|
|
|
} |
|
210
|
|
|
|
|
211
|
|
|
|
|
212
|
|
|
/** |
|
213
|
|
|
* Fetch the users latest payment of a particular type |
|
214
|
|
|
* @param integer $userId |
|
215
|
|
|
* @param string $reason |
|
216
|
|
|
* @return mixed |
|
217
|
|
|
*/ |
|
218
|
|
View Code Duplication |
public function latestUserPayment($userId, $reason = 'subscription') |
|
|
|
|
|
|
219
|
|
|
{ |
|
220
|
|
|
return $this->model->where('user_id', $userId) |
|
|
|
|
|
|
221
|
|
|
->whereRaw('reason = ? and (status = ? or status = ? or status = ?)', [$reason, 'paid', 'pending', 'withdrawn']) |
|
222
|
|
|
->orderBy('created_at', 'desc') |
|
223
|
|
|
->first(); |
|
224
|
|
|
} |
|
225
|
|
|
|
|
226
|
|
|
|
|
227
|
|
|
/** |
|
228
|
|
|
* Get all user payments of a specific reason |
|
229
|
|
|
* @param $userId |
|
230
|
|
|
* @param string $reason |
|
231
|
|
|
* @return mixed |
|
232
|
|
|
*/ |
|
233
|
|
View Code Duplication |
public function getUserPaymentsByReason($userId, $reason) |
|
|
|
|
|
|
234
|
|
|
{ |
|
235
|
|
|
return $this->model->where('user_id', $userId) |
|
|
|
|
|
|
236
|
|
|
->whereRaw('reason = ? and (status = ? or status = ? or status = ?)', [$reason, 'paid', 'pending', 'withdrawn']) |
|
237
|
|
|
->orderBy('created_at', 'desc') |
|
238
|
|
|
->get(); |
|
239
|
|
|
} |
|
240
|
|
|
|
|
241
|
|
|
|
|
242
|
|
|
/** |
|
243
|
|
|
* @param string $source |
|
244
|
|
|
*/ |
|
245
|
|
View Code Duplication |
public function getUserPaymentsBySource($userId, $source) |
|
|
|
|
|
|
246
|
|
|
{ |
|
247
|
|
|
return $this->model->where('user_id', $userId) |
|
|
|
|
|
|
248
|
|
|
->whereRaw('source = ? and (status = ? or status = ? or status = ?)', [$source, 'paid', 'pending', 'withdrawn']) |
|
249
|
|
|
->orderBy('created_at', 'desc') |
|
250
|
|
|
->get(); |
|
251
|
|
|
} |
|
252
|
|
|
|
|
253
|
|
|
/** |
|
254
|
|
|
* Get all payments with a specific reference |
|
255
|
|
|
* @param string $reference |
|
256
|
|
|
* @return \Illuminate\Database\Eloquent\Collection |
|
257
|
|
|
*/ |
|
258
|
|
|
public function getPaymentsByReference($reference) |
|
259
|
|
|
{ |
|
260
|
|
|
return $this->model->where('reference', $reference)->get(); |
|
|
|
|
|
|
261
|
|
|
} |
|
262
|
|
|
|
|
263
|
|
|
/** |
|
264
|
|
|
* @param string $referencePrefix |
|
265
|
|
|
* @return \Illuminate\Database\Eloquent\Collection |
|
266
|
|
|
*/ |
|
267
|
|
|
public function getEquipmentFeePayments($referencePrefix) |
|
268
|
|
|
{ |
|
269
|
|
|
return $this->model->where('reason', 'equipment-fee')->get()->filter(function($payment) use($referencePrefix) { |
|
|
|
|
|
|
270
|
|
|
return strpos($payment->reference, ':' . $referencePrefix) !== false; |
|
271
|
|
|
}); |
|
272
|
|
|
} |
|
273
|
|
|
|
|
274
|
|
|
|
|
275
|
|
|
/** |
|
276
|
|
|
* Return a paginated list of balance affecting payment for a user |
|
277
|
|
|
* @param $userId |
|
278
|
|
|
* @return \Illuminate\Database\Eloquent\Collection |
|
279
|
|
|
*/ |
|
280
|
|
View Code Duplication |
public function getBalancePaymentsPaginated($userId) |
|
|
|
|
|
|
281
|
|
|
{ |
|
282
|
|
|
return $this->model->where('user_id', $userId) |
|
|
|
|
|
|
283
|
|
|
->whereRaw('(source = ? or reason = ?) and (status = ? or status = ? or status = ?)', ['balance', 'balance', 'paid', 'pending', 'withdrawn']) |
|
284
|
|
|
->orderBy('created_at', 'desc') |
|
285
|
|
|
->simplePaginate($this->perPage); |
|
286
|
|
|
} |
|
287
|
|
|
|
|
288
|
|
|
|
|
289
|
|
|
/** |
|
290
|
|
|
* Return a collection of payments specifically for storage boxes |
|
291
|
|
|
* @param integer $userId |
|
292
|
|
|
* @return mixed |
|
293
|
|
|
*/ |
|
294
|
|
View Code Duplication |
public function getStorageBoxPayments($userId) |
|
|
|
|
|
|
295
|
|
|
{ |
|
296
|
|
|
return $this->model->where('user_id', $userId) |
|
|
|
|
|
|
297
|
|
|
->whereRaw('reason = ? and (status = ? or status = ? or status = ?)', ['storage-box', 'paid', 'pending', 'withdrawn']) |
|
298
|
|
|
->orderBy('created_at', 'desc') |
|
299
|
|
|
->get(); |
|
300
|
|
|
} |
|
301
|
|
|
|
|
302
|
|
|
public function dateFilter($startDate, $endDate) |
|
303
|
|
|
{ |
|
304
|
|
|
$this->startDate = $startDate; |
|
305
|
|
|
$this->endDate = $endDate; |
|
306
|
|
|
} |
|
307
|
|
|
|
|
308
|
|
|
private function hasDateFilter() |
|
309
|
|
|
{ |
|
310
|
|
|
return ($this->startDate && $this->endDate); |
|
311
|
|
|
} |
|
312
|
|
|
|
|
313
|
|
|
/** |
|
314
|
|
|
* Delete a record |
|
315
|
|
|
* @param $recordId |
|
316
|
|
|
* @return bool|null |
|
317
|
|
|
* @throws \Exception |
|
318
|
|
|
*/ |
|
319
|
|
|
public function delete($recordId) |
|
320
|
|
|
{ |
|
321
|
|
|
$payment = $this->getById($recordId); |
|
322
|
|
|
|
|
323
|
|
|
$state = $payment->delete(); |
|
324
|
|
|
|
|
325
|
|
|
//Fire an event, allows the balance to get updated |
|
326
|
|
|
\Event::fire('payment.delete', array($payment->user_id, $payment->source, $payment->reason, $payment->id)); |
|
327
|
|
|
|
|
328
|
|
|
return $state; |
|
329
|
|
|
} |
|
330
|
|
|
|
|
331
|
|
|
public function canDelete($recordId) |
|
|
|
|
|
|
332
|
|
|
{ |
|
333
|
|
|
throw new NotImplementedException(); |
|
334
|
|
|
} |
|
335
|
|
|
|
|
336
|
|
|
/** |
|
337
|
|
|
* Used for the getPaginated and getTotalAmount method |
|
338
|
|
|
* @param $reasonFilter |
|
339
|
|
|
*/ |
|
340
|
|
|
public function reasonFilter($reasonFilter) |
|
341
|
|
|
{ |
|
342
|
|
|
$this->reason = $reasonFilter; |
|
343
|
|
|
} |
|
344
|
|
|
|
|
345
|
|
|
private function hasReasonFilter() |
|
346
|
|
|
{ |
|
347
|
|
|
return ! is_null($this->reason); |
|
348
|
|
|
} |
|
349
|
|
|
|
|
350
|
|
|
/** |
|
351
|
|
|
* Used for the getPaginated and getTotalAmount method |
|
352
|
|
|
* @param string $sourceFilter |
|
353
|
|
|
*/ |
|
354
|
|
|
public function sourceFilter($sourceFilter) |
|
355
|
|
|
{ |
|
356
|
|
|
$this->source = $sourceFilter; |
|
357
|
|
|
} |
|
358
|
|
|
|
|
359
|
|
|
|
|
360
|
|
|
private function hasSourceFilter() |
|
361
|
|
|
{ |
|
362
|
|
|
return ! is_null($this->source); |
|
363
|
|
|
} |
|
364
|
|
|
|
|
365
|
|
|
/** |
|
366
|
|
|
* Used for the getPaginated and getTotalAmount method |
|
367
|
|
|
*/ |
|
368
|
|
|
public function resetFilters() |
|
369
|
|
|
{ |
|
370
|
|
|
$this->source = null; |
|
371
|
|
|
$this->reason = null; |
|
372
|
|
|
$this->memberId = null; |
|
373
|
|
|
$this->startDate = null; |
|
374
|
|
|
$this->endDate = null; |
|
375
|
|
|
} |
|
376
|
|
|
|
|
377
|
|
|
/** |
|
378
|
|
|
* Fetch a payment record using the id provided by the payment provider |
|
379
|
|
|
* |
|
380
|
|
|
* @param $sourceId |
|
381
|
|
|
* @return Payment |
|
382
|
|
|
*/ |
|
383
|
|
|
public function getPaymentBySourceId($sourceId) |
|
384
|
|
|
{ |
|
385
|
|
|
return $this->model->where('source_id', $sourceId)->first(); |
|
|
|
|
|
|
386
|
|
|
} |
|
387
|
|
|
|
|
388
|
|
|
/** |
|
389
|
|
|
* Record a balance payment transfer between two users |
|
390
|
|
|
* |
|
391
|
|
|
* @param integer $sourceUserId |
|
392
|
|
|
* @param integer $targetUserId |
|
393
|
|
|
* @param double $amount |
|
394
|
|
|
*/ |
|
395
|
|
|
public function recordBalanceTransfer($sourceUserId, $targetUserId, $amount) |
|
396
|
|
|
{ |
|
397
|
|
|
$paymentId = $this->recordPayment('transfer', $sourceUserId, 'balance', '', $amount, 'paid', 0, $targetUserId); |
|
398
|
|
|
$this->recordPayment('balance', $targetUserId, 'transfer', $paymentId, $amount, 'paid', 0, $sourceUserId); |
|
399
|
|
|
|
|
400
|
|
|
//Both of these events aren't needed adn the balance payment fires its own |
|
401
|
|
|
// but for the sake of neatness they are here |
|
402
|
|
|
event(new MemberBalanceChanged($sourceUserId)); |
|
403
|
|
|
event(new MemberBalanceChanged($targetUserId)); |
|
404
|
|
|
} |
|
405
|
|
|
} |
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.