Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "fleetbase/core-api",
"version": "1.6.52",
"version": "1.6.53",
"description": "Core Framework and Resources for Fleetbase API",
"keywords": [
"fleetbase",
Expand Down
2 changes: 2 additions & 0 deletions config/sms.php
Original file line number Diff line number Diff line change
Expand Up @@ -110,11 +110,13 @@

'custom_http' => [
'enabled' => env('CUSTOM_HTTP_SMS_ENABLED', false),
'method' => env('CUSTOM_HTTP_SMS_METHOD', 'POST'),
'url' => env('CUSTOM_HTTP_SMS_URL', ''),
'from' => env('CUSTOM_HTTP_SMS_FROM', ''),
'auth_header' => env('CUSTOM_HTTP_SMS_AUTH_HEADER', ''),
'auth_token' => env('CUSTOM_HTTP_SMS_AUTH_TOKEN', ''),
'headers' => [],
'query_params' => [],
'body' => [
'to' => '{{to}}',
'text' => '{{text}}',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
<?php

use Fleetbase\Models\Transaction;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;

return new class extends Migration {
public function up(): void
{
Schema::table('transactions', function (Blueprint $table) {
if (!Schema::hasColumn('transactions', 'settlement_status')) {
$table->string('settlement_status', 32)
->default(Transaction::SETTLEMENT_STATUS_UNPAID)
->after('status')
->index('transactions_settlement_status_index');
}
});

DB::table('transactions')
->whereNull('settlement_status')
->update(['settlement_status' => Transaction::SETTLEMENT_STATUS_UNPAID]);

DB::table('transactions')
->where('status', 'completed')
->update(['status' => Transaction::STATUS_SUCCESS]);

DB::table('transactions')
->where('status', 'paid')
->update([
'status' => Transaction::STATUS_SUCCESS,
'settlement_status' => Transaction::SETTLEMENT_STATUS_PAID,
'settled_at' => DB::raw('COALESCE(settled_at, updated_at, created_at)'),
]);

DB::table('transactions')
->whereIn('type', [
Transaction::TYPE_INVOICE_PAYMENT,
Transaction::TYPE_WALLET_DEPOSIT,
Transaction::TYPE_WALLET_WITHDRAWAL,
Transaction::TYPE_WALLET_TRANSFER_IN,
Transaction::TYPE_WALLET_TRANSFER_OUT,
'deposit',
'withdrawal',
'transfer_in',
'transfer_out',
])
->where('status', Transaction::STATUS_SUCCESS)
->where('settlement_status', Transaction::SETTLEMENT_STATUS_UNPAID)
->update([
'settlement_status' => Transaction::SETTLEMENT_STATUS_PAID,
'settled_at' => DB::raw('COALESCE(settled_at, updated_at, created_at)'),
]);
}

public function down(): void
{
Schema::table('transactions', function (Blueprint $table) {
if (Schema::hasColumn('transactions', 'settlement_status')) {
$table->dropIndex('transactions_settlement_status_index');
$table->dropColumn('settlement_status');
}
});
}
};
10 changes: 9 additions & 1 deletion src/Http/Controllers/Internal/v1/UserController.php
Original file line number Diff line number Diff line change
Expand Up @@ -486,7 +486,10 @@ public function resendInvitation(ResendUserInvite $request)
#[SkipAuthorizationCheck]
public function acceptCompanyInvite(AcceptCompanyInvite $request)
{
$invite = Invite::where('code', $request->input('code'))->with(['subject'])->first();
$invite = $this->findCompanyInvite($request->input('code'));
if (!$invite) {
return response()->error('This invitation has already been accepted or is no longer available.');
}

// get invited email
$email = Arr::first($invite->recipients);
Expand Down Expand Up @@ -563,6 +566,11 @@ public function acceptCompanyInvite(AcceptCompanyInvite $request)
]);
}

protected function findCompanyInvite(string $code): ?Invite
{
return Invite::where('code', $code)->with(['subject'])->first();
}

/**
* Deactivates a user.
*
Expand Down
2 changes: 1 addition & 1 deletion src/Http/Requests/Internal/AcceptCompanyInvite.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public function authorize()
public function rules()
{
return [
'code' => ['required', 'exists:invites,code'],
'code' => ['required'],
];
}
}
48 changes: 47 additions & 1 deletion src/Models/Transaction.php
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ class Transaction extends Model
'type',
'direction',
'status',
'settlement_status',

// Monetary (all in smallest currency unit / cents)
'amount',
Expand Down Expand Up @@ -190,6 +191,16 @@ class Transaction extends Model
public const STATUS_VOIDED = 'voided';
public const STATUS_EXPIRED = 'expired';

// =========================================================================
// Settlement Status Constants
// =========================================================================

public const SETTLEMENT_STATUS_UNPAID = 'unpaid';
public const SETTLEMENT_STATUS_PARTIALLY_PAID = 'partially_paid';
public const SETTLEMENT_STATUS_PAID = 'paid';
public const SETTLEMENT_STATUS_PARTIALLY_REFUNDED = 'partially_refunded';
public const SETTLEMENT_STATUS_REFUNDED = 'refunded';

// =========================================================================
// Type Constants — Platform-wide taxonomy
// =========================================================================
Expand Down Expand Up @@ -352,6 +363,14 @@ public function scopeFailed($query)
return $query->where('status', self::STATUS_FAILED);
}

/**
* Scope to paid or otherwise settled transactions.
*/
public function scopeSettled($query)
{
return $query->where('settlement_status', self::SETTLEMENT_STATUS_PAID);
}

/**
* Scope to a specific transaction type.
*/
Expand Down Expand Up @@ -485,7 +504,34 @@ public function isReversed(): bool
*/
public function isSettled(): bool
{
return $this->settled_at !== null;
return $this->settlement_status === self::SETTLEMENT_STATUS_PAID || $this->settled_at !== null;
}

/**
* Whether this transaction has not been settled.
*/
public function isUnpaid(): bool
{
return $this->settlement_status === self::SETTLEMENT_STATUS_UNPAID;
}

/**
* Whether this transaction has been partially settled.
*/
public function isPartiallyPaid(): bool
{
return $this->settlement_status === self::SETTLEMENT_STATUS_PARTIALLY_PAID;
}

/**
* Whether this transaction has been partially or fully refunded.
*/
public function isRefunded(): bool
{
return in_array($this->settlement_status, [
self::SETTLEMENT_STATUS_PARTIALLY_REFUNDED,
self::SETTLEMENT_STATUS_REFUNDED,
], true);
}

/**
Expand Down
27 changes: 26 additions & 1 deletion src/Routing/RESTRegistrar.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ class RESTRegistrar extends ResourceRegistrar
*
* @var string[]
*/
protected $resourceDefaults = ['query', 'find', 'create', 'update', 'delete'];
protected $resourceDefaults = ['query', 'create', 'bulkDelete', 'find', 'update', 'delete'];

/**
* Build a set of prefixed resource routes.
Expand Down Expand Up @@ -85,6 +85,31 @@ protected function addResourceFind($name, $id, $controller, $options)
return $this->router->get($uri, $action)->name($uniqueName);
}

/**
* Add the bulk delete method for a resourceful route.
*
* DELETE /resource/bulk-delete
*
* @param string $name
* @param string $id
* @param string $controller
* @param array $options
*
* @return \Illuminate\Routing\Route
*/
protected function addResourceBulkDelete($name, $id, $controller, $options)
{
$name = $this->getShallowName($name, $options);

$uri = $this->getResourceUri($name) . '/bulk-delete';

$action = $this->getResourceAction($name, $controller, 'bulkDelete', $options);

$uniqueName = $this->getUniqueRouteName(['bulk-delete', 'delete'], $name, $options);

return $this->router->delete($uri, $action)->name($uniqueName);
}

/**
* Add the create method for a resourceful route.
*
Expand Down
30 changes: 26 additions & 4 deletions src/Services/CustomHttpSmsService.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,11 @@ public function send(string $to, string $text, ?string $from = null, array $opti
'unique_id' => data_get($options, 'unique_id', data_get($options, 'reference', '')),
];

$url = $this->renderTemplate((string) data_get($this->config, 'url'), $variables);
$headers = $this->renderTemplateValues((array) data_get($this->config, 'headers', []), $variables);
$body = $this->renderTemplateValues((array) data_get($this->config, 'body', [
$url = $this->renderTemplate((string) data_get($this->config, 'url'), $variables);
$method = strtoupper((string) data_get($this->config, 'method', 'POST'));
$headers = $this->renderTemplateValues((array) data_get($this->config, 'headers', []), $variables);
$queryParams = $this->renderTemplateValues((array) data_get($this->config, 'query_params', []), $variables);
$body = $this->renderTemplateValues((array) data_get($this->config, 'body', [
'to' => '{{to}}',
'text' => '{{text}}',
'from' => '{{from}}',
Expand All @@ -43,7 +45,12 @@ public function send(string $to, string $text, ?string $from = null, array $opti

Log::info('Sending SMS via custom HTTP gateway', ['to' => $to, 'url' => $url]);

$response = Http::withHeaders($headers)->asJson()->post($url, $body);
$request = Http::withHeaders($headers);
$response = match ($method) {
'GET' => $request->get($url, $queryParams),
'POST' => $request->asJson()->post($this->appendQueryParams($url, $queryParams), $body),
default => throw new \InvalidArgumentException("Unsupported custom HTTP SMS method: {$method}"),
};
$payload = $response->json();

if ($response->successful()) {
Expand Down Expand Up @@ -82,6 +89,11 @@ protected function validateParameters(string $to, string $text): void
if (empty($text)) {
throw new \InvalidArgumentException('Message text cannot be empty');
}

$method = strtoupper((string) data_get($this->config, 'method', 'POST'));
if (!in_array($method, ['GET', 'POST'], true)) {
throw new \InvalidArgumentException('Custom HTTP SMS method must be GET or POST');
}
}

protected function renderTemplateValues(array $values, array $variables): array
Expand All @@ -105,4 +117,14 @@ protected function renderTemplate(string $template, array $variables): string

return $template;
}

protected function appendQueryParams(string $url, array $queryParams = []): string
{
$queryParams = array_filter($queryParams, static fn ($value) => $value !== null && $value !== '');
if (empty($queryParams)) {
return $url;
}

return $url . (str_contains($url, '?') ? '&' : '?') . http_build_query($queryParams);
}
}
1 change: 1 addition & 0 deletions src/Support/EnvironmentMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ class EnvironmentMapper
'SMPP_PASSWORD' => 'services.sms.providers.smpp.password',
'SMPP_SOURCE_ADDR' => 'services.sms.providers.smpp.source_addr',
'CUSTOM_HTTP_SMS_URL' => 'services.sms.providers.custom_http.url',
'CUSTOM_HTTP_SMS_METHOD' => 'services.sms.providers.custom_http.method',
'CUSTOM_HTTP_SMS_AUTH_HEADER' => 'services.sms.providers.custom_http.auth_header',
'CUSTOM_HTTP_SMS_AUTH_TOKEN' => 'services.sms.providers.custom_http.auth_token',
'GOOGLE_MAPS_API_KEY' => 'services.google_maps.api_key',
Expand Down
4 changes: 0 additions & 4 deletions src/routes.php
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,6 @@ function ($router) {
$router->fleetbaseRoutes(
'api-credentials',
function ($router, $controller) {
$router->delete('bulk-delete', $controller('bulkDelete'));
$router->patch('roll/{id}', $controller('roll'));
$router->get('export', $controller('export'));
}
Expand Down Expand Up @@ -263,7 +262,6 @@ function ($router, $controller) {
$router->patch('activate/{id}', $controller('activate'));
$router->patch('verify/{id}', $controller('verify'));
$router->delete('remove-from-company/{id}', $controller('removeFromCompany'));
$router->delete('bulk-delete', $controller('bulkDelete'));
$router->post('invite-user', $controller('inviteUser'));
$router->post('resend-invite', $controller('resendInvitation'));
$router->post('set-password', $controller('setCurrentUserPassword'));
Expand Down Expand Up @@ -309,7 +307,6 @@ function ($router, $controller) {
$router->get('get-settings', $controller('getSettings'));
$router->put('mark-as-read', $controller('markAsRead'));
$router->put('mark-all-read', $controller('markAllAsRead'));
$router->delete('bulk-delete', $controller('bulkDelete'));
$router->post('save-settings', $controller('saveSettings'));
});
$router->fleetbaseRoutes('dashboards', function ($router, $controller) {
Expand Down Expand Up @@ -348,7 +345,6 @@ function ($router, $controller) {
$router->fleetbaseRoutes('templates', function ($router, $controller) {
$router->get('context-schemas', $controller('contextSchemas'));
$router->post('preview', $controller('previewUnsaved'));
$router->delete('bulk-delete', $controller('bulkDelete'));
$router->post('{id}/preview', $controller('preview'));
$router->post('{id}/render', $controller('render'));
});
Expand Down
Loading
Loading