diff --git a/app/Factory/ExpenseCategoryFactory.php b/app/Factory/ExpenseCategoryFactory.php index a2bd3d0bd407..cd7254ba8ee7 100644 --- a/app/Factory/ExpenseCategoryFactory.php +++ b/app/Factory/ExpenseCategoryFactory.php @@ -22,7 +22,7 @@ class ExpenseCategoryFactory $expense->company_id = $company_id; $expense->name = ''; $expense->is_deleted = false; - $expense->color = '#fff'; + $expense->color = ''; return $expense; } diff --git a/app/Factory/TaskStatusFactory.php b/app/Factory/TaskStatusFactory.php index 8d9b4742341e..aa5ea60d4714 100644 --- a/app/Factory/TaskStatusFactory.php +++ b/app/Factory/TaskStatusFactory.php @@ -21,7 +21,7 @@ class TaskStatusFactory $task_status->user_id = $user_id; $task_status->company_id = $company_id; $task_status->name = ''; - $task_status->color = '#fff'; + $task_status->color = ''; $task_status->status_order = 9999; return $task_status; diff --git a/app/Http/Controllers/CompanyController.php b/app/Http/Controllers/CompanyController.php index b85793ffe940..a22091a218bf 100644 --- a/app/Http/Controllers/CompanyController.php +++ b/app/Http/Controllers/CompanyController.php @@ -474,6 +474,10 @@ class CompanyController extends BaseController */ public function destroy(DestroyCompanyRequest $request, Company $company) { + + if(Ninja::isHosted() && config('ninja.ninja_default_company_id') == $company->id) + return response()->json(['message' => 'Cannot purge this company'], 400); + $company_count = $company->account->companies->count(); $account = $company->account; $account_key = $account->key; diff --git a/app/Http/Controllers/LicenseController.php b/app/Http/Controllers/LicenseController.php index 68d4ac407e00..3d601f43c8ba 100644 --- a/app/Http/Controllers/LicenseController.php +++ b/app/Http/Controllers/LicenseController.php @@ -16,6 +16,7 @@ use App\Utils\CurlUtils; use Illuminate\Http\Request; use Illuminate\Http\Response; use stdClass; +use Carbon\Carbon; class LicenseController extends BaseController { @@ -152,7 +153,7 @@ class LicenseController extends BaseController { $account = auth()->user()->company()->account; - if($account->plan == 'white_label' && $account->plan_expires->lt(now())){ + if($account->plan == 'white_label' && Carbon::parse($account->plan_expires)->lt(now())){ $account->plan = null; $account->plan_expires = null; $account->save(); diff --git a/app/Http/Controllers/MigrationController.php b/app/Http/Controllers/MigrationController.php index 370dd080d403..94ab88c5123d 100644 --- a/app/Http/Controllers/MigrationController.php +++ b/app/Http/Controllers/MigrationController.php @@ -82,6 +82,9 @@ class MigrationController extends BaseController */ public function purgeCompany(Company $company) { + if(Ninja::isHosted() && config('ninja.ninja_default_company_id') == $company->id) + return response()->json(['message' => 'Cannot purge this company'], 400); + $account = $company->account; $company_id = $company->id; @@ -102,6 +105,9 @@ class MigrationController extends BaseController private function purgeCompanyWithForceFlag(Company $company) { + if(Ninja::isHosted() && config('ninja.ninja_default_company_id') == $company->id) + return response()->json(['message' => 'Cannot purge this company'], 400); + $account = $company->account; $company_id = $company->id; diff --git a/app/Http/Controllers/PaymentNotificationWebhookController.php b/app/Http/Controllers/PaymentNotificationWebhookController.php new file mode 100644 index 000000000000..d9d41618e328 --- /dev/null +++ b/app/Http/Controllers/PaymentNotificationWebhookController.php @@ -0,0 +1,36 @@ +decodePrimaryKey($company_gateway_id)); + $client = Client::find($this->decodePrimaryKey($client_hash)); + + return $company_gateway + ->driver($client) + ->processWebhookRequest($request); + } +} diff --git a/app/Http/Middleware/UrlSetDb.php b/app/Http/Middleware/UrlSetDb.php index 9e9fe306d323..e93ac08440ab 100644 --- a/app/Http/Middleware/UrlSetDb.php +++ b/app/Http/Middleware/UrlSetDb.php @@ -30,17 +30,22 @@ class UrlSetDb */ public function handle($request, Closure $next) { + if (config('ninja.db.multi_db_enabled')) { - $hashids = new Hashids('', 10); //decoded output is _always_ an array. + $hashids = new Hashids(config('ninja.hash_salt'), 10); //parse URL hash and set DB $segments = explode('-', $request->route('confirmation_code')); + if(!is_array($segments)) + return response()->json(['message' => 'Invalid confirmation code'], 403); + $hashed_db = $hashids->decode($segments[0]); MultiDB::setDB(MultiDB::DB_PREFIX.str_pad($hashed_db[0], 2, '0', STR_PAD_LEFT)); } return $next($request); + } } diff --git a/app/Http/Requests/Expense/StoreExpenseRequest.php b/app/Http/Requests/Expense/StoreExpenseRequest.php index 926ae7886d7d..1cb2e64624f8 100644 --- a/app/Http/Requests/Expense/StoreExpenseRequest.php +++ b/app/Http/Requests/Expense/StoreExpenseRequest.php @@ -59,7 +59,7 @@ class StoreExpenseRequest extends Request } if(array_key_exists('color', $input) && is_null($input['color'])) - $input['color'] = '#fff'; + $input['color'] = ''; $this->replace($input); } diff --git a/app/Http/Requests/ExpenseCategory/StoreExpenseCategoryRequest.php b/app/Http/Requests/ExpenseCategory/StoreExpenseCategoryRequest.php index b09a903bdbad..a896e8388927 100644 --- a/app/Http/Requests/ExpenseCategory/StoreExpenseCategoryRequest.php +++ b/app/Http/Requests/ExpenseCategory/StoreExpenseCategoryRequest.php @@ -43,7 +43,7 @@ class StoreExpenseCategoryRequest extends Request $input = $this->decodePrimaryKeys($input); if(array_key_exists('color', $input) && is_null($input['color'])) - $input['color'] = '#fff'; + $input['color'] = ''; $this->replace($input); } diff --git a/app/Http/Requests/ExpenseCategory/UpdateExpenseCategoryRequest.php b/app/Http/Requests/ExpenseCategory/UpdateExpenseCategoryRequest.php index 2b8e5a7449a7..921c5b14de3a 100644 --- a/app/Http/Requests/ExpenseCategory/UpdateExpenseCategoryRequest.php +++ b/app/Http/Requests/ExpenseCategory/UpdateExpenseCategoryRequest.php @@ -47,7 +47,7 @@ class UpdateExpenseCategoryRequest extends Request $input = $this->all(); if(array_key_exists('color', $input) && is_null($input['color'])) - $input['color'] = '#fff'; + $input['color'] = ''; $this->replace($input); } diff --git a/app/Http/Requests/Payments/PaymentNotificationWebhookRequest.php b/app/Http/Requests/Payments/PaymentNotificationWebhookRequest.php new file mode 100644 index 000000000000..54119add0454 --- /dev/null +++ b/app/Http/Requests/Payments/PaymentNotificationWebhookRequest.php @@ -0,0 +1,42 @@ +company_key); + + return true; + } + + public function rules() + { + return [ + // + ]; + } + +} diff --git a/app/Http/Requests/Project/StoreProjectRequest.php b/app/Http/Requests/Project/StoreProjectRequest.php index 9cc666e76c2d..b42dda0f62b5 100644 --- a/app/Http/Requests/Project/StoreProjectRequest.php +++ b/app/Http/Requests/Project/StoreProjectRequest.php @@ -51,7 +51,7 @@ class StoreProjectRequest extends Request if(array_key_exists('color', $input) && is_null($input['color'])) - $input['color'] = '#fff'; + $input['color'] = ''; $this->replace($input); } diff --git a/app/Http/Requests/Project/UpdateProjectRequest.php b/app/Http/Requests/Project/UpdateProjectRequest.php index d5fa2f0f52ea..0217de8264b1 100644 --- a/app/Http/Requests/Project/UpdateProjectRequest.php +++ b/app/Http/Requests/Project/UpdateProjectRequest.php @@ -49,7 +49,7 @@ class UpdateProjectRequest extends Request } if(array_key_exists('color', $input) && is_null($input['color'])) - $input['color'] = '#fff'; + $input['color'] = ''; $this->replace($input); } diff --git a/app/Http/Requests/Task/UpdateTaskRequest.php b/app/Http/Requests/Task/UpdateTaskRequest.php index 4b62444bd623..03f87b52a199 100644 --- a/app/Http/Requests/Task/UpdateTaskRequest.php +++ b/app/Http/Requests/Task/UpdateTaskRequest.php @@ -51,7 +51,7 @@ class UpdateTaskRequest extends Request } if(array_key_exists('color', $input) && is_null($input['color'])) - $input['color'] = '#fff'; + $input['color'] = ''; $this->replace($input); } diff --git a/app/Http/Requests/TaskStatus/StoreTaskStatusRequest.php b/app/Http/Requests/TaskStatus/StoreTaskStatusRequest.php index 8e476c5bb0a1..d3ba8750dab0 100644 --- a/app/Http/Requests/TaskStatus/StoreTaskStatusRequest.php +++ b/app/Http/Requests/TaskStatus/StoreTaskStatusRequest.php @@ -33,7 +33,7 @@ class StoreTaskStatusRequest extends Request $input = $this->all(); if(array_key_exists('color', $input) && is_null($input['color'])) - $input['color'] = '#fff'; + $input['color'] = ''; $this->replace($input); } diff --git a/app/Http/Requests/TaskStatus/UpdateTaskStatusRequest.php b/app/Http/Requests/TaskStatus/UpdateTaskStatusRequest.php index cbde21ac4e81..817e48d6a3e4 100644 --- a/app/Http/Requests/TaskStatus/UpdateTaskStatusRequest.php +++ b/app/Http/Requests/TaskStatus/UpdateTaskStatusRequest.php @@ -46,7 +46,7 @@ class UpdateTaskStatusRequest extends Request $input = $this->all(); if(array_key_exists('color', $input) && is_null($input['color'])) - $input['color'] = '#fff'; + $input['color'] = ''; $this->replace($input); } diff --git a/app/Jobs/Util/VersionCheck.php b/app/Jobs/Util/VersionCheck.php index cf57babc1a75..32dc7a2ac674 100644 --- a/app/Jobs/Util/VersionCheck.php +++ b/app/Jobs/Util/VersionCheck.php @@ -18,6 +18,7 @@ use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\SerializesModels; +use Carbon\Carbon; class VersionCheck implements ShouldQueue { @@ -49,7 +50,7 @@ class VersionCheck implements ShouldQueue if(!$account) return; - if($account->plan == 'white_label' && $account->plan_expires && $account->plan_expires->lt(now())){ + if($account->plan == 'white_label' && $account->plan_expires && Carbon::parse($account->plan_expires)->lt(now())){ $account->plan = null; $account->plan_expires = null; $account->save(); diff --git a/app/Listeners/Payment/PaymentNotification.php b/app/Listeners/Payment/PaymentNotification.php index a5aa1f07c8bc..d1a7a7b94748 100644 --- a/app/Listeners/Payment/PaymentNotification.php +++ b/app/Listeners/Payment/PaymentNotification.php @@ -71,7 +71,6 @@ class PaymentNotification implements ShouldQueue } - /*Google Analytics Track Revenue*/ if (isset($payment->company->google_analytics_key)) { $this->trackRevenue($event); diff --git a/app/Models/Account.php b/app/Models/Account.php index c062ef6dbf54..5984d1f15e9d 100644 --- a/app/Models/Account.php +++ b/app/Models/Account.php @@ -55,7 +55,7 @@ class Account extends BaseModel 'promo_expires', 'discount_expires', 'trial_started', - 'plan_expires' + // 'plan_expires' ]; const PLAN_FREE = 'free'; @@ -120,6 +120,11 @@ class Account extends BaseModel return $this->hasMany(CompanyUser::class); } + public function owner() + { + return $this->hasMany(CompanyUser::class)->where('is_owner', true)->first() ? $this->hasMany(CompanyUser::class)->where('is_owner', true)->first()->user : false; + } + public function getPlan() { return $this->plan ?: ''; diff --git a/app/Models/CompanyGateway.php b/app/Models/CompanyGateway.php index f79a15135f86..b9105b566e9e 100644 --- a/app/Models/CompanyGateway.php +++ b/app/Models/CompanyGateway.php @@ -371,6 +371,11 @@ class CompanyGateway extends BaseModel return $fee; } + public function webhookUrl() + { + return route('payment_webhook', ['company_key' => $this->company->company_key, 'company_gateway_id' => $this->hashed_id]); + } + /** * we need to average out the gateway fees across all the invoices * so lets iterate. @@ -412,4 +417,6 @@ class CompanyGateway extends BaseModel return $this ->where('id', $this->decodePrimaryKey($value))->firstOrFail(); } + + } diff --git a/app/Models/Presenters/UserPresenter.php b/app/Models/Presenters/UserPresenter.php index dd5f079db5ac..bf45ef5870f4 100644 --- a/app/Models/Presenters/UserPresenter.php +++ b/app/Models/Presenters/UserPresenter.php @@ -21,6 +21,10 @@ class UserPresenter extends EntityPresenter */ public function name() { + + if(!$this->entity) + return "No User Object Available"; + $first_name = isset($this->entity->first_name) ? $this->entity->first_name : ''; $last_name = isset($this->entity->last_name) ? $this->entity->last_name : ''; diff --git a/app/Models/SystemLog.php b/app/Models/SystemLog.php index ae05a07aec22..7614c01ecccc 100644 --- a/app/Models/SystemLog.php +++ b/app/Models/SystemLog.php @@ -67,6 +67,7 @@ class SystemLog extends Model const TYPE_CUSTOM = 306; const TYPE_BRAINTREE = 307; const TYPE_WEPAY = 309; + const TYPE_PAYFAST = 310; const TYPE_QUOTA_EXCEEDED = 400; diff --git a/app/Models/User.php b/app/Models/User.php index a7e34827cb59..8533c211784c 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -161,7 +161,7 @@ class User extends Authenticatable implements MustVerifyEmail public function setCompany($company) { $this->company = $company; - + return $this; } diff --git a/app/PaymentDrivers/BaseDriver.php b/app/PaymentDrivers/BaseDriver.php index 8060b9976bff..d7e2757f0794 100644 --- a/app/PaymentDrivers/BaseDriver.php +++ b/app/PaymentDrivers/BaseDriver.php @@ -548,6 +548,15 @@ class BaseDriver extends AbstractPaymentDriver ); } + public function genericWebhookUrl() + { + return route('payment_notification_webhook', [ + 'company_key' => $this->client->company->company_key, + 'company_gateway_id' => $this->encodePrimaryKey($this->company_gateway->id), + 'client' => $this->encodePrimaryKey($this->client->id), + ]); + } + /* Performs an extra iterate on the gatewayTypes() array and passes back only the enabled gateways*/ public function gatewayTypeEnabled($type) { diff --git a/app/PaymentDrivers/DriverTemplate.php b/app/PaymentDrivers/DriverTemplate.php index 083db80e8d68..36c0f9bf5b72 100644 --- a/app/PaymentDrivers/DriverTemplate.php +++ b/app/PaymentDrivers/DriverTemplate.php @@ -11,6 +11,7 @@ namespace App\PaymentDrivers; +use App\Http\Requests\Payments\PaymentWebhookRequest; use App\Models\ClientGatewayToken; use App\Models\GatewayType; use App\Models\Payment; @@ -39,6 +40,22 @@ class DriverTemplate extends BaseDriver const SYSTEM_LOG_TYPE = SystemLog::TYPE_STRIPE; //define a constant for your gateway ie TYPE_YOUR_CUSTOM_GATEWAY - set the const in the SystemLog model + public function init() + { + return $this; /* This is where you boot the gateway with your auth credentials*/ + } + + /* Returns an array of gateway types for the payment gateway */ + public function gatewayTypes(): array + { + $types = []; + + $types[] = GatewayType::CREDIT_CARD; + + return $types; + } + + /* Sets the payment method initialized */ public function setPaymentMethod($payment_method_id) { $class = self::$methods[$payment_method_id]; @@ -75,4 +92,8 @@ class DriverTemplate extends BaseDriver { return $this->payment_method->yourTokenBillingImplmentation(); //this is your custom implementation from here } + + public function processWebhookRequest(PaymentWebhookRequest $request, Payment $payment = null) + { + } } diff --git a/app/PaymentDrivers/PayFast/CreditCard.php b/app/PaymentDrivers/PayFast/CreditCard.php new file mode 100644 index 000000000000..ea5caebb56de --- /dev/null +++ b/app/PaymentDrivers/PayFast/CreditCard.php @@ -0,0 +1,282 @@ +payfast = $payfast; + } + +/* + $data = array(); + $data['merchant_id'] = $this->getMerchantId(); + $data['merchant_key'] = $this->getMerchantKey(); + $data['return_url'] = $this->getReturnUrl(); + $data['cancel_url'] = $this->getCancelUrl(); + $data['notify_url'] = $this->getNotifyUrl(); + + if ($this->getCard()) { + $data['name_first'] = $this->getCard()->getFirstName(); + $data['name_last'] = $this->getCard()->getLastName(); + $data['email_address'] = $this->getCard()->getEmail(); + } + + $data['m_payment_id'] = $this->getTransactionId(); + $data['amount'] = $this->getAmount(); + $data['item_name'] = $this->getDescription(); + $data['custom_int1'] = $this->getCustomInt1(); + $data['custom_int2'] = $this->getCustomInt2(); + $data['custom_int3'] = $this->getCustomInt3(); + $data['custom_int4'] = $this->getCustomInt4(); + $data['custom_int5'] = $this->getCustomInt5(); + $data['custom_str1'] = $this->getCustomStr1(); + $data['custom_str2'] = $this->getCustomStr2(); + $data['custom_str3'] = $this->getCustomStr3(); + $data['custom_str4'] = $this->getCustomStr4(); + $data['custom_str5'] = $this->getCustomStr5(); + + if ($this->getPaymentMethod()) { + $data['payment_method'] = $this->getPaymentMethod(); + } + + if (1 == $this->getSubscriptionType()) { + $data['subscription_type'] = $this->getSubscriptionType(); + $data['billing_date'] = $this->getBillingDate(); + $data['recurring_amount'] = $this->getRecurringAmount(); + $data['frequency'] = $this->getFrequency(); + $data['cycles'] = $this->getCycles(); + } + if (2 == $this->getSubscriptionType()) { + $data['subscription_type'] = $this->getSubscriptionType(); + } + + $data['passphrase'] = $this->getParameter('passphrase'); 123456789012aV + $data['signature'] = $this->generateSignature($data); + */ + + public function authorizeView($data) + { + $hash = Str::random(32); + + Cache::put($hash, 'cc_auth', 300); + + $data = [ + 'merchant_id' => $this->payfast->company_gateway->getConfigField('merchantId'), + 'merchant_key' => $this->payfast->company_gateway->getConfigField('merchantKey'), + 'return_url' => route('client.payment_methods.index'), + 'cancel_url' => route('client.payment_methods.index'), + 'notify_url' => $this->payfast->genericWebhookUrl(), + 'm_payment_id' => $hash, + 'amount' => 5, + 'item_name' => 'pre-auth', + 'item_description' => 'Credit Card Pre Authorization', + 'subscription_type' => 2, + 'passphrase' => $this->payfast->company_gateway->getConfigField('passphrase'), + ]; + + $data['signature'] = $this->payfast->generateSignature($data); + $data['gateway'] = $this->payfast; + $data['payment_endpoint_url'] = $this->payfast->endpointUrl(); + + return render('gateways.payfast.authorize', $data); + } + + /* + 'm_payment_id' => NULL, + 'pf_payment_id' => '1409993', + 'payment_status' => 'COMPLETE', + 'item_name' => 'pre-auth', + 'item_description' => NULL, + 'amount_gross' => '5.00', + 'amount_fee' => '-2.53', + 'amount_net' => '2.47', + 'custom_str1' => NULL, + 'custom_str2' => NULL, + 'custom_str3' => NULL, + 'custom_str4' => NULL, + 'custom_str5' => NULL, + 'custom_int1' => NULL, + 'custom_int2' => NULL, + 'custom_int3' => NULL, + 'custom_int4' => NULL, + 'custom_int5' => NULL, + 'name_first' => NULL, + 'name_last' => NULL, + 'email_address' => NULL, + 'merchant_id' => '10023100', + 'token' => '34b66bc2-3c54-9590-03ea-42ee8b89922a', + 'billing_date' => '2021-07-05', + 'signature' => 'ebdb4ca937d0e3f43462841c0afc6ad9', + 'q' => '/payment_notification_webhook/EhbnVYyzJZyccY85hcHIkIzNPI2rtHzznAyyyG73oSxZidAdN9gf8BvAKDomqeHp/4openRe7Az/WPe99p3eLy', + */ + public function authorizeResponse($request) + { + $data = $request->all(); + + $cgt = []; + $cgt['token'] = $data['token']; + $cgt['payment_method_id'] = GatewayType::CREDIT_CARD; + + $payment_meta = new \stdClass; + $payment_meta->exp_month = 'xx'; + $payment_meta->exp_year = 'xx'; + $payment_meta->brand = 'CC'; + $payment_meta->last4 = 'xxxx'; + $payment_meta->type = GatewayType::CREDIT_CARD; + + $cgt['payment_meta'] = $payment_meta; + + $token = $this->payfast->storeGatewayToken($cgt, []); + + return response()->json([], 200); + + } + + public function paymentView($data) + { + + $payfast_data = [ + 'merchant_id' => $this->payfast->company_gateway->getConfigField('merchantId'), + 'merchant_key' => $this->payfast->company_gateway->getConfigField('merchantKey'), + 'return_url' => route('client.payments.index'), + 'cancel_url' => route('client.payment_methods.index'), + 'notify_url' => $this->payfast->genericWebhookUrl(), + 'm_payment_id' => $data['payment_hash'], + 'amount' => $data['amount_with_fee'], + 'item_name' => 'purchase', + 'item_description' => ctrans('texts.invoices') . ': ' . collect($data['invoices'])->pluck('invoice_number'), + 'passphrase' => $this->payfast->company_gateway->getConfigField('passphrase'), + ]; + + $payfast_data['signature'] = $this->payfast->generateSignature($payfast_data); + $payfast_data['gateway'] = $this->payfast; + $payfast_data['payment_endpoint_url'] = $this->payfast->endpointUrl(); + + return render('gateways.payfast.pay', array_merge($data, $payfast_data)); + + } + + /* + [2021-07-05 11:21:24] local.INFO: array ( + 'm_payment_id' => 'B7G9Q2vPhqkLEoMwwY1paXvPGuFxpbDe', + 'pf_payment_id' => '1410364', + 'payment_status' => 'COMPLETE', + 'item_name' => 'purchase', + 'item_description' => 'Invoices: ["0001"]', + 'amount_gross' => '100.00', + 'amount_fee' => '-2.30', + 'amount_net' => '97.70', + 'custom_str1' => NULL, + 'custom_str2' => NULL, + 'custom_str3' => NULL, + 'custom_str4' => NULL, + 'custom_str5' => NULL, + 'custom_int1' => NULL, + 'custom_int2' => NULL, + 'custom_int3' => NULL, + 'custom_int4' => NULL, + 'custom_int5' => NULL, + 'name_first' => NULL, + 'name_last' => NULL, + 'email_address' => NULL, + 'merchant_id' => '10023100', + 'signature' => '3ed27638479fd65cdffb0f4910679d10', + 'q' => '/payment_notification_webhook/EhbnVYyzJZyccY85hcHIkIzNPI2rtHzznAyyyG73oSxZidAdN9gf8BvAKDomqeHp/4openRe7Az/WPe99p3eLy', + ) + + */ + public function paymentResponse(Request $request) + { + $response_array = $request->all(); + + $state = [ + 'server_response' => $request->all(), + 'payment_hash' => $request->input('m_payment_id'), + ]; + + $this->payfast->payment_hash->data = array_merge((array) $this->payfast->payment_hash->data, $state); + $this->payfast->payment_hash->save(); + + if($response_array['payment_status'] == 'COMPLETE') { + + $this->payfast->logSuccessfulGatewayResponse(['response' => $response_array, 'data' => $this->payfast->payment_hash], SystemLog::TYPE_PAYFAST); + + return $this->processSuccessfulPayment($response_array); + } + else { + $this->processUnsuccessfulPayment($response_array); + } + } + + private function processSuccessfulPayment($response_array) + { + + $payment_record = []; + $payment_record['amount'] = $response_array['amount_gross']; + $payment_record['payment_type'] = PaymentType::CREDIT_CARD_OTHER; + $payment_record['gateway_type_id'] = GatewayType::CREDIT_CARD; + $payment_record['transaction_reference'] = $response_array['pf_payment_id']; + + $payment = $this->payfast->createPayment($payment_record, Payment::STATUS_COMPLETED); + + return redirect()->route('client.payments.show', ['payment' => $this->payfast->encodePrimaryKey($payment->id)]); + } + + private function processUnsuccessfulPayment($server_response) + { + PaymentFailureMailer::dispatch($this->payfast->client, $server_response->cancellation_reason, $this->payfast->client->company, $server_response->amount); + + PaymentFailureMailer::dispatch( + $this->payfast->client, + $server_response, + $this->payfast->client->company, + $server_response['amount_gross'] + ); + + $message = [ + 'server_response' => $server_response, + 'data' => $this->payfast->payment_hash->data, + ]; + + SystemLogger::dispatch( + $message, + SystemLog::CATEGORY_GATEWAY_RESPONSE, + SystemLog::EVENT_GATEWAY_FAILURE, + SystemLog::TYPE_PAYFAST, + $this->payfast->client, + $this->payfast->client->company, + ); + + throw new PaymentFailed('Failed to process the payment.', 500); + } + +} \ No newline at end of file diff --git a/app/PaymentDrivers/PayFast/Token.php b/app/PaymentDrivers/PayFast/Token.php new file mode 100644 index 000000000000..fc1235a0d3cc --- /dev/null +++ b/app/PaymentDrivers/PayFast/Token.php @@ -0,0 +1,184 @@ +payfast = $payfast; + } + + // Attributes + // merchant-id + // integer, 8 char | REQUIRED + // Header, the Merchant ID as given by the PayFast system. + // version + // string | REQUIRED + // Header, the PayFast API version (i.e. v1). + // timestamp + // ISO-8601 date and time | REQUIRED + // Header, the current timestamp (YYYY-MM-DDTHH:MM:SS[+HH:MM]). + // signature + // string | REQUIRED + // Header, MD5 hash of the alphabetised submitted header and body variables, as well as the passphrase. Characters must be in lower case. + // amount + // integer | REQUIRED + // Body, the amount which the buyer must pay, in cents (ZAR), no decimals. + // item_name + // string, 100 char | REQUIRED + // Body, the name of the item being charged for. + // item_description + // string, 255 char | OPTIONAL + // Body, the description of the item being charged for. + // itn + // boolean | OPTIONAL + // Body, specify whether an ITN must be sent for the tokenization payment (true by default). + // m_payment_id + // string, 100 char | OPTIONAL + // Body, unique payment ID on the merchant’s system. + // cc_cvv + // numeric | OPTIONAL + + + public function tokenBilling(ClientGatewayToken $cgt, PaymentHash $payment_hash) + { + + $amount = array_sum(array_column($payment_hash->invoices(), 'amount')) + $payment_hash->fee_total; + $amount = round(($amount * pow(10, $this->payfast->client->currency()->precision)),0); + + $header =[ + 'merchant-id' => $this->payfast->company_gateway->getConfigField('merchantId'), + 'timestamp' => now()->format('c'), + 'version' => 'v1', + ]; + + nlog($header); + + $body = [ + 'amount' => $amount, + 'item_name' => 'purchase', + 'item_description' => ctrans('texts.invoices') . ': ' . collect($payment_hash->invoices())->pluck('invoice_number'), + 'm_payment_id' => $payment_hash->hash, + 'passphrase' => $this->payfast->company_gateway->getConfigField('passphrase'), + ]; + + $header['signature'] = $this->genSig(array_merge($header, $body)); + + nlog($header['signature']); + nlog($header['timestamp']); + nlog($this->payfast->company_gateway->getConfigField('merchantId')); + + $result = $this->send($header, $body, $cgt->token); + + nlog($result); + + // /*Refactor and push to BaseDriver*/ + // if ($data['response'] != null && $data['response']->getMessages()->getResultCode() == 'Ok') { + + // $response = $data['response']; + + // $this->storePayment($payment_hash, $data); + + // $vars = [ + // 'invoices' => $payment_hash->invoices(), + // 'amount' => $amount, + // ]; + + // $logger_message = [ + // 'server_response' => $response->getTransactionResponse()->getTransId(), + // 'data' => $this->formatGatewayResponse($data, $vars), + // ]; + + // SystemLogger::dispatch($logger_message, SystemLog::CATEGORY_GATEWAY_RESPONSE, SystemLog::EVENT_GATEWAY_SUCCESS, SystemLog::TYPE_AUTHORIZE, $this->authorize->client, $this->authorize->client->company); + + // return true; + // } else { + + // $vars = [ + // 'invoices' => $payment_hash->invoices(), + // 'amount' => $amount, + // ]; + + // $logger_message = [ + // 'server_response' => $response->getTransactionResponse()->getTransId(), + // 'data' => $this->formatGatewayResponse($data, $vars), + // ]; + + // PaymentFailureMailer::dispatch($this->authorize->client, $response->getTransactionResponse()->getTransId(), $this->authorize->client->company, $amount); + + // SystemLogger::dispatch($logger_message, SystemLog::CATEGORY_GATEWAY_RESPONSE, SystemLog::EVENT_GATEWAY_FAILURE, SystemLog::TYPE_AUTHORIZE, $this->authorize->client, $this->authorize->client->company); + + // return false; + // } + } + + private function genSig($data) + { + $fields = []; + + ksort($data); + + foreach($data as $key => $value) + { + if (!empty($data[$key])) { + $fields[$key] = $data[$key]; + } + } + + return md5(http_build_query($fields)); + } + + private function send($headers, $body, $token) + { + $client = new \GuzzleHttp\Client( + [ + 'headers' => $headers, + ]); + + try { + $response = $client->post("https://api.payfast.co.za/subscriptions/{$token}/adhoc?testing=true",[ + RequestOptions::JSON => ['body' => $body], RequestOptions::ALLOW_REDIRECTS => false + ]); + + return json_decode($response->getBody(),true); + } + catch(\Exception $e) + { + + nlog($e->getMessage()); + + } + } +} + diff --git a/app/PaymentDrivers/PayFastPaymentDriver.php b/app/PaymentDrivers/PayFastPaymentDriver.php new file mode 100644 index 000000000000..8e274eb8cfaf --- /dev/null +++ b/app/PaymentDrivers/PayFastPaymentDriver.php @@ -0,0 +1,202 @@ + CreditCard::class, + ]; + + const SYSTEM_LOG_TYPE = SystemLog::TYPE_PAYFAST; + + //developer resources + //https://sandbox.payfast.co.za/ + + public function gatewayTypes(): array + { + $types = []; + + if($this->client->currency()->code == 'ZAR') + $types[] = GatewayType::CREDIT_CARD; + + return $types; + } + + public function endpointUrl() + { + if($this->company_gateway->getConfigField('testMode')) + return 'https://sandbox.payfast.co.za/eng/process'; + + return 'https://www.payfast.co.za/eng/process'; + } + + public function init() + { + + try{ + + $this->payfast = new \PayFast\PayFastPayment( + [ + 'merchantId' => $this->company_gateway->getConfigField('merchantId'), + 'merchantKey' => $this->company_gateway->getConfigField('merchantKey'), + 'passPhrase' => $this->company_gateway->getConfigField('passPhrase'), + 'testMode' => $this->company_gateway->getConfigField('testMode') + ] + ); + + } catch(Exception $e) { + + echo '##PAYFAST## There was an exception: '.$e->getMessage(); + + } + + return $this; + } + + public function setPaymentMethod($payment_method_id) + { + $class = self::$methods[$payment_method_id]; + $this->payment_method = new $class($this); + return $this; + } + + public function authorizeView(array $data) + { + return $this->payment_method->authorizeView($data); + } + + public function authorizeResponse($request) + { + return $this->payment_method->authorizeResponse($request); + } + + public function processPaymentView(array $data) + { + return $this->payment_method->paymentView($data); //this is your custom implementation from here + } + + public function processPaymentResponse($request) + { + return $this->payment_method->paymentResponse($request); //this is your custom implementation from here + } + + public function refund(Payment $payment, $amount, $return_client_response = false) + { + return false; + } + + public function tokenBilling(ClientGatewayToken $cgt, PaymentHash $payment_hash) + { + return (new Token($this))->tokenBilling($cgt, $payment_hash); + } + + public function generateSignature($data) + { + $fields = array(); + + // specific order required by PayFast + // @see https://developers.payfast.co.za/documentation/#checkout-page + foreach (array('merchant_id', 'merchant_key', 'return_url', 'cancel_url', 'notify_url', 'name_first', + 'name_last', 'email_address', 'cell_number', + /** + * Transaction Details + */ + 'm_payment_id', 'amount', 'item_name', 'item_description', + /** + * Custom return data + */ + 'custom_int1', 'custom_int2', 'custom_int3', 'custom_int4', 'custom_int5', + 'custom_str1', 'custom_str2', 'custom_str3', 'custom_str4', 'custom_str5', + /** + * Email confirmation + */ + 'email_confirmation', 'confirmation_address', + /** + * Payment Method + */ + 'payment_method', + /** + * Subscriptions + */ + 'subscription_type', 'billing_date', 'recurring_amount', 'frequency', 'cycles', + /** + * Passphrase for md5 signature generation + */ + 'passphrase') as $key) { + if (!empty($data[$key])) { + $fields[$key] = $data[$key]; + } + } + + return md5(http_build_query($fields)); + } + + + public function processWebhookRequest(Request $request, Payment $payment = null) + { + + $data = $request->all(); + nlog($data); + + if(array_key_exists('m_payment_id', $data)) + { + + $hash = Cache::get($data['m_payment_id']); + + switch ($hash) + { + case 'cc_auth': + return $this->setPaymentMethod(GatewayType::CREDIT_CARD) + ->authorizeResponse($request); + break; + + default: + + $payment_hash = PaymentHash::whereRaw('BINARY `hash`= ?', [$data['m_payment_id']])->first(); + + return $this->setPaymentMethod(GatewayType::CREDIT_CARD) + ->setPaymentHash($payment_hash) + ->processPaymentResponse($request); + break; + } + + + } + + return response()->json([], 200); + + } +} diff --git a/app/PaymentDrivers/WePay/CreditCard.php b/app/PaymentDrivers/WePay/CreditCard.php index a943800e4a28..8e6a155ddc43 100644 --- a/app/PaymentDrivers/WePay/CreditCard.php +++ b/app/PaymentDrivers/WePay/CreditCard.php @@ -117,7 +117,6 @@ use WePayCommon; nlog("authorize the card first!"); $response = $this->wepay_payment_driver->wepay->request('credit_card/authorize', array( - // 'callback_uri' => route('payment_webhook', ['company_key' => $this->wepay_payment_driver->company_gateway->company->company_key, 'company_gateway_id' => $this->wepay_payment_driver->company_gateway->hashed_id]), 'client_id' => config('ninja.wepay.client_id'), 'client_secret' => config('ninja.wepay.client_secret'), 'credit_card_id' => (int)$request->input('credit_card_id'), diff --git a/app/Services/Invoice/HandleReversal.php b/app/Services/Invoice/HandleReversal.php index 40db69ad2d40..469597f242e9 100644 --- a/app/Services/Invoice/HandleReversal.php +++ b/app/Services/Invoice/HandleReversal.php @@ -71,7 +71,8 @@ class HandleReversal extends AbstractService $credit = CreditFactory::create($this->invoice->company_id, $this->invoice->user_id); $credit->client_id = $this->invoice->client_id; $credit->invoice_id = $this->invoice->id; - + $credit->date = now(); + $item = InvoiceItemFactory::create(); $item->quantity = 1; $item->cost = (float) $total_paid; diff --git a/app/Services/Subscription/SubscriptionService.php b/app/Services/Subscription/SubscriptionService.php index 348b0573938e..e83e69dbc721 100644 --- a/app/Services/Subscription/SubscriptionService.php +++ b/app/Services/Subscription/SubscriptionService.php @@ -77,7 +77,8 @@ class SubscriptionService $recurring_invoice->next_send_date = now(); $recurring_invoice = $recurring_invoice_repo->save([], $recurring_invoice); $recurring_invoice->next_send_date = $recurring_invoice->nextSendDate(); - + $recurring_invoice->auto_bill = $this->subscription->auto_bill; + /* Start the recurring service */ $recurring_invoice->service() ->start() diff --git a/app/Utils/PhantomJS/Phantom.php b/app/Utils/PhantomJS/Phantom.php index ca957ffe9bee..57495c8411a2 100644 --- a/app/Utils/PhantomJS/Phantom.php +++ b/app/Utils/PhantomJS/Phantom.php @@ -26,9 +26,9 @@ use App\Utils\CurlUtils; use App\Utils\HtmlEngine; use App\Utils\Traits\MakesHash; use Illuminate\Support\Facades\App; -use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Response; use Illuminate\Support\Facades\Storage; +use Illuminate\Support\Facades\Cache; use Illuminate\Support\Str; class Phantom diff --git a/composer.json b/composer.json index b90b87d4fcf5..056dee862f67 100644 --- a/composer.json +++ b/composer.json @@ -64,6 +64,7 @@ "maennchen/zipstream-php": "^1.2", "nwidart/laravel-modules": "^8.0", "omnipay/paypal": "^3.0", + "payfast/payfast-php-sdk": "^1.1", "pragmarx/google2fa": "^8.0", "predis/predis": "^1.1", "sentry/sentry-laravel": "^2", diff --git a/composer.lock b/composer.lock index 40bf18944965..99a3b5134fb9 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "16a38ffa3774d9d28a9f4c49366baac0", + "content-hash": "25a0cbf18fc238305c7ea640c49ba89a", "packages": [ { "name": "asm/php-ansible", @@ -159,16 +159,16 @@ }, { "name": "aws/aws-sdk-php", - "version": "3.185.7", + "version": "3.185.10", "source": { "type": "git", "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "7c0cd260e749374b5df247c4768c8f33f9a604e4" + "reference": "667a83e4a18cb75db3ce74162efc97123da96261" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/7c0cd260e749374b5df247c4768c8f33f9a604e4", - "reference": "7c0cd260e749374b5df247c4768c8f33f9a604e4", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/667a83e4a18cb75db3ce74162efc97123da96261", + "reference": "667a83e4a18cb75db3ce74162efc97123da96261", "shasum": "" }, "require": { @@ -243,9 +243,9 @@ "support": { "forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80", "issues": "https://github.com/aws/aws-sdk-php/issues", - "source": "https://github.com/aws/aws-sdk-php/tree/3.185.7" + "source": "https://github.com/aws/aws-sdk-php/tree/3.185.10" }, - "time": "2021-07-06T18:16:14+00:00" + "time": "2021-07-09T19:21:22+00:00" }, { "name": "bacon/bacon-qr-code", @@ -4113,16 +4113,16 @@ }, { "name": "league/oauth1-client", - "version": "v1.9.0", + "version": "v1.9.1", "source": { "type": "git", "url": "https://github.com/thephpleague/oauth1-client.git", - "reference": "1e7e6be2dc543bf466236fb171e5b20e1b06aee6" + "reference": "19a3ce488bb1547c906209e8293199ec34eaa5b1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/oauth1-client/zipball/1e7e6be2dc543bf466236fb171e5b20e1b06aee6", - "reference": "1e7e6be2dc543bf466236fb171e5b20e1b06aee6", + "url": "https://api.github.com/repos/thephpleague/oauth1-client/zipball/19a3ce488bb1547c906209e8293199ec34eaa5b1", + "reference": "19a3ce488bb1547c906209e8293199ec34eaa5b1", "shasum": "" }, "require": { @@ -4182,9 +4182,9 @@ ], "support": { "issues": "https://github.com/thephpleague/oauth1-client/issues", - "source": "https://github.com/thephpleague/oauth1-client/tree/v1.9.0" + "source": "https://github.com/thephpleague/oauth1-client/tree/v1.9.1" }, - "time": "2021-01-20T01:40:53+00:00" + "time": "2021-07-07T22:54:46+00:00" }, { "name": "league/omnipay", @@ -4251,16 +4251,16 @@ }, { "name": "livewire/livewire", - "version": "v2.5.1", + "version": "v2.5.3", "source": { "type": "git", "url": "https://github.com/livewire/livewire.git", - "reference": "a4ffb135693e7982e5b982ca203f5dc7a7ae1126" + "reference": "1ca6757c78dbead4db7f52a72dabb8b27efcb3f6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/livewire/livewire/zipball/a4ffb135693e7982e5b982ca203f5dc7a7ae1126", - "reference": "a4ffb135693e7982e5b982ca203f5dc7a7ae1126", + "url": "https://api.github.com/repos/livewire/livewire/zipball/1ca6757c78dbead4db7f52a72dabb8b27efcb3f6", + "reference": "1ca6757c78dbead4db7f52a72dabb8b27efcb3f6", "shasum": "" }, "require": { @@ -4311,7 +4311,7 @@ "description": "A front-end framework for Laravel.", "support": { "issues": "https://github.com/livewire/livewire/issues", - "source": "https://github.com/livewire/livewire/tree/v2.5.1" + "source": "https://github.com/livewire/livewire/tree/v2.5.3" }, "funding": [ { @@ -4319,7 +4319,7 @@ "type": "github" } ], - "time": "2021-06-15T13:24:48+00:00" + "time": "2021-07-08T13:58:45+00:00" }, { "name": "maennchen/zipstream-php", @@ -5334,6 +5334,57 @@ }, "time": "2020-10-15T08:29:30+00:00" }, + { + "name": "payfast/payfast-php-sdk", + "version": "v1.1.2", + "source": { + "type": "git", + "url": "https://github.com/PayFast/payfast-php-sdk.git", + "reference": "1372980e38f381b84eed7eb46a40d5819a4fe58c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PayFast/payfast-php-sdk/zipball/1372980e38f381b84eed7eb46a40d5819a4fe58c", + "reference": "1372980e38f381b84eed7eb46a40d5819a4fe58c", + "shasum": "" + }, + "require": { + "ext-json": "*", + "guzzlehttp/guzzle": ">=6.0.0", + "php": ">=7.2.5" + }, + "require-dev": { + "phpunit/phpunit": "^9" + }, + "type": "library", + "autoload": { + "psr-4": { + "PayFast\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Claire Grant", + "email": "claire.grant@payfast.co.za" + } + ], + "description": "PayFast PHP Library", + "keywords": [ + "api", + "onsite", + "payfast", + "php" + ], + "support": { + "issues": "https://github.com/PayFast/payfast-php-sdk/issues", + "source": "https://github.com/PayFast/payfast-php-sdk/tree/v1.1.2" + }, + "time": "2021-03-15T19:58:26+00:00" + }, { "name": "php-http/client-common", "version": "2.4.0", @@ -7319,16 +7370,16 @@ }, { "name": "stripe/stripe-php", - "version": "v7.87.0", + "version": "v7.88.0", "source": { "type": "git", "url": "https://github.com/stripe/stripe-php.git", - "reference": "9392f03cb8d8803bf8273378ce42d5cbbf1e24fc" + "reference": "7203d00ba9b09830c0c5d5c06a9558db43b8e0ea" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/stripe/stripe-php/zipball/9392f03cb8d8803bf8273378ce42d5cbbf1e24fc", - "reference": "9392f03cb8d8803bf8273378ce42d5cbbf1e24fc", + "url": "https://api.github.com/repos/stripe/stripe-php/zipball/7203d00ba9b09830c0c5d5c06a9558db43b8e0ea", + "reference": "7203d00ba9b09830c0c5d5c06a9558db43b8e0ea", "shasum": "" }, "require": { @@ -7374,9 +7425,9 @@ ], "support": { "issues": "https://github.com/stripe/stripe-php/issues", - "source": "https://github.com/stripe/stripe-php/tree/v7.87.0" + "source": "https://github.com/stripe/stripe-php/tree/v7.88.0" }, - "time": "2021-06-30T18:22:47+00:00" + "time": "2021-07-09T20:01:03+00:00" }, { "name": "swiftmailer/swiftmailer", diff --git a/config/logging.php b/config/logging.php index 9adace7c1ce4..4c6c4233642f 100644 --- a/config/logging.php +++ b/config/logging.php @@ -109,7 +109,6 @@ return [ 'gelf' => [ 'driver' => 'custom', - 'via' => \Hedii\LaravelGelfLogger\GelfLoggerFactory::class, // This optional option determines the processors that should be diff --git a/database/migrations/2021_07_10_085821_activate_payfast_payment_driver.php b/database/migrations/2021_07_10_085821_activate_payfast_payment_driver.php new file mode 100644 index 000000000000..b66c5c64aae4 --- /dev/null +++ b/database/migrations/2021_07_10_085821_activate_payfast_payment_driver.php @@ -0,0 +1,29 @@ +update(['visible' => 1]); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + // + } +} diff --git a/resources/views/portal/ninja2020/gateways/payfast/authorize.blade.php b/resources/views/portal/ninja2020/gateways/payfast/authorize.blade.php new file mode 100644 index 000000000000..5dbba361c5a1 --- /dev/null +++ b/resources/views/portal/ninja2020/gateways/payfast/authorize.blade.php @@ -0,0 +1,48 @@ +@extends('portal.ninja2020.layout.payments', ['gateway_title' => ctrans('texts.credit_card'), 'card_title' => ctrans('texts.credit_card')]) + +@section('gateway_head') + + +@endsection + +@section('gateway_content') +
+@endsection + +@section('gateway_footer') + +@endsection diff --git a/resources/views/portal/ninja2020/gateways/payfast/pay.blade.php b/resources/views/portal/ninja2020/gateways/payfast/pay.blade.php new file mode 100644 index 000000000000..9b3c4a889c3b --- /dev/null +++ b/resources/views/portal/ninja2020/gateways/payfast/pay.blade.php @@ -0,0 +1,74 @@ +@extends('portal.ninja2020.layout.payments', ['gateway_title' => ctrans('texts.credit_card'), 'card_title' => ctrans('texts.credit_card')]) + +@section('gateway_head') + + +@endsection + +@section('gateway_content') + +@endsection + +@section('gateway_footer') + +@endsection + diff --git a/routes/api.php b/routes/api.php index 58aa8e1e798c..18e97cf40517 100644 --- a/routes/api.php +++ b/routes/api.php @@ -198,6 +198,10 @@ Route::match(['get', 'post'], 'payment_webhook/{company_key}/{company_gateway_id ->middleware(['guest']) ->name('payment_webhook'); +Route::match(['get', 'post'], 'payment_notification_webhook/{company_key}/{company_gateway_id}/{client}', 'PaymentNotificationWebhookController') + ->middleware(['guest']) + ->name('payment_notification_webhook'); + Route::post('api/v1/postmark_webhook', 'PostMarkController@webhook'); Route::get('token_hash_router', 'OneTimeTokenController@router'); Route::get('webcron', 'WebCronController@index'); diff --git a/tests/Unit/CentConversionTest.php b/tests/Unit/CentConversionTest.php new file mode 100644 index 000000000000..4823f6c788a1 --- /dev/null +++ b/tests/Unit/CentConversionTest.php @@ -0,0 +1,67 @@ +assertEquals(1020, $amount); + + $amount = 2; + $amount = round(($amount * pow(10, $precision)),0); + + $this->assertEquals(200, $amount); + + $amount = 2.12; + $amount = round(($amount * pow(10, $precision)),0); + + $this->assertEquals(212, $amount); + } + + + public function testBcMathWay() + { + + $amount = 64.99; + $amount = bcmul($amount, 100); + + $this->assertEquals(6499, $amount); + + $amount = 2; + $amount = bcmul($amount, 100); + + $this->assertEquals(200, $amount); + + $amount = 2.12; + $amount = bcmul($amount, 100); + + $this->assertEquals(212, $amount); + } +} diff --git a/tests/Unit/CreditBalanceTest.php b/tests/Unit/CreditBalanceTest.php index 307a50f45370..84c941c75742 100644 --- a/tests/Unit/CreditBalanceTest.php +++ b/tests/Unit/CreditBalanceTest.php @@ -16,6 +16,7 @@ use App\Models\Company; use App\Models\Credit; use App\Models\CreditInvitation; use App\Models\User; +use App\Utils\Traits\AppSetup; use Tests\MockUnitData; use Tests\TestCase; @@ -25,6 +26,7 @@ use Tests\TestCase; class CreditBalanceTest extends TestCase { use MockUnitData; + use AppSetup; public function setUp() :void { @@ -35,6 +37,8 @@ class CreditBalanceTest extends TestCase }); $this->makeTestData(); + + $this->buildCache(true); } public function testCreditBalance()