Skip to content
This repository has been archived by the owner on Feb 2, 2022. It is now read-only.

Commit

Permalink
Merge pull request #296 from sandervanhooft/handle_invalid_mandate
Browse files Browse the repository at this point in the history
handle invalid mandate
  • Loading branch information
sandervanhooft committed Feb 1, 2021
2 parents 86bf80b + b6a2d97 commit 2e49186
Show file tree
Hide file tree
Showing 5 changed files with 126 additions and 2 deletions.
4 changes: 4 additions & 0 deletions README.md
Expand Up @@ -568,6 +568,10 @@ An Invoice is available on the Order. Access it using `$event->order->invoice()`
#### `OrderPaymentFailed` event
The payment for an order has failed.

#### `OrderPaymentFailedDueToInvalidMandate` event
The payment for an order has failed due to an invalid payment mandate. This happens for example when the customer's
credit card has expired.

#### `OrderPaymentPaid` event
The payment for an order was successful.

Expand Down
2 changes: 1 addition & 1 deletion src/CashierServiceProvider.php
Expand Up @@ -17,7 +17,7 @@ class CashierServiceProvider extends ServiceProvider
{
use RegistersMollieInteractions;

const PACKAGE_VERSION = '1.15.0';
const PACKAGE_VERSION = '1.16.0';

/**
* Bootstrap the application services.
Expand Down
27 changes: 27 additions & 0 deletions src/Events/OrderPaymentFailedDueToInvalidMandate.php
@@ -0,0 +1,27 @@
<?php

namespace Laravel\Cashier\Events;

use Illuminate\Queue\SerializesModels;

class OrderPaymentFailedDueToInvalidMandate
{
use SerializesModels;

/**
* The failed order.
*
* @var \Laravel\Cashier\Order\Order
*/
public $order;

/**
* Creates a new OrderPaymentFailed event.
*
* @param $order
*/
public function __construct($order)
{
$this->order = $order;
}
}
42 changes: 41 additions & 1 deletion src/Order/Order.php
Expand Up @@ -12,6 +12,7 @@
use Laravel\Cashier\Events\BalanceTurnedStale;
use Laravel\Cashier\Events\OrderCreated;
use Laravel\Cashier\Events\OrderPaymentFailed;
use Laravel\Cashier\Events\OrderPaymentFailedDueToInvalidMandate;
use Laravel\Cashier\Events\OrderPaymentPaid;
use Laravel\Cashier\Events\OrderProcessed;
use Laravel\Cashier\Exceptions\InvalidMandateException;
Expand Down Expand Up @@ -159,7 +160,12 @@ public function processPayment()
$this->total_due = $total->subtract($creditUsed)->getAmount();
}

$minimumPaymentAmount = $this->ensureValidMandateAndMinimumPaymentAmountWhenTotalDuePositive();
try {
$minimumPaymentAmount = $this->ensureValidMandateAndMinimumPaymentAmountWhenTotalDuePositive();
} catch (InvalidMandateException $e) {
return $this->handlePaymentFailedDueToInvalidMandate();
}

$totalDue = money($this->total_due, $this->currency);

switch (true) {
Expand Down Expand Up @@ -403,6 +409,40 @@ public function handlePaymentFailed()
});
}

/**
* Handles a failed payment for the Order due to an invalid Mollie payment Mandate.
* Restores any credit used to the customer's balance and resets the credits applied to the Order.
* Invokes handlePaymentFailed() on each related OrderItem.
*
* @return $this
*/
public function handlePaymentFailedDueToInvalidMandate()
{
return DB::transaction(function () {
if ($this->creditApplied()) {
$this->owner->addCredit($this->getCreditUsed());
}

$this->update([
'mollie_payment_id' => null,
'mollie_payment_status' => 'failed',
'balance_before' => 0,
'credit_used' => 0,
'processed_at' => now(),
]);

Event::dispatch(new OrderPaymentFailedDueToInvalidMandate($this));

$this->items->each(function (OrderItem $item) {
$item->handlePaymentFailed();
});

$this->owner->clearMollieMandate();

return $this;
});
}

/**
* Handles a paid payment for this order.
* Invokes handlePaymentPaid() on each related OrderItem.
Expand Down
53 changes: 53 additions & 0 deletions tests/Order/OrderTest.php
Expand Up @@ -6,6 +6,7 @@
use Illuminate\Support\Facades\Event;
use Laravel\Cashier\Events\BalanceTurnedStale;
use Laravel\Cashier\Events\OrderCreated;
use Laravel\Cashier\Events\OrderPaymentFailedDueToInvalidMandate;
use Laravel\Cashier\Events\OrderProcessed;
use Laravel\Cashier\Mollie\Contracts\CreateMolliePayment;
use Laravel\Cashier\Mollie\Contracts\GetMollieCustomer;
Expand Down Expand Up @@ -360,6 +361,58 @@ public function createsAMolliePaymentIfTotalDueIsLargerThanMolliesMinimum()
$this->assertDispatchedOrderProcessed($order);
}

/** @test */
public function handlesAnInvalidMandateWhenProcessingThePayment()
{
Event::fake();

$this->mock(GetMollieMandate::class, function ($mock) {
$mandate = new Mandate(new MollieApiClient);
$mandate->id = 'mdt_unique_mandate_id';
$mandate->status = 'invalid';
$mandate->method = 'directdebit';

return $mock->shouldReceive('execute')
->with('cst_unique_customer_id', 'mdt_unique_mandate_id')
->once()
->andReturn($mandate);
});

$this->mock(GetMollieCustomer::class, function ($mock) {
$customer = new Customer(new MollieApiClient);
$customer->id = 'cst_unique_customer_id';

return $mock->shouldReceive('execute')
->with('cst_unique_customer_id')
->once()
->andReturn($customer);
});

$user = $this->getMandatedUser(true, [
'id' => 2,
'mollie_mandate_id' => 'mdt_unique_mandate_id',
'mollie_customer_id' => 'cst_unique_customer_id',
]);

$order = $user->orders()->save(factory(Order::class)->make([
'total' => 1025,
'total_due' => 1025,
'currency' => 'EUR',
]));
$this->assertFalse($order->isProcessed());
$this->assertFalse($user->hasCredit('EUR'));

$order->processPayment();

$this->assertTrue($order->isProcessed());
$this->assertNull($order->mollie_payment_id);
$this->assertEquals('failed', $order->mollie_payment_status);

Event::assertDispatched(OrderPaymentFailedDueToInvalidMandate::class, function ($event) use ($order) {
return $order->is($event->order);
});
}

/** @test */
public function storesOwnerCreditIfTotalIsPositiveAndSmallerThanMolliesMinimum()
{
Expand Down

0 comments on commit 2e49186

Please sign in to comment.