1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace App\PaymentProvider; |
4
|
|
|
|
5
|
|
|
use App\Purchase; |
6
|
|
|
use App\Exceptions\PaymentProviderException; |
7
|
|
|
use App\Mail\TicketsPaid; |
8
|
|
|
use Illuminate\Support\Facades\Log; |
9
|
|
|
use Illuminate\Support\Facades\Mail; |
10
|
|
|
use Mollie\Api\MollieApiClient; |
11
|
|
|
|
12
|
|
|
/** |
13
|
|
|
* Class to process requests to Mollie |
14
|
|
|
* |
15
|
|
|
* Find docs @ https://github.com/mollie/mollie-api-php |
16
|
|
|
*/ |
17
|
|
|
class Mollie |
18
|
|
|
{ |
19
|
|
|
/** |
20
|
|
|
* API-Object; initialized by constructor |
21
|
|
|
*/ |
22
|
|
|
protected $apiClient; |
23
|
|
|
|
24
|
|
|
/** |
25
|
|
|
* |
26
|
|
|
* @param string $configKey Configuration key for this exact project |
27
|
|
|
* @return void |
28
|
|
|
*/ |
29
|
|
|
public function __construct($configKey) |
30
|
|
|
{ |
31
|
|
|
$this->apiClient = new MollieApiClient(); |
32
|
|
|
$this->apiClient->setApiKey($configKey); |
33
|
|
|
} |
34
|
|
|
|
35
|
|
|
/** |
36
|
|
|
* Generates for a given Purchase a payment request at Mollie and |
37
|
|
|
* returns a payment url where the customer can pay the purchase |
38
|
|
|
* |
39
|
|
|
* For more details on the payment-create-process take a look at |
40
|
|
|
* https://github.com/mollie/mollie-api-php/blob/master/examples/payments/create-payment.php |
41
|
|
|
* |
42
|
|
|
* @param Purchase $purchase the customer's purchase |
43
|
|
|
* @return string PaymentUrl to Mollie for the given purchase |
44
|
|
|
*/ |
45
|
|
|
public function getPaymentUrl(Purchase $purchase) |
46
|
|
|
{ |
47
|
|
|
// Format the purchase's total to have 2 decimals being seperated by a dot and without |
48
|
|
|
// a thousand-seperator. Format = 123456.78 |
49
|
|
|
$amountFormatted = number_format($purchase->total(), 2, '.', ''); |
50
|
|
|
|
51
|
|
|
$firstEvent = $purchase->events()->pop(); |
52
|
|
|
|
53
|
|
|
try { |
54
|
|
|
$payment = $this->apiClient->payments->create([ |
55
|
|
|
'amount' => [ |
56
|
|
|
'currency' => 'EUR', |
57
|
|
|
'value' => $amountFormatted |
58
|
|
|
], |
59
|
|
|
'description' => $firstEvent->project->name . ' | ' . $firstEvent->second_name, |
60
|
|
|
'redirectUrl' => route('ticket.purchase', $purchase), |
61
|
|
|
'webhookUrl' => route('ts.payment.mollie.webhook'), |
62
|
|
|
'metadata' => [ |
63
|
|
|
'purchase_id' => $purchase->id |
64
|
|
|
] |
65
|
|
|
]); |
66
|
|
|
|
67
|
|
|
// Store the payment-reference from Mollie to the associated purchase |
68
|
|
|
$purchase->payment_id = $payment->id; |
69
|
|
|
$purchase->save(); |
70
|
|
|
|
71
|
|
|
} catch( \Mollie\Api\Exceptions\ApiException $e ) { |
72
|
|
|
throw new PaymentProviderException( $e ); |
73
|
|
|
} |
74
|
|
|
|
75
|
|
|
// Set a reference to the customer, depending if a user object exists |
76
|
|
|
$customerReference = $purchase->customer ? $purchase->customer->id : $purchase->customer_name; |
77
|
|
|
Log::info('[Purchase#' . $purchase->id . '] Created Payment#' . $payment->id . ' @Mollie and sending customer#' . $customerReference . ' to payment provider'); |
78
|
|
|
|
79
|
|
|
return $payment->getCheckoutUrl(); |
80
|
|
|
} |
81
|
|
|
|
82
|
|
|
/** |
83
|
|
|
* This function gets called by the mollie webhook |
84
|
|
|
* |
85
|
|
|
* For more details on the payment-create-process take a look at |
86
|
|
|
* https://github.com/mollie/mollie-api-php/blob/master/examples/payments/webhook.php |
87
|
|
|
* |
88
|
|
|
* @param string $paymentId |
89
|
|
|
* @return void |
90
|
|
|
* @throws PaymentProviderException |
91
|
|
|
*/ |
92
|
|
|
public function updatePayment(string $paymentId) |
93
|
|
|
{ |
94
|
|
|
try { |
95
|
|
|
// Retrieve the payment's current state and its associated purchase |
96
|
|
|
$payment = $this->apiClient->payments->get($paymentId); |
97
|
|
|
$purchaseId = $payment->metadata->purchase_id; |
98
|
|
|
$purchase = Purchase::find($purchaseId); |
99
|
|
|
|
100
|
|
|
if( !$purchase ) { |
101
|
|
|
throw new PaymentProviderException('Payment-Id "' . $paymentId . '" has no matching purchase!'); |
102
|
|
|
} |
103
|
|
|
|
104
|
|
|
if( $payment->isPaid() && !$payment->hasRefunds() && !$payment->hasChargebacks() ) { |
105
|
|
|
/* |
106
|
|
|
* The payment is paid and isn't refunded or charged back. |
107
|
|
|
*/ |
108
|
|
|
// Only send an email with the tickets on the change of state to "paid" |
109
|
|
|
if($purchase->state != 'paid') { |
110
|
|
|
Log::info('[Purchase#' . $purchase->id . '] Sending Ticket-Mail.'); |
111
|
|
|
Mail::to($purchase->customer)->send(new TicketsPaid($purchase)); |
112
|
|
|
} |
113
|
|
|
} elseif( $payment->isOpen() ) { |
114
|
|
|
/* |
115
|
|
|
* The payment is open. |
116
|
|
|
*/ |
117
|
|
|
} elseif( $payment->isPending() ) { |
118
|
|
|
/* |
119
|
|
|
* The payment is pending. |
120
|
|
|
*/ |
121
|
|
|
} elseif( $payment->isFailed() ) { |
122
|
|
|
/* |
123
|
|
|
* The payment has failed. |
124
|
|
|
*/ |
125
|
|
|
$purchase->deleteWithAllData(); |
126
|
|
|
} elseif( $payment->isExpired() ) { |
127
|
|
|
/* |
128
|
|
|
* The payment is expired. |
129
|
|
|
*/ |
130
|
|
|
$purchase->deleteWithAllData(); |
131
|
|
|
} elseif( $payment->isCanceled() ) { |
132
|
|
|
/* |
133
|
|
|
* The payment has been canceled. |
134
|
|
|
*/ |
135
|
|
|
$purchase->deleteWithAllData(); |
136
|
|
|
} elseif( $payment->hasRefunds() ) { |
137
|
|
|
/* |
138
|
|
|
* The payment has been (partially) refunded. |
139
|
|
|
* The status of the payment is still "paid" |
140
|
|
|
*/ |
141
|
|
|
} elseif( $payment->hasChargebacks() ) { |
142
|
|
|
/* |
143
|
|
|
* The payment has been (partially) charged back. |
144
|
|
|
* The status of the payment is still "paid" |
145
|
|
|
*/ |
146
|
|
|
} |
147
|
|
|
|
148
|
|
|
// Update the purchase's state |
149
|
|
|
$purchase->state = $payment->status; |
150
|
|
|
$purchase->state_updated = new \DateTime(); |
151
|
|
|
$purchase->save(); |
152
|
|
|
|
153
|
|
|
} catch (\Mollie\Api\Exceptions\ApiException $e) { |
154
|
|
|
throw new PaymentProviderException( $e ); |
155
|
|
|
} |
156
|
|
|
} |
157
|
|
|
|
158
|
|
|
} |
159
|
|
|
|