Changeset View
Changeset View
Standalone View
Standalone View
src/app/Providers/Payment/Mollie.php
Show First 20 Lines • Show All 89 Lines • ▼ Show 20 Lines | public function createMandate(Wallet $wallet, array $payment): ?array | ||||
if ($response->mandateId) { | if ($response->mandateId) { | ||||
$wallet->setSetting('mollie_mandate_id', $response->mandateId); | $wallet->setSetting('mollie_mandate_id', $response->mandateId); | ||||
} | } | ||||
// Store the payment reference in database | // Store the payment reference in database | ||||
$payment['status'] = $response->status; | $payment['status'] = $response->status; | ||||
$payment['id'] = $response->id; | $payment['id'] = $response->id; | ||||
$payment['type'] = self::TYPE_MANDATE; | $payment['type'] = Payment::TYPE_MANDATE; | ||||
$this->storePayment($payment, $wallet->id); | $this->storePayment($payment, $wallet->id); | ||||
return [ | return [ | ||||
'id' => $response->id, | 'id' => $response->id, | ||||
'redirectUrl' => $response->getCheckoutUrl(), | 'redirectUrl' => $response->getCheckoutUrl(), | ||||
]; | ]; | ||||
} | } | ||||
▲ Show 20 Lines • Show All 74 Lines • ▼ Show 20 Lines | class Mollie extends \App\Providers\PaymentProvider | ||||
* - methodId: Payment method | * - methodId: Payment method | ||||
* | * | ||||
* @return array Provider payment data: | * @return array Provider payment data: | ||||
* - id: Operation identifier | * - id: Operation identifier | ||||
* - redirectUrl: the location to redirect to | * - redirectUrl: the location to redirect to | ||||
*/ | */ | ||||
public function payment(Wallet $wallet, array $payment): ?array | public function payment(Wallet $wallet, array $payment): ?array | ||||
{ | { | ||||
if ($payment['type'] == self::TYPE_RECURRING) { | if ($payment['type'] == Payment::TYPE_RECURRING) { | ||||
return $this->paymentRecurring($wallet, $payment); | return $this->paymentRecurring($wallet, $payment); | ||||
} | } | ||||
// Register the user in Mollie, if not yet done | // Register the user in Mollie, if not yet done | ||||
$customer_id = self::mollieCustomerId($wallet, true); | $customer_id = self::mollieCustomerId($wallet, true); | ||||
$amount = $this->exchange($payment['amount'], $wallet->currency, $payment['currency']); | $amount = $this->exchange($payment['amount'], $wallet->currency, $payment['currency']); | ||||
$payment['currency_amount'] = $amount; | $payment['currency_amount'] = $amount; | ||||
Show All 29 Lines | public function payment(Wallet $wallet, array $payment): ?array | ||||
$this->storePayment($payment, $wallet->id); | $this->storePayment($payment, $wallet->id); | ||||
return [ | return [ | ||||
'id' => $payment['id'], | 'id' => $payment['id'], | ||||
'redirectUrl' => $response->getCheckoutUrl(), | 'redirectUrl' => $response->getCheckoutUrl(), | ||||
]; | ]; | ||||
} | } | ||||
/** | /** | ||||
* Cancel a pending payment. | * Cancel a pending payment. | ||||
* | * | ||||
* @param \App\Wallet $wallet The wallet | * @param \App\Wallet $wallet The wallet | ||||
* @param string $paymentId Payment Id | * @param string $paymentId Payment Id | ||||
* | * | ||||
* @return bool True on success, False on failure | * @return bool True on success, False on failure | ||||
*/ | */ | ||||
public function cancel(Wallet $wallet, $paymentId): bool | public function cancel(Wallet $wallet, $paymentId): bool | ||||
{ | { | ||||
$response = mollie()->payments()->delete($paymentId); | $response = mollie()->payments()->delete($paymentId); | ||||
$db_payment = Payment::find($paymentId); | $db_payment = Payment::find($paymentId); | ||||
$db_payment->status = $response->status; | $db_payment->status = $response->status; | ||||
$db_payment->save(); | $db_payment->save(); | ||||
return true; | return true; | ||||
} | } | ||||
/** | /** | ||||
* Create a new automatic payment operation. | * Create a new automatic payment operation. | ||||
* | * | ||||
* @param \App\Wallet $wallet The wallet | * @param \App\Wallet $wallet The wallet | ||||
* @param array $payment Payment data (see self::payment()) | * @param array $payment Payment data (see self::payment()) | ||||
* | * | ||||
* @return array Provider payment/session data: | * @return array Provider payment/session data: | ||||
* - id: Operation identifier | * - id: Operation identifier | ||||
▲ Show 20 Lines • Show All 93 Lines • ▼ Show 20 Lines | public function webhook(): int | ||||
// Get the payment details from Mollie | // Get the payment details from Mollie | ||||
// TODO: Consider https://github.com/mollie/mollie-api-php/issues/502 when it's fixed | // TODO: Consider https://github.com/mollie/mollie-api-php/issues/502 when it's fixed | ||||
$mollie_payment = mollie()->payments()->get($payment_id); | $mollie_payment = mollie()->payments()->get($payment_id); | ||||
$refunds = []; | $refunds = []; | ||||
if ($mollie_payment->isPaid()) { | if ($mollie_payment->isPaid()) { | ||||
// The payment is paid. Update the balance, and notify the user | // The payment is paid. Update the balance, and notify the user | ||||
if ($payment->status != self::STATUS_PAID && $payment->amount > 0) { | if ($payment->status != Payment::STATUS_PAID && $payment->amount > 0) { | ||||
$credit = true; | $credit = true; | ||||
$notify = $payment->type == self::TYPE_RECURRING; | $notify = $payment->type == Payment::TYPE_RECURRING; | ||||
} | } | ||||
// The payment has been (partially) refunded. | // The payment has been (partially) refunded. | ||||
// Let's process refunds with status "refunded". | // Let's process refunds with status "refunded". | ||||
if ($mollie_payment->hasRefunds()) { | if ($mollie_payment->hasRefunds()) { | ||||
foreach ($mollie_payment->refunds() as $refund) { | foreach ($mollie_payment->refunds() as $refund) { | ||||
if ($refund->isTransferred() && $refund->amount->value) { | if ($refund->isTransferred() && $refund->amount->value) { | ||||
$refunds[] = [ | $refunds[] = [ | ||||
'id' => $refund->id, | 'id' => $refund->id, | ||||
'description' => $refund->description, | 'description' => $refund->description, | ||||
'amount' => round(floatval($refund->amount->value) * 100), | 'amount' => round(floatval($refund->amount->value) * 100), | ||||
'type' => self::TYPE_REFUND, | 'type' => Payment::TYPE_REFUND, | ||||
'currency' => $refund->amount->currency | 'currency' => $refund->amount->currency | ||||
]; | ]; | ||||
} | } | ||||
} | } | ||||
} | } | ||||
// The payment has been (partially) charged back. | // The payment has been (partially) charged back. | ||||
// Let's process chargebacks (they have no states as refunds) | // Let's process chargebacks (they have no states as refunds) | ||||
if ($mollie_payment->hasChargebacks()) { | if ($mollie_payment->hasChargebacks()) { | ||||
foreach ($mollie_payment->chargebacks() as $chargeback) { | foreach ($mollie_payment->chargebacks() as $chargeback) { | ||||
if ($chargeback->amount->value) { | if ($chargeback->amount->value) { | ||||
$refunds[] = [ | $refunds[] = [ | ||||
'id' => $chargeback->id, | 'id' => $chargeback->id, | ||||
'amount' => round(floatval($chargeback->amount->value) * 100), | 'amount' => round(floatval($chargeback->amount->value) * 100), | ||||
'type' => self::TYPE_CHARGEBACK, | 'type' => Payment::TYPE_CHARGEBACK, | ||||
'currency' => $chargeback->amount->currency | 'currency' => $chargeback->amount->currency | ||||
]; | ]; | ||||
} | } | ||||
} | } | ||||
} | } | ||||
// In case there were multiple auto-payment setup requests (e.g. caused by a double | // In case there were multiple auto-payment setup requests (e.g. caused by a double | ||||
// form submission) we end up with multiple payment records and mollie_mandate_id | // form submission) we end up with multiple payment records and mollie_mandate_id | ||||
// pointing to the one from the last payment not the successful one. | // pointing to the one from the last payment not the successful one. | ||||
// We make sure to use mandate id from the successful "first" payment. | // We make sure to use mandate id from the successful "first" payment. | ||||
if ( | if ( | ||||
$payment->type == self::TYPE_MANDATE | $payment->type == Payment::TYPE_MANDATE | ||||
&& $mollie_payment->mandateId | && $mollie_payment->mandateId | ||||
&& $mollie_payment->sequenceType == Types\SequenceType::SEQUENCETYPE_FIRST | && $mollie_payment->sequenceType == Types\SequenceType::SEQUENCETYPE_FIRST | ||||
) { | ) { | ||||
$payment->wallet->setSetting('mollie_mandate_id', $mollie_payment->mandateId); | $payment->wallet->setSetting('mollie_mandate_id', $mollie_payment->mandateId); | ||||
} | } | ||||
} elseif ($mollie_payment->isFailed()) { | } elseif ($mollie_payment->isFailed()) { | ||||
// Note: I didn't find a way to get any description of the problem with a payment | // Note: I didn't find a way to get any description of the problem with a payment | ||||
\Log::info(sprintf('Mollie payment failed (%s)', $payment->id)); | \Log::info(sprintf('Mollie payment failed (%s)', $payment->id)); | ||||
// Disable the mandate | // Disable the mandate | ||||
if ($payment->type == self::TYPE_RECURRING) { | if ($payment->type == Payment::TYPE_RECURRING) { | ||||
$notify = true; | $notify = true; | ||||
$payment->wallet->setSetting('mandate_disabled', 1); | $payment->wallet->setSetting('mandate_disabled', 1); | ||||
} | } | ||||
} | } | ||||
DB::beginTransaction(); | DB::beginTransaction(); | ||||
// This is a sanity check, just in case the payment provider api | // This is a sanity check, just in case the payment provider api | ||||
// sent us open -> paid -> open -> paid. So, we lock the payment after | // sent us open -> paid -> open -> paid. So, we lock the payment after | ||||
// recivied a "final" state. | // recivied a "final" state. | ||||
$pending_states = [self::STATUS_OPEN, self::STATUS_PENDING, self::STATUS_AUTHORIZED]; | $pending_states = [Payment::STATUS_OPEN, Payment::STATUS_PENDING, Payment::STATUS_AUTHORIZED]; | ||||
if (in_array($payment->status, $pending_states)) { | if (in_array($payment->status, $pending_states)) { | ||||
$payment->status = $mollie_payment->status; | $payment->status = $mollie_payment->status; | ||||
$payment->save(); | $payment->save(); | ||||
} | } | ||||
if (!empty($credit)) { | if (!empty($credit)) { | ||||
self::creditPayment($payment, $mollie_payment); | self::creditPayment($payment, $mollie_payment); | ||||
} | } | ||||
▲ Show 20 Lines • Show All 71 Lines • ▼ Show 20 Lines | class Mollie extends \App\Providers\PaymentProvider | ||||
/** | /** | ||||
* Apply the successful payment's pecunia to the wallet | * Apply the successful payment's pecunia to the wallet | ||||
*/ | */ | ||||
protected static function creditPayment($payment, $mollie_payment) | protected static function creditPayment($payment, $mollie_payment) | ||||
{ | { | ||||
// Extract the payment method for transaction description | // Extract the payment method for transaction description | ||||
$method = self::paymentMethod($mollie_payment, 'Mollie'); | $method = self::paymentMethod($mollie_payment, 'Mollie'); | ||||
// TODO: Localization? | $payment->credit($method); | ||||
$description = $payment->type == self::TYPE_RECURRING ? 'Auto-payment' : 'Payment'; | |||||
$description .= " transaction {$payment->id} using {$method}"; | |||||
$payment->wallet->credit($payment, $description); | |||||
// Unlock the disabled auto-payment mandate | |||||
if ($payment->wallet->balance >= 0) { | |||||
$payment->wallet->setSetting('mandate_disabled', null); | |||||
} | |||||
} | } | ||||
/** | /** | ||||
* Extract payment method description from Mollie payment/mandate details | * Extract payment method description from Mollie payment/mandate details | ||||
*/ | */ | ||||
protected static function paymentMethod($object, $default = ''): string | protected static function paymentMethod($object, $default = ''): string | ||||
{ | { | ||||
$details = $object->details; | $details = $object->details; | ||||
▲ Show 20 Lines • Show All 107 Lines • Show Last 20 Lines |