sanjabteam/baloot
Baloot is a Laravel e-commerce package from SanjabTeam that helps PHP developers add products, categories, carts, orders, and payment flows to applications, with extensible tools for building and managing online stores.
Read this first: the package is archived (last release April 2021) and was built for the Laravel 6/7/8 era. Before writing any code, open composer.json in the repo and check the php, laravel/framework, and nesbot/carbon constraints against your app. If you are on Laravel 9+, treat this as a reference implementation to copy from, not a dependency to install.
Minimal adoption path:
Install only if constraints allow
composer require sanjabteam/baloot
The service provider is auto-discovered. If there is a config file, publish it immediately so you can see the actual knobs rather than guessing:
php artisan vendor:publish --provider="SanjabTeam\Baloot\BalootServiceProvider"
Where to look first
README.md — it is in Persian; keep a translator handy but the code samples are readable on their own.src/ — look for the validation rules directory, the helpers/facade file, and any config/ file. These three tell you the entire surface area within ten minutes.tests/ — if present, this is the most reliable documentation of edge-case behavior (checksums, leap years, digit normalization).First use case — an Iranian registration form. This is the highest-value 20 minutes you will get out of the package: validate a national ID (کد ملی), a mobile number, and render a Jalali date.
// app/Http/Requests/RegisterRequest.php
public function rules(): array
{
return [
'national_code' => ['required', 'national_code'], // verify exact rule key in src/
'mobile' => ['required', 'mobile'],
'birth_date' => ['required', 'jalali_date'],
];
}
Confirm the actual rule names before committing. Grep the source rather than trusting the README — rule aliases were the most likely thing to drift between releases:
grep -rn "function validate" vendor/sanjabteam/baloot/src
If you are on Laravel 9+ and only need the checksum/localization logic, the pragmatic move is to vendor a copy of the specific classes you need (they are usually small, dependency-free static helpers) into app/Support/ and add tests. That removes the archived-dependency risk entirely and keeps the business logic you actually wanted.
Validation rules in FormRequests, never in controllers. The package's main value is Iranian-specific validation. Bind it at the request boundary so every endpoint inherits the same behavior:
final class UpdateProfileRequest extends FormRequest
{
public function rules(): array
{
return [
'national_code' => ['nullable', 'national_code', 'unique:users,national_code,' . $this->user()->id],
'sheba' => ['nullable', 'sheba'],
'card_number' => ['nullable', 'card_number'],
];
}
public function messages(): array
{
return [
'national_code.national_code' => 'کد ملی وارد شده معتبر نیست.',
];
}
}
Normalize before you validate. Users paste Persian/Arabic digits and stray spaces. Wrap the package rule in a prepareForValidation() that converts ۰-۹ and ٠-٩ to ASCII and strips -/spaces from card and Sheba numbers. Do not rely on the package rule to be lenient — assume it is strict.
protected function prepareForValidation(): void
{
$this->merge([
'national_code' => $this->toEnglishDigits($this->input('national_code')),
'sheba' => str_replace(['IR', ' ', '-'], '', (string) $this->input('sheba')),
]);
}
Isolate Jalali dates behind your own cast. Do not let Jalali formatting leak into Blade templates and API resources ad hoc. Either use the package's conversion helpers inside a custom cast, or — more robustly — do the conversion yourself with morilog/jalali and use baloot only for the input-validation side:
// app/Casts/JalaliDate.php — the single place conversion happens
public function get($model, string $key, $value, array $attributes): ?string
{
return $value ? Baloot::toJalali(Carbon::parse($value))->format('Y/m/d') : null;
}
This means a future swap of the package is a one-file change.
Treat it as a facade over your own domain services. Wrap the static/facade calls in small app/Support/IranianIdentity.php methods (isValidNationalCode, normalizeMobile, formatToman). Benefits: you can unit test without the package, mock it, and you get grep-able call sites when you eventually replace it.
Config-driven behavior for province/city lists. If the package ships geographic data, load it once into cache at boot rather than per-request:
$provinces = Cache::rememberForever('iran.provinces', fn () => Baloot::provinces());
Testing. Assert on the outcome rules you care about, not the package internals, so your test suite survives removal of the package:
it('rejects an invalid national code', function () {
$this->post('/register', ['national_code' => '1234567890'])
->assertSessionHasErrors('national_code');
});
Include the classic trap cases as fixtures: 0000000000, 1111111111, all-same-digit codes, and a genuine valid code. Naive checksum implementations pass repeated-digit strings; you want to know which behavior yours has.
The archive status is the top risk. No security patches, no Laravel 10/11 support, and transitive dependency conflicts with modern nesbot/carbon and illuminate/* are likely. Run composer why-not sanjabteam/baloot after install attempts to see the real blocker. If you adopt it for a long-lived project, pin it and add a ticket to replace it.
Digit normalization is bidirectional and bites in both directions. Persian ی vs Arabic ي, and Persian ک vs Arabic ك, are different bytes. A name or city comparison that looks correct on screen will fail ===. Normalize on input, and for search use a normalization pass on the DB column or a generated column rather than LIKE on raw text.
National ID checksum edge cases. The standard algorithm accepts some nonsensical inputs (notably repeated digits) unless explicitly guarded. Verify the package's guard by testing, and add your own not_in rule for sentinel values like 0000000000 if it does not.
Iranian bank cards do not use the Luhn algorithm. If you see a Luhn check anywhere in your stack rejecting valid Iranian cards, it is wrong. Validate length, 6xxxxxxxxxxxxxxx prefix against the current bank BIN list, and checksum only if the package documents one.
Sheba vs IBAN. Iranian Sheba is IR + 24 digits and does have a mod-97 check. If the package stores a normalized 24-digit form, keep the display format (IR...) purely in the presentation layer — never store the formatted version, and never assume the package's normalize direction without reading the source.
Timezone and DST. Iran abolished daylight saving in 2022. Set config('app.timezone') = 'Asia/Tehran' explicitly, and be aware that a 2021-era package may carry an old DST table or none. For anything that compares "today" against stored UTC timestamps, compute the Jalali date from a Tehran-local Carbon instance, not from UTC.
Jalali month-length traps. Months 1–6 have 31 days, 7–11 have 30, month 12 has 29 or 30 depending on leap year. Round-tripping a Gregorian date through conversion helpers can silently roll 1403/12/30 into the next year. Test Esfand 30 in both leap and non-leap years.
Money formatting is presentation, not arithmetic. Always store amounts as integer Rial (the canonical unit). Formatting helpers that emit "تومان" imply a divide-by-10 — keep that division in the presenter so no invoice math ever mixes the two units.
Mobile prefix drift. Iranian prefixes have expanded (e.g. 0990, 0991) since 2021. If the package hardcodes a prefix regex, it will reject new valid numbers. Override the rule in your app with a preg_match('/^09\d{9}$/') base plus a prefix allowlist you control.
Debugging failed validation silently. When a package rule fails unexpectedly, check in this order: (1) is prepareForValidation actually running for this request, (2) did the middleware convert '' to null and trip a required, (3) is the rule failing on the raw attribute before your normalizer runs. A quick dd($request->all()) inside the FormRequest resolves most of these in seconds.
Extension points worth using. Rather than forking, prefer: a custom Rule object that wraps the package call, a Blade directive or component for Jalali rendering, and a config override for any prefix/locale lists. If you must patch, fork to your own namespace under app/Support/ and delete the composer dependency — you will get Laravel 11 compatibility for free and lose nothing but the vendor line.
How can I help you explore Laravel packages today?