diff --git a/src/app/Http/Controllers/API/V4/Admin/DiscountsController.php b/src/app/Http/Controllers/API/V4/Admin/DiscountsController.php
new file mode 100644
--- /dev/null
+++ b/src/app/Http/Controllers/API/V4/Admin/DiscountsController.php
@@ -0,0 +1,42 @@
+orderBy('discount')->get()
+ ->map(function ($discount) use (&$discounts) {
+ $label = $discount->discount . '% - ' . $discount->description;
+
+ if ($discount->code) {
+ $label .= " [{$discount->code}]";
+ }
+
+ $discounts[] = [
+ 'id' => $discount->id,
+ 'discount' => $discount->discount,
+ 'code' => $discount->code,
+ 'description' => $discount->description,
+ 'label' => $label,
+ ];
+ });
+
+ return response()->json([
+ 'status' => 'success',
+ 'list' => $discounts,
+ 'count' => count($discounts),
+ ]);
+ }
+}
diff --git a/src/app/Http/Controllers/API/V4/Admin/WalletsController.php b/src/app/Http/Controllers/API/V4/Admin/WalletsController.php
--- a/src/app/Http/Controllers/API/V4/Admin/WalletsController.php
+++ b/src/app/Http/Controllers/API/V4/Admin/WalletsController.php
@@ -2,6 +2,48 @@
namespace App\Http\Controllers\API\V4\Admin;
+use App\Discount;
+use App\Wallet;
+use Illuminate\Http\Request;
+
class WalletsController extends \App\Http\Controllers\API\V4\WalletsController
{
+ /**
+ * Update wallet data.
+ *
+ * @param \Illuminate\Http\Request $request The API request.
+ * @params string $id Wallet identifier
+ *
+ * @return \Illuminate\Http\JsonResponse The response
+ */
+ public function update(Request $request, $id)
+ {
+ $wallet = Wallet::find($id);
+
+ if (empty($wallet)) {
+ return $this->errorResponse(404);
+ }
+
+ if (array_key_exists('discount', $request->input())) {
+ if (empty($request->discount)) {
+ $wallet->discount()->dissociate();
+ $wallet->save();
+ } elseif ($discount = Discount::find($request->discount)) {
+ $wallet->discount()->associate($discount);
+ $wallet->save();
+ }
+ }
+
+ $response = $wallet->toArray();
+
+ if ($wallet->discount) {
+ $response['discount'] = $wallet->discount->discount;
+ $response['discount_description'] = $wallet->discount->description;
+ }
+
+ $response['status'] = 'success';
+ $response['message'] = \trans('app.wallet-update-success');
+
+ return response()->json($response);
+ }
}
diff --git a/src/app/Http/Controllers/API/V4/WalletsController.php b/src/app/Http/Controllers/API/V4/WalletsController.php
--- a/src/app/Http/Controllers/API/V4/WalletsController.php
+++ b/src/app/Http/Controllers/API/V4/WalletsController.php
@@ -13,7 +13,7 @@
/**
* Display a listing of the resource.
*
- * @return \Illuminate\Http\Response
+ * @return \Illuminate\Http\JsonResponse
*/
public function index()
{
@@ -23,7 +23,7 @@
/**
* Show the form for creating a new resource.
*
- * @return \Illuminate\Http\Response
+ * @return \Illuminate\Http\JsonResponse
*/
public function create()
{
@@ -35,7 +35,7 @@
*
* @param \Illuminate\Http\Request $request
*
- * @return \Illuminate\Http\Response
+ * @return \Illuminate\Http\JsonResponse
*/
public function store(Request $request)
{
@@ -47,7 +47,7 @@
*
* @param int $id
*
- * @return \Illuminate\Http\Response
+ * @return \Illuminate\Http\JsonResponse
*/
public function show($id)
{
@@ -59,7 +59,7 @@
*
* @param int $id
*
- * @return \Illuminate\Http\Response
+ * @return \Illuminate\Http\JsonResponse
*/
public function edit($id)
{
@@ -72,7 +72,7 @@
* @param \Illuminate\Http\Request $request
* @param int $id
*
- * @return \Illuminate\Http\Response
+ * @return \Illuminate\Http\JsonResponse
*/
public function update(Request $request, $id)
{
@@ -84,7 +84,7 @@
*
* @param int $id
*
- * @return \Illuminate\Http\Response
+ * @return \Illuminate\Http\JsonResponse
*/
public function destroy($id)
{
diff --git a/src/resources/lang/en/app.php b/src/resources/lang/en/app.php
--- a/src/resources/lang/en/app.php
+++ b/src/resources/lang/en/app.php
@@ -27,4 +27,6 @@
'search-foundxdomains' => ':x domains have been found.',
'search-foundxusers' => ':x user accounts have been found.',
+
+ 'wallet-update-success' => 'User wallet updated successfully.',
];
diff --git a/src/resources/vue/Admin/User.vue b/src/resources/vue/Admin/User.vue
--- a/src/resources/vue/Admin/User.vue
+++ b/src/resources/vue/Admin/User.vue
@@ -110,7 +110,7 @@
{{ wallet_discount ? (wallet_discount + '% - ' + wallet_discount_description) : 'none' }}
-
+
@@ -223,6 +223,33 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -240,8 +267,10 @@
balance: 0,
discount: 0,
discount_description: '',
+ discounts: [],
wallet_discount: 0,
wallet_discount_description: '',
+ wallet_discount_id: '',
domains: [],
skus: [],
users: [],
@@ -281,6 +310,7 @@
})
this.wallet_discount = this.user.wallets[0].discount
+ this.wallet_discount_id = this.user.wallets[0].discount_id || ''
this.wallet_discount_description = this.user.wallets[0].discount_description
// Create subscriptions list
@@ -293,6 +323,8 @@
let item = {
id: sku.id,
name: sku.name,
+ cost: sku.cost,
+ units: count - sku.units_free,
price: this.price(sku.cost, count - sku.units_free)
}
@@ -329,6 +361,21 @@
})
},
methods: {
+ discountEdit() {
+ $('#discount-dialog')
+ .on('shown.bs.modal', (e, a) => {
+ $(e.target).find('select').focus()
+ })
+ .modal()
+
+ if (!this.discounts.length) {
+ // Fetch discounts
+ axios.get('/api/v4/discounts')
+ .then(response => {
+ this.discounts = response.data.list
+ })
+ }
+ },
price(cost, units = 1) {
let index = ''
@@ -342,7 +389,30 @@
}
return this.$root.price(cost * units) + '/month' + index
- }
+ },
+ submitDiscount() {
+ let dialog = $('#discount-dialog').modal('hide')
+
+ axios.put('/api/v4/wallets/' + this.user.wallets[0].id, { discount: this.wallet_discount_id })
+ .then(response => {
+ if (response.data.status == 'success') {
+ this.$toastr('success', response.data.message)
+ this.wallet_discount = response.data.discount
+ this.wallet_discount_id = response.data.discount_id || ''
+ this.wallet_discount_description = response.data.discount_description
+
+ // Update prices in Subscriptions tab
+ if (this.user.wallet.id == response.data.id) {
+ this.discount = this.wallet_discount
+ this.discount_description = this.wallet_discount_description
+
+ this.skus.forEach(sku => {
+ sku.price = this.price(sku.cost, sku.units)
+ })
+ }
+ }
+ })
+ },
}
}
diff --git a/src/routes/api.php b/src/routes/api.php
--- a/src/routes/api.php
+++ b/src/routes/api.php
@@ -94,5 +94,6 @@
Route::apiResource('skus', API\V4\Admin\SkusController::class);
Route::apiResource('users', API\V4\Admin\UsersController::class);
Route::apiResource('wallets', API\V4\Admin\WalletsController::class);
+ Route::apiResource('discounts', API\V4\Admin\DiscountsController::class);
}
);
diff --git a/src/tests/Browser/Admin/UserTest.php b/src/tests/Browser/Admin/UserTest.php
--- a/src/tests/Browser/Admin/UserTest.php
+++ b/src/tests/Browser/Admin/UserTest.php
@@ -4,6 +4,7 @@
use App\Discount;
use Tests\Browser;
+use Tests\Browser\Components\Dialog;
use Tests\Browser\Components\Toast;
use Tests\Browser\Pages\Admin\User as UserPage;
use Tests\Browser\Pages\Dashboard;
@@ -339,4 +340,63 @@
});
});
}
+
+ /**
+ * Test editing wallet discount
+ *
+ * @depends testUserInfo2
+ */
+ public function testWalletDiscount(): void
+ {
+ $this->browse(function (Browser $browser) {
+ $john = $this->getTestUser('john@kolab.org');
+
+ $browser->visit(new UserPage($john->id))
+ ->pause(100)
+ ->click('@user-finances #discount button')
+ // Test dialog content, and closing it with Cancel button
+ ->with(new Dialog('#discount-dialog'), function (Browser $browser) {
+ $browser->assertSeeIn('@title', 'Account discount')
+ ->assertFocused('@body select')
+ ->assertSelected('@body select', '')
+ ->assertSeeIn('@button-cancel', 'Cancel')
+ ->assertSeeIn('@button-action', 'Submit')
+ ->click('@button-cancel');
+ })
+ ->assertMissing('#discount-dialog')
+ ->click('@user-finances #discount button')
+ // Change the discount
+ ->with(new Dialog('#discount-dialog'), function (Browser $browser) {
+ $browser->click('@body select')
+ ->click('@body select option:nth-child(2)')
+ ->click('@button-action');
+ })
+ ->assertToast(Toast::TYPE_SUCCESS, '', 'User wallet updated successfully.')
+ ->assertSeeIn('#discount span', '10% - Test voucher')
+ ->click('@nav #tab-subscriptions')
+ ->with('@user-subscriptions', function (Browser $browser) {
+ $browser->assertSeeIn('table tbody tr:nth-child(1) td:last-child', '3,99 CHF/month¹')
+ ->assertSeeIn('table tbody tr:nth-child(2) td:last-child', '0,00 CHF/month¹')
+ ->assertSeeIn('table tbody tr:nth-child(3) td:last-child', '4,99 CHF/month¹')
+ ->assertSeeIn('table + .hint', '¹ applied discount: 10% - Test voucher');
+ })
+ // Change back to 'none'
+ ->click('@nav #tab-finances')
+ ->click('@user-finances #discount button')
+ ->with(new Dialog('#discount-dialog'), function (Browser $browser) {
+ $browser->click('@body select')
+ ->click('@body select option:nth-child(1)')
+ ->click('@button-action');
+ })
+ ->assertToast(Toast::TYPE_SUCCESS, '', 'User wallet updated successfully.')
+ ->assertSeeIn('#discount span', 'none')
+ ->click('@nav #tab-subscriptions')
+ ->with('@user-subscriptions', function (Browser $browser) {
+ $browser->assertSeeIn('table tbody tr:nth-child(1) td:last-child', '4,44 CHF/month')
+ ->assertSeeIn('table tbody tr:nth-child(2) td:last-child', '0,00 CHF/month')
+ ->assertSeeIn('table tbody tr:nth-child(3) td:last-child', '5,55 CHF/month')
+ ->assertMissing('table + .hint');
+ });
+ });
+ }
}
diff --git a/src/tests/Feature/Controller/Admin/DiscountsTest.php b/src/tests/Feature/Controller/Admin/DiscountsTest.php
new file mode 100644
--- /dev/null
+++ b/src/tests/Feature/Controller/Admin/DiscountsTest.php
@@ -0,0 +1,61 @@
+getTestUser('john@kolab.org');
+ $admin = $this->getTestUser('jeroen@jeroen.jeroen');
+
+ // Non-admin user
+ $response = $this->actingAs($user)->get("api/v4/discounts");
+ $response->assertStatus(403);
+
+ // Admin user
+ $response = $this->actingAs($admin)->get("api/v4/discounts");
+ $response->assertStatus(200);
+
+ $json = $response->json();
+
+ $discount_test = Discount::where('code', 'TEST')->first();
+ $discount_free = Discount::where('discount', 100)->first();
+
+ $this->assertSame(3, $json['count']);
+ $this->assertSame($discount_test->id, $json['list'][0]['id']);
+ $this->assertSame($discount_test->discount, $json['list'][0]['discount']);
+ $this->assertSame($discount_test->code, $json['list'][0]['code']);
+ $this->assertSame($discount_test->description, $json['list'][0]['description']);
+ $this->assertSame('10% - Test voucher [TEST]', $json['list'][0]['label']);
+
+ $this->assertSame($discount_free->id, $json['list'][2]['id']);
+ $this->assertSame($discount_free->discount, $json['list'][2]['discount']);
+ $this->assertSame($discount_free->code, $json['list'][2]['code']);
+ $this->assertSame($discount_free->description, $json['list'][2]['description']);
+ $this->assertSame('100% - Free Account', $json['list'][2]['label']);
+ }
+}
diff --git a/src/tests/Feature/Controller/Admin/WalletsTest.php b/src/tests/Feature/Controller/Admin/WalletsTest.php
new file mode 100644
--- /dev/null
+++ b/src/tests/Feature/Controller/Admin/WalletsTest.php
@@ -0,0 +1,70 @@
+getTestUser('john@kolab.org');
+ $admin = $this->getTestUser('jeroen@jeroen.jeroen');
+ $wallet = $user->wallets()->first();
+ $discount = Discount::where('code', 'TEST')->first();
+
+ // Non-admin user
+ $response = $this->actingAs($user)->put("api/v4/wallets/{$wallet->id}", []);
+ $response->assertStatus(403);
+
+ // Admin user - setting a discount
+ $post = ['discount' => $discount->id];
+ $response = $this->actingAs($admin)->put("api/v4/wallets/{$wallet->id}", $post);
+ $response->assertStatus(200);
+
+ $json = $response->json();
+
+ $this->assertSame('success', $json['status']);
+ $this->assertSame('User wallet updated successfully.', $json['message']);
+ $this->assertSame($wallet->id, $json['id']);
+ $this->assertSame($discount->discount, $json['discount']);
+ $this->assertSame($discount->id, $json['discount_id']);
+ $this->assertSame($discount->description, $json['discount_description']);
+ $this->assertSame($discount->id, $wallet->fresh()->discount->id);
+
+ // Admin user - removing a discount
+ $post = ['discount' => null];
+ $response = $this->actingAs($admin)->put("api/v4/wallets/{$wallet->id}", $post);
+ $response->assertStatus(200);
+
+ $json = $response->json();
+
+ $this->assertSame('success', $json['status']);
+ $this->assertSame('User wallet updated successfully.', $json['message']);
+ $this->assertSame($wallet->id, $json['id']);
+ $this->assertSame(null, $json['discount_id']);
+ $this->assertTrue(empty($json['discount_description']));
+ $this->assertSame(null, $wallet->fresh()->discount);
+ }
+}