Page MenuHomePhorge

D1900.1775542678.diff
No OneTemporary

Authored By
Unknown
Size
15 KB
Referenced Files
None
Subscribers
None

D1900.1775542678.diff

diff --git a/src/app/Http/Controllers/API/V4/PaymentsController.php b/src/app/Http/Controllers/API/V4/PaymentsController.php
--- a/src/app/Http/Controllers/API/V4/PaymentsController.php
+++ b/src/app/Http/Controllers/API/V4/PaymentsController.php
@@ -21,7 +21,7 @@
$user = Auth::guard()->user();
// TODO: Wallet selection
- $wallet = $user->wallets->first();
+ $wallet = $user->wallets()->first();
$mandate = self::walletMandate($wallet);
@@ -40,7 +40,7 @@
$current_user = Auth::guard()->user();
// TODO: Wallet selection
- $wallet = $current_user->wallets->first();
+ $wallet = $current_user->wallets()->first();
$rules = [
'amount' => 'required|numeric',
@@ -59,7 +59,15 @@
$amount = (int) ($request->amount * 100);
// Validate the minimum value
- if ($amount < PaymentProvider::MIN_AMOUNT) {
+ // It has to be at least minimum payment amount and must cover current debt
+ if (
+ $wallet->balance < 0
+ && $wallet->balance * -1 > PaymentProvider::MIN_AMOUNT
+ && $wallet->balance + $amount < 0
+ ) {
+ $errors = ['amount' => \trans('validation.minamountdebt')];
+ return response()->json(['status' => 'error', 'errors' => $errors], 422);
+ } elseif ($amount < PaymentProvider::MIN_AMOUNT) {
$min = intval(PaymentProvider::MIN_AMOUNT / 100) . ' CHF';
$errors = ['amount' => \trans('validation.minamount', ['amount' => $min])];
return response()->json(['status' => 'error', 'errors' => $errors], 422);
@@ -72,10 +80,15 @@
$request = [
'currency' => 'CHF',
- 'amount' => $amount,
'description' => \config('app.name') . ' Auto-Payment Setup',
];
+ // Normally the auto-payment operation is 0, if the balance is below 0,
+ // we'll charge for the mandate amount
+ if ($wallet->balance < 0) {
+ $request['amount'] = $amount;
+ }
+
$provider = PaymentProvider::factory($wallet);
$result = $provider->createMandate($wallet, $request);
@@ -95,7 +108,7 @@
$user = Auth::guard()->user();
// TODO: Wallet selection
- $wallet = $user->wallets->first();
+ $wallet = $user->wallets()->first();
$provider = PaymentProvider::factory($wallet);
@@ -121,7 +134,7 @@
$current_user = Auth::guard()->user();
// TODO: Wallet selection
- $wallet = $current_user->wallets->first();
+ $wallet = $current_user->wallets()->first();
$rules = [
'amount' => 'required|numeric',
@@ -185,7 +198,7 @@
$current_user = Auth::guard()->user();
// TODO: Wallet selection
- $wallet = $current_user->wallets->first();
+ $wallet = $current_user->wallets()->first();
$rules = [
'amount' => 'required|numeric',
diff --git a/src/app/Providers/Payment/Mollie.php b/src/app/Providers/Payment/Mollie.php
--- a/src/app/Providers/Payment/Mollie.php
+++ b/src/app/Providers/Payment/Mollie.php
@@ -37,7 +37,7 @@
*
* @param \App\Wallet $wallet The wallet
* @param array $payment Payment data:
- * - amount: Value in cents
+ * - amount: Value in cents (optional)
* - currency: The operation currency
* - description: Operation desc.
*
@@ -50,10 +50,14 @@
// Register the user in Mollie, if not yet done
$customer_id = self::mollieCustomerId($wallet, true);
+ if (!isset($payment['amount'])) {
+ $payment['amount'] = 0;
+ }
+
$request = [
'amount' => [
'currency' => $payment['currency'],
- 'value' => '0.00',
+ 'value' => sprintf('%.2f', $payment['amount'] / 100),
],
'customerId' => $customer_id,
'sequenceType' => 'first',
@@ -71,6 +75,13 @@
$wallet->setSetting('mollie_mandate_id', $response->mandateId);
}
+ // Store the payment reference in database
+ $payment['status'] = $response->status;
+ $payment['id'] = $response->id;
+ $payment['type'] = self::TYPE_MANDATE;
+
+ $this->storePayment($payment, $wallet->id);
+
return [
'id' => $response->id,
'redirectUrl' => $response->getCheckoutUrl(),
diff --git a/src/app/Providers/Payment/Stripe.php b/src/app/Providers/Payment/Stripe.php
--- a/src/app/Providers/Payment/Stripe.php
+++ b/src/app/Providers/Payment/Stripe.php
@@ -56,7 +56,7 @@
*
* @param \App\Wallet $wallet The wallet
* @param array $payment Payment data:
- * - amount: Value in cents
+ * - amount: Value in cents (not used)
* - currency: The operation currency
* - description: Operation desc.
*
@@ -77,12 +77,14 @@
'mode' => 'setup',
];
+ // Note: Stripe does not allow to set amount for 'setup' operation
+ // We'll dispatch WalletCharge job when we receive a webhook request
+
$session = StripeAPI\Checkout\Session::create($request);
- $payment = [
- 'id' => $session->setup_intent,
- 'type' => self::TYPE_MANDATE,
- ];
+ $payment['amount'] = 0;
+ $payment['id'] = $session->setup_intent;
+ $payment['type'] = self::TYPE_MANDATE;
$this->storePayment($payment, $wallet->id);
@@ -355,6 +357,11 @@
if ($status == self::STATUS_PAID) {
$payment->wallet->setSetting('stripe_mandate_id', $intent->id);
+
+ // Update the balance, if it wasn't already
+ if ($payment->wallet->balance < 0 && $payment->status != self::STATUS_PAID) {
+ \App\Jobs\WalletCharge::dispatch($payment->wallet);
+ }
}
$payment->status = $status;
diff --git a/src/tests/Feature/Controller/PaymentsMollieTest.php b/src/tests/Feature/Controller/PaymentsMollieTest.php
--- a/src/tests/Feature/Controller/PaymentsMollieTest.php
+++ b/src/tests/Feature/Controller/PaymentsMollieTest.php
@@ -80,6 +80,7 @@
$response->assertStatus(401);
$user = $this->getTestUser('john@kolab.org');
+ $wallet = $user->wallets()->first();
// Test creating a mandate (invalid input)
$post = [];
@@ -104,7 +105,7 @@
$this->assertCount(1, $json['errors']);
$this->assertSame('The balance must be a number.', $json['errors']['balance'][0]);
- // Test creating a mandate (invalid input)
+ // Test creating a mandate (amount smaller than the minimum value)
$post = ['amount' => -100, 'balance' => 0];
$response = $this->actingAs($user)->post("api/v4/payments/mandate", $post);
$response->assertStatus(422);
@@ -116,6 +117,18 @@
$min = intval(PaymentProvider::MIN_AMOUNT / 100) . ' CHF';
$this->assertSame("Minimum amount for a single payment is {$min}.", $json['errors']['amount']);
+ // Test creating a mandate (negative balance, amount too small)
+ Wallet::where('id', $wallet->id)->update(['balance' => -2000]);
+ $post = ['amount' => PaymentProvider::MIN_AMOUNT / 100, 'balance' => 0];
+ $response = $this->actingAs($user)->post("api/v4/payments/mandate", $post);
+ $response->assertStatus(422);
+
+ $json = $response->json();
+
+ $this->assertSame('error', $json['status']);
+ $this->assertCount(1, $json['errors']);
+ $this->assertSame("The specified amount does not cover the balance on the account.", $json['errors']['amount']);
+
// Test creating a mandate (valid input)
$post = ['amount' => 20.10, 'balance' => 0];
$response = $this->actingAs($user)->post("api/v4/payments/mandate", $post);
@@ -126,6 +139,13 @@
$this->assertSame('success', $json['status']);
$this->assertRegExp('|^https://www.mollie.com|', $json['redirectUrl']);
+ // Assert the proper payment amount has been used
+ $payment = Payment::where('id', $json['id'])->first();
+ $this->assertSame(2010, $payment->amount);
+ $this->assertSame($wallet->id, $payment->wallet_id);
+ $this->assertSame("Kolab Now Auto-Payment Setup", $payment->description);
+ $this->assertSame(PaymentProvider::TYPE_MANDATE, $payment->type);
+
// Test fetching the mandate information
$response = $this->actingAs($user)->get("api/v4/payments/mandate");
$response->assertStatus(200);
@@ -456,12 +476,14 @@
$result = PaymentsController::topUpWallet($wallet);
$this->assertTrue($result);
- // Check that the payments table contains a new record with proper amount
- // There should be two records, one for the first payment and another for
- // the recurring payment
- $this->assertCount(1, $wallet->payments()->get());
- $payment = $wallet->payments()->first();
- $this->assertSame(2010, $payment->amount);
+ // Check that the payments table contains a new record with proper amount.
+ // There should be two records, one for the mandate payment and another for
+ // the top-up payment
+ $payments = $wallet->payments()->orderBy('amount')->get();
+ $this->assertCount(2, $payments);
+ $this->assertSame(0, $payments[0]->amount);
+ $this->assertSame(2010, $payments[1]->amount);
+ $payment = $payments[1];
// In mollie we don't have to wait for a webhook, the response to
// PaymentIntent already sets the status to 'paid', so we can test
@@ -488,7 +510,7 @@
$wallet->setSetting('mandate_disabled', 1);
$result = PaymentsController::topUpWallet($wallet);
$this->assertFalse($result);
- $this->assertCount(1, $wallet->payments()->get());
+ $this->assertCount(2, $wallet->payments()->get());
// Expect no payment if balance is ok
$wallet->setSetting('mandate_disabled', null);
@@ -496,7 +518,7 @@
$wallet->save();
$result = PaymentsController::topUpWallet($wallet);
$this->assertFalse($result);
- $this->assertCount(1, $wallet->payments()->get());
+ $this->assertCount(2, $wallet->payments()->get());
// Expect no payment if the top-up amount is not enough
$wallet->setSetting('mandate_disabled', null);
@@ -504,7 +526,7 @@
$wallet->save();
$result = PaymentsController::topUpWallet($wallet);
$this->assertFalse($result);
- $this->assertCount(1, $wallet->payments()->get());
+ $this->assertCount(2, $wallet->payments()->get());
Bus::assertDispatchedTimes(\App\Jobs\PaymentMandateDisabledEmail::class, 1);
Bus::assertDispatched(\App\Jobs\PaymentMandateDisabledEmail::class, function ($job) use ($wallet) {
@@ -518,7 +540,7 @@
$wallet->save();
$result = PaymentsController::topUpWallet($wallet);
$this->assertFalse($result);
- $this->assertCount(1, $wallet->payments()->get());
+ $this->assertCount(2, $wallet->payments()->get());
Bus::assertDispatchedTimes(\App\Jobs\PaymentMandateDisabledEmail::class, 1);
diff --git a/src/tests/Feature/Controller/PaymentsStripeTest.php b/src/tests/Feature/Controller/PaymentsStripeTest.php
--- a/src/tests/Feature/Controller/PaymentsStripeTest.php
+++ b/src/tests/Feature/Controller/PaymentsStripeTest.php
@@ -72,6 +72,7 @@
$response->assertStatus(401);
$user = $this->getTestUser('john@kolab.org');
+ $wallet = $user->wallets()->first();
// Test creating a mandate (invalid input)
$post = [];
@@ -108,6 +109,18 @@
$min = intval(PaymentProvider::MIN_AMOUNT / 100) . ' CHF';
$this->assertSame("Minimum amount for a single payment is {$min}.", $json['errors']['amount']);
+ // Test creating a mandate (negative balance, amount too small)
+ Wallet::where('id', $wallet->id)->update(['balance' => -2000]);
+ $post = ['amount' => PaymentProvider::MIN_AMOUNT / 100, 'balance' => 0];
+ $response = $this->actingAs($user)->post("api/v4/payments/mandate", $post);
+ $response->assertStatus(422);
+
+ $json = $response->json();
+
+ $this->assertSame('error', $json['status']);
+ $this->assertCount(1, $json['errors']);
+ $this->assertSame("The specified amount does not cover the balance on the account.", $json['errors']['amount']);
+
// Test creating a mandate (valid input)
$post = ['amount' => 20.10, 'balance' => 0];
$response = $this->actingAs($user)->post("api/v4/payments/mandate", $post);
@@ -118,6 +131,13 @@
$this->assertSame('success', $json['status']);
$this->assertRegExp('|^cs_test_|', $json['id']);
+ // Assert the proper payment amount has been used
+ // Stripe in 'setup' mode does not allow to set the amount
+ $payment = Payment::where('wallet_id', $wallet->id)->first();
+ $this->assertSame(0, $payment->amount);
+ $this->assertSame("Kolab Now Auto-Payment Setup", $payment->description);
+ $this->assertSame(PaymentProvider::TYPE_MANDATE, $payment->type);
+
// Test fetching the mandate information
$response = $this->actingAs($user)->get("api/v4/payments/mandate");
$response->assertStatus(200);
@@ -406,6 +426,7 @@
{
$user = $this->getTestUser('john@kolab.org');
$wallet = $user->wallets()->first();
+ Wallet::where('id', $wallet->id)->update(['balance' => -1000]);
// Test creating a mandate (valid input)
$post = ['amount' => 20.10, 'balance' => 0];
@@ -438,6 +459,8 @@
'type' => "setup_intent.succeeded"
];
+ Bus::fake();
+
// Test payment succeeded event
$response = $this->webhookRequest($post);
$response->assertStatus(200);
@@ -447,6 +470,13 @@
$this->assertSame(PaymentProvider::STATUS_PAID, $payment->status);
$this->assertSame($payment->id, $wallet->fresh()->getSetting('stripe_mandate_id'));
+ // Expect a WalletCharge job if the balance is negative
+ Bus::assertDispatchedTimes(\App\Jobs\WalletCharge::class, 1);
+ Bus::assertDispatched(\App\Jobs\WalletCharge::class, function ($job) use ($wallet) {
+ $job_wallet = TestCase::getObjectProperty($job, 'wallet');
+ return $job_wallet->id === $wallet->id;
+ });
+
// TODO: test other setup_intent.* events
}
diff --git a/src/tests/Feature/Controller/WalletsTest.php b/src/tests/Feature/Controller/WalletsTest.php
--- a/src/tests/Feature/Controller/WalletsTest.php
+++ b/src/tests/Feature/Controller/WalletsTest.php
@@ -190,6 +190,8 @@
$john = $this->getTestUser('john@kolab.org');
$jack = $this->getTestUser('jack@kolab.org');
$wallet = $john->wallets()->first();
+ $wallet->balance = -100;
+ $wallet->save();
// Accessing a wallet of someone else
$response = $this->actingAs($jack)->get("api/v4/wallets/{$wallet->id}");
@@ -209,7 +211,6 @@
$this->assertSame('CHF', $json['currency']);
$this->assertSame($wallet->balance, $json['balance']);
$this->assertTrue(empty($json['description']));
- // TODO: This assertion does not work after a longer while from seeding
$this->assertTrue(!empty($json['notice']));
}

File Metadata

Mime Type
text/plain
Expires
Tue, Apr 7, 6:17 AM (9 h, 46 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
18821903
Default Alt Text
D1900.1775542678.diff (15 KB)

Event Timeline