254 lines
9.3 KiB
PHP
254 lines
9.3 KiB
PHP
<?php
|
|||
|
|
|
||
|
|
namespace App\Services;
|
||
|
|
|
||
|
|
use App\Models\Customer;
|
||
|
|
use App\Models\Price;
|
||
|
|
use App\Models\Withdraw;
|
||
|
|
use Illuminate\Support\Facades\DB;
|
||
|
|
use Rooyesh\Wallet\interfaces\TransactionInterface;
|
||
|
|
use Rooyesh\Wallet\interfaces\WalletInterface;
|
||
|
|
use Symfony\Component\HttpKernel\Exception\UnprocessableEntityHttpException;
|
||
|
|
|
||
|
|
class DigipayInstallmentService
|
||
|
|
{
|
||
|
|
public function enabled(): bool
|
||
|
|
{
|
||
|
|
return app(ConfigService::class)->getBool('enable_digipay_installment', false);
|
||
|
|
}
|
||
|
|
|
||
|
|
public function publicConfig(): array
|
||
|
|
{
|
||
|
|
$cfg = app(ConfigService::class);
|
||
|
|
|
||
|
|
return [
|
||
|
|
'onboarding_url' => trim($cfg->getString('digipay_onboarding_url', '')),
|
||
|
|
'fee_shop_percent' => (float) $cfg->getString('digipay_fee_shop_percent', '8'),
|
||
|
|
'fee_gateway_percent' => (float) $cfg->getString('digipay_fee_gateway_percent', '5.7'),
|
||
|
|
'fee_extra_percent' => (float) $cfg->getString('digipay_fee_extra_percent', '2.3'),
|
||
|
|
'plan4_cap_toman' => (int) $cfg->getString('digipay_plan4_cap_toman', '30000000'),
|
||
|
|
'plan12_cap_toman' => (int) $cfg->getString('digipay_plan12_cap_toman', '200000000'),
|
||
|
|
];
|
||
|
|
}
|
||
|
|
|
||
|
|
public function statusFor(int $customerId): array
|
||
|
|
{
|
||
|
|
$credit = DB::table('digipay_credits')->where('customer_id', $customerId)->first();
|
||
|
|
$config = $this->publicConfig();
|
||
|
|
|
||
|
|
return [
|
||
|
|
'enabled' => true,
|
||
|
|
'status' => $credit->status ?? 'none',
|
||
|
|
'plan4_remaining_toman' => (int) floor(((int) ($credit->plan4_remaining_rial ?? 0)) / 10),
|
||
|
|
'plan12_remaining_toman' => (int) floor(((int) ($credit->plan12_remaining_rial ?? 0)) / 10),
|
||
|
|
'gram_price_rial' => $this->gramPriceRial(),
|
||
|
|
'config' => $config,
|
||
|
|
];
|
||
|
|
}
|
||
|
|
|
||
|
|
public function markStarted(int $customerId): void
|
||
|
|
{
|
||
|
|
$config = $this->publicConfig();
|
||
|
|
if ($config['onboarding_url'] === '') {
|
||
|
|
throw new UnprocessableEntityHttpException('لینک دیجیپی در تنظیمات ثبت نشده است');
|
||
|
|
}
|
||
|
|
|
||
|
|
$existing = DB::table('digipay_credits')->where('customer_id', $customerId)->first();
|
||
|
|
if ($existing && $existing->status === 'active') {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
if ($existing) {
|
||
|
|
DB::table('digipay_credits')->where('id', $existing->id)->update([
|
||
|
|
'status' => 'pending',
|
||
|
|
'updated_at' => now(),
|
||
|
|
]);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
DB::table('digipay_credits')->insert([
|
||
|
|
'customer_id' => $customerId,
|
||
|
|
'status' => 'pending',
|
||
|
|
'plan4_remaining_rial' => 0,
|
||
|
|
'plan12_remaining_rial' => 0,
|
||
|
|
'created_at' => now(),
|
||
|
|
'updated_at' => now(),
|
||
|
|
]);
|
||
|
|
}
|
||
|
|
|
||
|
|
public function setCredit(string $mobile, int $plan4Toman, int $plan12Toman): array
|
||
|
|
{
|
||
|
|
$customer = Customer::findActiveByMobile($mobile);
|
||
|
|
if (! $customer) {
|
||
|
|
throw new UnprocessableEntityHttpException('مشتری با این موبایل پیدا نشد');
|
||
|
|
}
|
||
|
|
|
||
|
|
$row = [
|
||
|
|
'status' => 'active',
|
||
|
|
'plan4_remaining_rial' => max(0, $plan4Toman) * 10,
|
||
|
|
'plan12_remaining_rial' => max(0, $plan12Toman) * 10,
|
||
|
|
'updated_at' => now(),
|
||
|
|
];
|
||
|
|
|
||
|
|
$existing = DB::table('digipay_credits')->where('customer_id', $customer->id)->first();
|
||
|
|
if ($existing) {
|
||
|
|
DB::table('digipay_credits')->where('id', $existing->id)->update($row);
|
||
|
|
} else {
|
||
|
|
$row['customer_id'] = $customer->id;
|
||
|
|
$row['created_at'] = now();
|
||
|
|
DB::table('digipay_credits')->insert($row);
|
||
|
|
}
|
||
|
|
|
||
|
|
return [
|
||
|
|
'customer_id' => (int) $customer->id,
|
||
|
|
'name' => $customer->name,
|
||
|
|
'plan4_remaining_toman' => max(0, $plan4Toman),
|
||
|
|
'plan12_remaining_toman' => max(0, $plan12Toman),
|
||
|
|
];
|
||
|
|
}
|
||
|
|
|
||
|
|
public function buy(int $customerId, int $plan, int $amountToman): array
|
||
|
|
{
|
||
|
|
if (! in_array($plan, [4, 12], true)) {
|
||
|
|
throw new UnprocessableEntityHttpException('طرح اقساط نامعتبر است');
|
||
|
|
}
|
||
|
|
if ($amountToman < 1000) {
|
||
|
|
throw new UnprocessableEntityHttpException('مبلغ خرید را وارد کنید');
|
||
|
|
}
|
||
|
|
|
||
|
|
$price = $this->gramPriceRial();
|
||
|
|
if ($price <= 0) {
|
||
|
|
throw new UnprocessableEntityHttpException('قیمت لحظهای در دسترس نیست');
|
||
|
|
}
|
||
|
|
|
||
|
|
$config = $this->publicConfig();
|
||
|
|
$cap = $plan === 4 ? $config['plan4_cap_toman'] : $config['plan12_cap_toman'];
|
||
|
|
if ($amountToman > $cap) {
|
||
|
|
throw new UnprocessableEntityHttpException('مبلغ از سقف این طرح بیشتر است');
|
||
|
|
}
|
||
|
|
|
||
|
|
$feePercent = $config['fee_shop_percent'] + $config['fee_gateway_percent'] + $config['fee_extra_percent'];
|
||
|
|
$amountRial = $amountToman * 10;
|
||
|
|
$chargeRial = (int) round($amountRial * (1 + ($feePercent / 100)));
|
||
|
|
$feeRial = $chargeRial - $amountRial;
|
||
|
|
$grams = round($amountRial / $price, 4);
|
||
|
|
if ($grams <= 0) {
|
||
|
|
throw new UnprocessableEntityHttpException('مقدار مس محاسبهشده نامعتبر است');
|
||
|
|
}
|
||
|
|
|
||
|
|
$column = $plan === 4 ? 'plan4_remaining_rial' : 'plan12_remaining_rial';
|
||
|
|
|
||
|
|
return DB::transaction(function () use ($customerId, $plan, $amountRial, $feeRial, $grams, $price, $chargeRial, $column) {
|
||
|
|
$credit = DB::table('digipay_credits')->where('customer_id', $customerId)->lockForUpdate()->first();
|
||
|
|
if (! $credit || $credit->status !== 'active') {
|
||
|
|
throw new UnprocessableEntityHttpException('اعتبار اقساطی شما هنوز فعال نشده است');
|
||
|
|
}
|
||
|
|
if ((int) $credit->{$column} < $chargeRial) {
|
||
|
|
throw new UnprocessableEntityHttpException('مانده اعتبار این طرح کافی نیست');
|
||
|
|
}
|
||
|
|
|
||
|
|
DB::table('digipay_credits')->where('id', $credit->id)->update([
|
||
|
|
$column => (int) $credit->{$column} - $chargeRial,
|
||
|
|
'updated_at' => now(),
|
||
|
|
]);
|
||
|
|
|
||
|
|
$orderId = DB::table('digipay_orders')->insertGetId([
|
||
|
|
'customer_id' => $customerId,
|
||
|
|
'plan' => $plan,
|
||
|
|
'amount_rial' => $amountRial,
|
||
|
|
'fee_rial' => $feeRial,
|
||
|
|
'grams' => $grams,
|
||
|
|
'gram_price_rial' => $price,
|
||
|
|
'status' => 'done',
|
||
|
|
'created_at' => now(),
|
||
|
|
'updated_at' => now(),
|
||
|
|
]);
|
||
|
|
|
||
|
|
$this->creditMetal($customerId, $grams, (int) $orderId);
|
||
|
|
|
||
|
|
return [
|
||
|
|
'order_id' => (int) $orderId,
|
||
|
|
'grams' => $grams,
|
||
|
|
'plan' => $plan,
|
||
|
|
'charge_toman' => (int) floor($chargeRial / 10),
|
||
|
|
];
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
public function recentOrders(int $limit = 50): array
|
||
|
|
{
|
||
|
|
return DB::table('digipay_orders as o')
|
||
|
|
->leftJoin('customers as c', 'c.id', '=', 'o.customer_id')
|
||
|
|
->orderByDesc('o.id')
|
||
|
|
->limit($limit)
|
||
|
|
->get([
|
||
|
|
'o.id',
|
||
|
|
'o.customer_id',
|
||
|
|
'c.name',
|
||
|
|
'c.mobile',
|
||
|
|
'o.plan',
|
||
|
|
'o.amount_rial',
|
||
|
|
'o.fee_rial',
|
||
|
|
'o.grams',
|
||
|
|
'o.created_at',
|
||
|
|
])
|
||
|
|
->map(function ($row) {
|
||
|
|
return [
|
||
|
|
'id' => (int) $row->id,
|
||
|
|
'customer_id' => (int) $row->customer_id,
|
||
|
|
'name' => $row->name,
|
||
|
|
'mobile' => $row->mobile,
|
||
|
|
'plan' => (int) $row->plan,
|
||
|
|
'amount_toman' => (int) floor(((int) $row->amount_rial) / 10),
|
||
|
|
'fee_toman' => (int) floor(((int) $row->fee_rial) / 10),
|
||
|
|
'grams' => (float) $row->grams,
|
||
|
|
'created_at' => $row->created_at,
|
||
|
|
];
|
||
|
|
})
|
||
|
|
->all();
|
||
|
|
}
|
||
|
|
|
||
|
|
private function gramPriceRial(): int
|
||
|
|
{
|
||
|
|
return (int) (Price::query()->orderByDesc('id')->value('price') ?? 0);
|
||
|
|
}
|
||
|
|
|
||
|
|
private function creditMetal(int $customerId, float $grams, int $orderId): void
|
||
|
|
{
|
||
|
|
$user = Customer::query()->find($customerId);
|
||
|
|
if (! $user) {
|
||
|
|
throw new UnprocessableEntityHttpException('مشتری پیدا نشد');
|
||
|
|
}
|
||
|
|
|
||
|
|
$withdraw = Withdraw::create([
|
||
|
|
'customer_id' => $user->id,
|
||
|
|
'gold' => $grams,
|
||
|
|
'note' => 'خرید اقساطی',
|
||
|
|
'sts' => 6,
|
||
|
|
'tracked_number' => $orderId,
|
||
|
|
]);
|
||
|
|
|
||
|
|
$wallet = new WalletInterface($user->id, $user->type);
|
||
|
|
new TransactionInterface($wallet, [
|
||
|
|
'cash' => 0,
|
||
|
|
'gold' => $grams,
|
||
|
|
'expire_at' => now(),
|
||
|
|
'note' => 'خرید اقساطی',
|
||
|
|
'withdraw_id' => $withdraw->id,
|
||
|
|
'active' => true,
|
||
|
|
], 7, true, false);
|
||
|
|
|
||
|
|
try {
|
||
|
|
app(ShayeganSyncService::class)->recordWalletGold([
|
||
|
|
'customer_id' => $user->id,
|
||
|
|
'value' => $grams,
|
||
|
|
'direction' => 1,
|
||
|
|
'withdraw_id' => $withdraw->id,
|
||
|
|
'note' => 'خرید اقساطی',
|
||
|
|
]);
|
||
|
|
} catch (\Throwable $e) {
|
||
|
|
report($e);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|