/**
  * @param $user
  * @param $amount
  * @return void
  */
 private function setupNewMember(User $user, $amount)
 {
     //At this point the day of the month hasn't been set for the user, set it now
     $user->updateSubscription('paypal', Carbon::now()->day);
     //if this is blank then the user hasn't been setup yet
     $subCharge = $this->subscriptionChargeRepository->createCharge($user->id, Carbon::now(), $amount);
 }
 /**
  * Start the creation of a new gocardless payment
  *   Details get posted into this method and the redirected to gocardless
  * @param $userId
  * @throws \BB\Exceptions\AuthenticationException
  * @throws \BB\Exceptions\FormValidationException
  * @throws \BB\Exceptions\NotImplementedException
  */
 public function store($userId)
 {
     User::findWithPermission($userId);
     $requestData = \Request::only(['reason', 'amount', 'return_path', 'stripeToken', 'ref']);
     $stripeToken = $requestData['stripeToken'];
     $amount = $requestData['amount'];
     $reason = $requestData['reason'];
     $returnPath = $requestData['return_path'];
     $ref = $requestData['ref'];
     try {
         $charge = Stripe_Charge::create(array("amount" => $amount, "currency" => "gbp", "card" => $stripeToken, "description" => $reason));
     } catch (\Exception $e) {
         \Log::error($e);
         if (\Request::wantsJson()) {
             return \Response::json(['error' => 'There was an error confirming your payment'], 400);
         }
         \Notification::error("There was an error confirming your payment");
         return \Redirect::to($returnPath);
     }
     //Replace the amount with the one from the charge, this prevents issues with variable tempering
     $amount = $charge->amount / 100;
     //Stripe don't provide us with the fee so this should be OK
     $fee = $amount * 0.024 + 0.2;
     $this->paymentRepository->recordPayment($reason, $userId, 'stripe', $charge->id, $amount, 'paid', $fee, $ref);
     if (\Request::wantsJson()) {
         return \Response::json(['message' => 'Payment made']);
     }
     \Notification::success("Payment made");
     return \Redirect::to($returnPath);
 }
 public function run()
 {
     $today = new Carbon();
     //Fetch and check over active users which have a status of leaving
     $users = User::paymentWarning()->get();
     foreach ($users as $user) {
         /** @var $user \BB\Entities\User */
         if ($user->subscription_expires->lt($today)) {
             //User has passed their expiry date
             echo $user->name . ' has a payment warning and has passed their expiry date' . PHP_EOL;
             //Check the actual expiry date
             //When did their last sub payment expire
             $paidUntil = MembershipPayments::lastUserPaymentExpires($user->id);
             //What grace period do they have - when should we give them to
             $cutOffDate = MembershipPayments::getSubGracePeriodDate($user->payment_method);
             //If the cut of date is greater than (sooner) than the last payment date "expire" them
             if ($paidUntil == false || $cutOffDate->subDays(2)->gt($paidUntil)) {
                 //set the status to left and active to false
                 $this->userRepository->memberLeft($user->id);
                 echo $user->name . ' marked as having left' . PHP_EOL;
             }
             //an email will be sent by the user observer
         } else {
             echo $user->name . ' has a payment warning but is within their expiry date' . PHP_EOL;
         }
     }
 }
 public function update($userId)
 {
     //Verify the user can access this user record - we don't need the record just the auth check
     $user = User::findWithPermission($userId);
     $input = \Input::all();
     //Clear the profile photo field as this is handled separately below.
     unset($input['new_profile_photo']);
     if (empty($input['profile_photo_private'])) {
         $input['profile_photo_private'] = false;
     }
     //Trim all the data so some of the validation doesn't choke on spaces
     foreach ($input as $key => $value) {
         if (is_string($value)) {
             $input[$key] = trim($value);
         }
     }
     $this->profileValidator->validate($input, $userId);
     $this->profileRepo->update($userId, $input);
     if (\Input::file('new_profile_photo')) {
         try {
             $this->userImage->uploadPhoto($user->hash, \Input::file('new_profile_photo')->getRealPath(), true);
             $this->profileRepo->update($userId, ['new_profile_photo' => 1]);
             \Notification::success("Photo uploaded, it will be checked and appear shortly");
         } catch (\Exception $e) {
             \Log::error($e);
         }
     } else {
         \Notification::success("Profile Updated");
     }
     return \Redirect::route('members.show', $userId);
 }
 /**
  * Start the creation of a new balance payment
  *   Details get posted into this method
  * @param $userId
  * @throws \BB\Exceptions\AuthenticationException
  * @throws \BB\Exceptions\FormValidationException
  * @throws \BB\Exceptions\NotImplementedException
  */
 public function store($userId)
 {
     $user = User::findWithPermission($userId);
     $this->bbCredit->setUserId($user->id);
     $requestData = \Request::only(['reason', 'amount', 'return_path', 'ref']);
     $amount = $requestData['amount'] * 1 / 100;
     $reason = $requestData['reason'];
     $returnPath = $requestData['return_path'];
     $ref = $requestData['ref'];
     //Can the users balance go below 0
     $minimumBalance = $this->bbCredit->acceptableNegativeBalance($reason);
     //What is the users balance
     $userBalance = $this->bbCredit->getBalance();
     //With this payment will the users balance go to low?
     if ($userBalance - $amount < $minimumBalance) {
         if (\Request::wantsJson()) {
             return \Response::json(['error' => 'You don\'t have the money for this'], 400);
         }
         \Notification::error("You don't have the money for this");
         return \Redirect::to($returnPath);
     }
     //Everything looks gooc, create the payment
     $this->paymentRepository->recordPayment($reason, $userId, 'balance', '', $amount, 'paid', 0, $ref);
     //Update the users cached balance
     $this->bbCredit->recalculate();
     if (\Request::wantsJson()) {
         return \Response::json(['message' => 'Payment made']);
     }
     \Notification::success("Payment made");
     return \Redirect::to($returnPath);
 }
 public function run()
 {
     $users = User::active()->where('status', '=', 'active')->notSpecialCase()->get();
     foreach ($users as $user) {
         /** @var $user \BB\Entities\User */
         echo $user->name;
         $expired = false;
         $cutOffDate = MembershipPayments::getSubGracePeriodDate($user->payment_method);
         if (!$user->subscription_expires || $user->subscription_expires->lt($cutOffDate)) {
             $expired = true;
         }
         //Check for payments first
         if ($expired) {
             $paidUntil = MembershipPayments::lastUserPaymentExpires($user->id);
             //$paidUntil = $this->memberSubscriptionCharges->lastUserChargeExpires($user->id);
             if ($paidUntil) {
                 if ($user->subscription_expires && $user->subscription_expires->lt($paidUntil)) {
                     $user->extendMembership($user->payment_method, $paidUntil);
                     //This may not be true but it simplifies things now and tomorrows process will deal with it
                     $expired = false;
                 }
             }
         }
         if ($expired) {
             $user->setSuspended();
             echo ' - Suspended';
         }
         echo PHP_EOL;
     }
 }
 /**
  * Update the specified resource in storage.
  *
  * @param  \Illuminate\Http\Request  $request
  * @param  int  $id
  * @return \Illuminate\Http\Response
  */
 public function update(Request $request, $id)
 {
     $user = User::findWithPermission($id);
     $input = $request->only('rules_agreed', 'induction_completed');
     $this->inductionValidator->validate($input);
     $this->userRepository->recordInductionCompleted($id);
     return \Redirect::route('account.show', [$user->id]);
 }
 public function index($userId)
 {
     //Verify the user can access this user record
     $user = User::findWithPermission($userId);
     $this->bbCredit->setUserId($user->id);
     $userBalance = $this->bbCredit->getBalanceFormatted();
     $payments = $this->bbCredit->getBalancePaymentsPaginated();
     return \View::make('account.bbcredit.index')->with('user', $user)->with('payments', $payments)->with('userBalance', $userBalance);
 }
 public function show($id)
 {
     $user = User::findOrFail($id);
     if (\Auth::guest() && $user->profile_private) {
         return \Response::make('', 404);
     }
     $profileData = $this->profileRepo->getUserProfile($id);
     $userSkills = array_intersect_ukey($this->profileSkillsRepository->getAll(), array_flip($profileData->skills), [$this, 'key_compare_func']);
     return \View::make('members.show')->with('user', $user)->with('profileData', $profileData)->with('userSkills', $userSkills);
 }
Ejemplo n.º 10
0
 public function memberCantVisitRolesPage(FunctionalTester $I)
 {
     $I->am('a member');
     $I->wantTo('make sure I can\'t view the roles page');
     //Load and login a known member
     $user = User::find(1);
     \Auth::login($user);
     $I->amOnPage('/roles');
     $I->canSeeResponseCodeIs(403);
 }
Ejemplo n.º 11
0
 public function memberCanLeave(FunctionalTester $I)
 {
     $I->am('a member');
     $I->wantTo('leave build brighton');
     //Load and login a known member
     $user = User::find(1);
     Auth::login($user);
     $I->amOnPage('/account/' . $user->id . '');
     $I->canSee('Active');
     $I->click("Leave Build Brighton");
     $I->canSee('Leaving');
 }
 /**
  * Remove the specified resource from storage.
  *
  * @param $roleId
  * @param $userId
  * @return Response
  */
 public function destroy($roleId, $userId)
 {
     $role = Role::findOrFail($roleId);
     //don't let people remove the admin permission if they are a trustee
     $user = User::findOrFail($userId);
     if ($user->active && $user->director && $role->name == 'admin') {
         \Notification::error("You cannot remove a trustee from the admin group");
         return \Redirect::back();
     }
     $role->users()->detach($userId);
     return \Redirect::back();
 }
 public function teamMemberCanEditLog(FunctionalTester $I)
 {
     $I->am('a laser team member');
     $I->wantTo('make sure I can edit laser logs');
     //Load and login a known admin member
     $user = $I->loginLaserTeamMember();
     $otherUser = User::find(1);
     $I->amOnPage('/equipment/laser');
     $I->see($otherUser->name);
     $I->selectOption('form[name=equipmentLog] select[name=reason]', 'testing');
     $I->click('Update');
 }
 public function financeMemberCanVisitPaymentPage(FunctionalTester $I)
 {
     $I->am('a member of the finance group');
     $I->wantTo('make sure I can view the payments page');
     //Load and login a known member
     $user = User::find(3);
     $role = Role::findByName('finance');
     $role->users()->attach($user->id);
     Auth::login($user);
     $I->amOnPage('/payments');
     $I->seeCurrentUrlEquals('/payments');
     $I->see('Payments');
 }
 public function paymentDateChange(UnitTester $I)
 {
     $user = \BB\Entities\User::create(['given_name' => 'Jon', 'family_name' => 'Doe', 'email' => '*****@*****.**']);
     $I->assertEquals(0, $user->payment_day);
     $user->payment_day = 10;
     $user->save();
     $I->assertEquals(10, $user->payment_day);
     $user->payment_day = 28;
     $user->save();
     $I->assertEquals(28, $user->payment_day);
     $user->payment_day = 31;
     $user->save();
     $I->assertEquals(1, $user->payment_day);
 }
 public function run()
 {
     $today = new Carbon();
     //Fetch and check over active users which have a status of leaving
     $users = User::leaving()->notSpecialCase()->get();
     foreach ($users as $user) {
         if ($user->subscription_expires->lt($today)) {
             //User has passed their expiry date
             //set the status to left and active to false
             $this->userRepository->memberLeft($user->id);
             //an email will be sent by the user observer
         }
     }
 }
Ejemplo n.º 17
0
 /**
  * @param array   $memberData The new members details
  * @param boolean $isAdminCreating Is the user making the change an admin
  * @return User
  */
 public function registerMember(array $memberData, $isAdminCreating)
 {
     if (empty($memberData['profile_photo_private'])) {
         $memberData['profile_photo_private'] = false;
     }
     if (empty($memberData['password'])) {
         unset($memberData['password']);
     }
     $memberData['hash'] = str_random(30);
     $memberData['rules_agreed'] ? Carbon::now() : null;
     $user = $this->model->create($memberData);
     $this->profileDataRepository->createProfile($user->id);
     $this->addressRepository->saveUserAddress($user->id, $memberData['address'], $isAdminCreating);
     return $user;
 }
 public function adminCanDeclineExpenses(FunctionalTester $I)
 {
     $I->am('an admin');
     $I->wantTo('make sure I can decline an expense');
     //Create a proposal that's currently open
     $I->haveInDatabase('expenses', ['id' => 4, 'category' => 'consumables', 'description' => 'Another Description', 'user_id' => '3', 'amount' => 1234, 'expense_date' => Carbon::now()]);
     //Load and login a known member
     $user = User::find(3);
     Auth::login($user);
     $I->amOnPage('/expenses');
     $I->canSee('Expenses');
     $I->canSee('Another Description');
     $I->cantSee('Declined by');
     $I->click('Decline');
     $I->canSee('Declined by');
 }
 /**
  * Remove cash from the users balance
  *
  * @param $userId
  * @return mixed
  * @throws \BB\Exceptions\AuthenticationException
  * @throws \BB\Exceptions\InvalidDataException
  */
 public function destroy($userId)
 {
     $user = User::findWithPermission($userId);
     $this->bbCredit->setUserId($userId);
     $amount = \Request::get('amount');
     $returnPath = \Request::get('return_path');
     $ref = \Request::get('ref');
     $minimumBalance = $this->bbCredit->acceptableNegativeBalance('withdrawal');
     if ($user->cash_balance + $minimumBalance * 100 < $amount * 100) {
         \Notification::error("Not enough money");
         return \Redirect::to($returnPath);
     }
     $this->paymentRepository->recordPayment('withdrawal', $userId, 'balance', '', $amount, 'paid', 0, $ref);
     $this->bbCredit->recalculate();
     \Notification::success("Payment recorded");
     return \Redirect::to($returnPath);
 }
 /**
  * Processes the return for old gocardless payments
  *
  * @param $userId
  * @return \Illuminate\Http\RedirectResponse
  * @throws \BB\Exceptions\AuthenticationException
  */
 public function handleManualReturn($userId)
 {
     $user = User::findWithPermission($userId);
     $confirm_params = array('resource_id' => $_GET['resource_id'], 'resource_type' => $_GET['resource_type'], 'resource_uri' => $_GET['resource_uri'], 'signature' => $_GET['signature']);
     // State is optional
     if (isset($_GET['state'])) {
         $confirm_params['state'] = $_GET['state'];
     }
     //Get the details, reason, reference and return url
     $details = explode(':', \Input::get('state'));
     $reason = 'unknown';
     $ref = null;
     $returnPath = route('account.show', [$user->id], false);
     if (is_array($details)) {
         if (isset($details[0])) {
             $reason = $details[0];
         }
         if (isset($details[1])) {
             $ref = $details[1];
         }
         if (isset($details[2])) {
             $returnPath = $details[2];
         }
     }
     //Confirm the resource
     try {
         $confirmed_resource = $this->goCardless->confirmResource($confirm_params);
     } catch (\Exception $e) {
         \Notification::error($e->getMessage());
         return \Redirect::to($returnPath);
     }
     //Store the payment
     $fee = $confirmed_resource->amount - $confirmed_resource->amount_minus_fees;
     $paymentSourceId = $confirmed_resource->id;
     $amount = $confirmed_resource->amount;
     $status = $confirmed_resource->status;
     //The record payment process will make the necessary record updates
     $this->paymentRepository->recordPayment($reason, $userId, 'gocardless', $paymentSourceId, $amount, $status, $fee, $ref);
     \Notification::success("Payment made");
     return \Redirect::to($returnPath);
 }
 /**
  * This is a basic method for recording a payment transfer between two people
  * This should not exist and the normal balance payment controller should be used
  * If any more work is needed here please take the time and move it over!
  *
  * @param Request $request
  * @param integer $userId
  *
  * @return mixed
  * @throws ValidationException
  * @throws AuthenticationException
  */
 public function recordTransfer(Request $request, $userId)
 {
     $user = User::findWithPermission($userId);
     $this->bbCredit->setUserId($user->id);
     $amount = $request->get('amount');
     $targetUserId = $request->get('target_user_id');
     $targetUser = $this->userRepository->getById($targetUserId);
     if ($targetUserId === $userId) {
         throw new ValidationException('Your\'e trying to send money to yourself, no!');
     }
     //What is the users balance
     $userBalance = $this->bbCredit->getBalance();
     //With this payment will the users balance go to low?
     if ($userBalance - $amount < 0) {
         \Notification::error("You don't have the money for this");
         return \Redirect::route('account.balance.index', $user->id);
     }
     $this->paymentRepository->recordBalanceTransfer($user->id, $targetUser->id, $amount);
     \Notification::success("Transfer made");
     return \Redirect::route('account.balance.index', $user->id);
 }
 public function memberCanReturnBox(FunctionalTester $I)
 {
     $I->am('a member');
     $I->wantTo('make sure I can return a box I own');
     //Load and login a known member
     $user = User::find(1);
     $I->amLoggedAs($user);
     //Setup a box a already claimed
     $box = \BB\Entities\StorageBox::first();
     $box->user_id = $user->id;
     $box->save();
     $I->amOnPage('/storage_boxes');
     //Make sure the db is correct
     $I->seeInDatabase('storage_boxes', ['user_id' => $user->id]);
     //The page should have our name next to the claimed box
     $I->see($user->name);
     $I->click('Return Box');
     //We should be gone from the DB
     $I->dontSeeInDatabase('storage_boxes', ['user_id' => $user->id]);
     $I->cantSee($user->name);
 }
 public function run()
 {
     $today = new Carbon();
     //Fetch and check over active users which have a status of suspended
     $users = User::suspended()->get();
     foreach ($users as $user) {
         if (!$user->subscription_expires || $user->subscription_expires->lt($today)) {
             //User has passed their expiry date
             echo $user->name . ' is suspended and has passed their expiry date' . PHP_EOL;
             //Check the actual expiry date
             //When did their last sub payment expire
             $paidUntil = MembershipPayments::lastUserPaymentExpires($user->id);
             if ($paidUntil) {
                 if (!$user->subscription_expires || $user->subscription_expires->lt($paidUntil)) {
                     $user->extendMembership(null, $paidUntil);
                 }
             }
             //an email will be sent by the user observer
         } else {
             echo $user->name . ' has a payment warning but is within their expiry date' . PHP_EOL;
         }
     }
 }
 /**
  * Remove the specified resource from storage.
  *
  * @param  int  $id
  * @return Illuminate\Http\RedirectResponse
  */
 public function destroy($userId, $id = null)
 {
     /**
      * TODO: Check for and cancel pending sub charges
      */
     $user = User::findWithPermission($userId);
     if ($user->payment_method == 'gocardless') {
         try {
             $subscription = $this->goCardless->cancelSubscription($user->subscription_id);
             if ($subscription->status == 'cancelled') {
                 $user->cancelSubscription();
                 \Notification::success('Your subscription has been cancelled');
                 return \Redirect::back();
             }
         } catch (\GoCardless_ApiException $e) {
             if ($e->getCode() == 404) {
                 $user->cancelSubscription();
                 \Notification::success('Your subscription has been cancelled');
                 return \Redirect::back();
             }
         }
     } elseif ($user->payment_method == 'gocardless-variable') {
         $status = $this->goCardless->cancelPreAuth($user->subscription_id);
         if ($status) {
             $user->subscription_id = null;
             $user->payment_method = '';
             $user->save();
             $user->setLeaving();
             $this->subscriptionChargeRepository->cancelOutstandingCharges($userId);
             \Notification::success('Your direct debit has been cancelled');
             return \Redirect::back();
         }
     }
     \Notification::error('Sorry, we were unable to cancel your subscription, please get in contact');
     return \Redirect::back();
 }
 public function updateSubscriptionAmount($id)
 {
     $amount = \Input::get('monthly_subscription');
     if ($amount < 5) {
         throw new ValidationException('The minimum subscription is 5 GBP');
     } elseif (!\Auth::user()->isAdmin() && $amount < 15) {
         throw new ValidationException('The minimum subscription is 15 GBP, please contact the trustees for a lower amount. trustees@buildbrighton.com');
     }
     $user = User::findWithPermission($id);
     $user->updateSubAmount(\Input::get('monthly_subscription'));
     \Notification::success('Details Updated');
     return \Redirect::route('account.show', [$user->id]);
 }
<?php

use BB\Entities\Payment;
use BB\Entities\User;
$I = new UnitTester($scenario);
$I->wantTo('confirm the payment helper fetches the correct payment date');
//Create a user record
$user = User::create(['given_name' => 'Test', 'family_name' => 'Person', 'email' => '*****@*****.**']);
$date = \BB\Helpers\MembershipPayments::lastUserPaymentDate($user->id);
$I->assertFalse($date, 'Date should be false as no payments exist');
//Create some payment records
\BB\Entities\SubscriptionCharge::create(['user_id' => $user->id, 'charge_date' => '2014-01-01', 'payment_date' => '2014-01-01', 'status' => 'paid']);
Payment::create(['reason' => 'subscription', 'source' => 'other', 'user_id' => $user->id, 'amount' => 20, 'amount_minus_fee' => 20, 'status' => 'paid', 'created_at' => '2014-01-01']);
\BB\Entities\SubscriptionCharge::create(['user_id' => $user->id, 'charge_date' => '2014-06-01', 'status' => 'processing']);
Payment::create(['reason' => 'subscription', 'source' => 'other', 'user_id' => $user->id, 'amount' => 20, 'amount_minus_fee' => 20, 'status' => 'pending', 'created_at' => '2014-06-01']);
\BB\Entities\SubscriptionCharge::create(['user_id' => $user->id, 'charge_date' => '2014-08-01', 'status' => 'cancelled']);
Payment::create(['reason' => 'subscription', 'source' => 'other', 'user_id' => $user->id, 'amount' => 20, 'amount_minus_fee' => 20, 'status' => 'cancelled', 'created_at' => '2014-08-01']);
//Now we have some payments re-fetch the last payment date
$date = \BB\Helpers\MembershipPayments::lastUserPaymentDate($user->id);
//Make sure its a date that's returned
$I->assertEquals(get_parent_class($date), 'DateTime');
//Confirm the datetime matched the first payment record, the only paid one
$I->assertEquals(new \Carbon\Carbon('2014-01-01'), $date);
 public function adminCantEditStartedProposal(FunctionalTester $I)
 {
     $I->am('an admin');
     $I->wantTo('make sure I cannt edit a proposal thats been started');
     //Create a proposal that's currently open
     $startDate = Carbon::now()->subDays(2)->format('Y-m-d');
     $endDate = Carbon::now()->addDays(2)->format('Y-m-d');
     $I->haveInDatabase('proposals', ['id' => 2, 'title' => 'Proposal 2', 'description' => 'Demo Description', 'user_id' => '3', 'start_date' => $startDate, 'end_date' => $endDate]);
     //Load and login a known member
     $user = User::find(3);
     Auth::login($user);
     $I->amOnPage('/proposals/2');
     //I can visit the edit page
     $I->cantSee('Edit Proposal');
 }
Ejemplo n.º 28
0
<?php

use Mockery as m;
$I = new FunctionalTester($scenario);
$I->am('a guest');
$I->wantTo('sign up to build brighton');
$I->amOnPage('/');
$I->click('Become a Member');
$I->seeCurrentUrlEquals('/register');
$I->fillField('First Name', 'Jon');
$I->fillField('Family Name', 'Doe');
$I->fillField('Email', '*****@*****.**');
$I->fillField('Password', '12345678');
$I->fillField(['name' => 'address[line_1]'], 'Street Address');
$I->fillField(['name' => 'address[postcode]'], 'BN3 1AN');
$I->fillField('Phone', '0123456789');
$I->fillField('Emergency Contact', 'Contact Details');
$I->attachFile('Profile Photo', 'test-image.png');
$I->checkOption('rules_agreed');
//$userImageService = m::mock('\BB\Helpers\UserImage');
//$userImageService->shouldReceive('uploadPhoto')->times(1);
//$this->app->instance('\BB\Helpers\UserImage',$userImageService);
//$I->haveEnabledFilters();
$I->click('Join');
//Make sure we are now on an account page with the new id
$I->seeCurrentUrlMatches('^/account/(\\d+)^');
$user = \BB\Entities\User::where('email', '*****@*****.**')->first();
$I->assertNotEmpty($user->hash);
<?php

use BB\Entities\User;
$I = new FunctionalTester($scenario);
$I->am('a member');
$I->wantTo('confirm I cant see other peoples edit page');
//Load and login a known member
$user = User::find(1);
Auth::login($user);
$otherUser = User::find(3);
$I->amOnPage('/account/' . $otherUser->id . '/profile/edit');
$I->seeResponseCodeIs(403);
<?php

use BB\Entities\User;
$I = new FunctionalTester($scenario);
$I->am('a member');
$I->wantTo('update my profile photo');
//Load and login a known member
$user = User::find(1);
Auth::login($user);
$I->amOnPage('/account/' . $user->id . '/profile/edit');
$I->canSee('Profile Photo');
$I->attachFile('Profile Photo', 'test-image.png');
$I->click('Save');
$I->seeCurrentUrlEquals('/members/' . $user->id);