|
|
@@ -0,0 +1,203 @@
|
|
|
+<?php
|
|
|
+
|
|
|
+namespace Webkul\Shop\Http\Controllers\API;
|
|
|
+
|
|
|
+use Carbon\Carbon;
|
|
|
+use Illuminate\Http\JsonResponse;
|
|
|
+use Illuminate\Support\Facades\DB;
|
|
|
+use Longyi\RewardPoints\Helpers\ApiResponse;
|
|
|
+use Webkul\CartRule\Repositories\CartRuleCouponRepository;
|
|
|
+use Webkul\CartRule\Repositories\CartRuleRepository;
|
|
|
+
|
|
|
+class CouponController extends APIController
|
|
|
+{
|
|
|
+ protected string $charset = 'ABCDEFGHJKMNPQRSTUVWXYZ23456789';
|
|
|
+
|
|
|
+ protected int $codeLength = 8;
|
|
|
+
|
|
|
+ public function __construct(
|
|
|
+ protected CartRuleCouponRepository $cartRuleCouponRepository,
|
|
|
+ protected CartRuleRepository $cartRuleRepository
|
|
|
+ ) {}
|
|
|
+
|
|
|
+ /**
|
|
|
+ * List all coupons for the current customer.
|
|
|
+ */
|
|
|
+ public function index(): JsonResponse
|
|
|
+ {
|
|
|
+ $customer = auth()->guard('customer')->user();
|
|
|
+ if (! $customer) {
|
|
|
+ return ApiResponse::unauthorized();
|
|
|
+ }
|
|
|
+
|
|
|
+ $coupons = DB::table('cart_rule_coupons')
|
|
|
+ ->join('cart_rules', 'cart_rule_coupons.cart_rule_id', '=', 'cart_rules.id')
|
|
|
+ ->leftJoin('cart_rule_coupon_usage', function ($join) use ($customer) {
|
|
|
+ $join->on('cart_rule_coupons.id', '=', 'cart_rule_coupon_usage.cart_rule_coupon_id')
|
|
|
+ ->where('cart_rule_coupon_usage.customer_id', '=', $customer->id);
|
|
|
+ })
|
|
|
+ ->where('cart_rule_coupons.customer_id', $customer->id)
|
|
|
+ ->where('cart_rules.status', 1)
|
|
|
+ ->select(
|
|
|
+ 'cart_rule_coupons.id',
|
|
|
+ 'cart_rule_coupons.code',
|
|
|
+ 'cart_rule_coupons.cart_rule_id',
|
|
|
+ 'cart_rule_coupons.expired_at',
|
|
|
+ 'cart_rule_coupons.usage_limit',
|
|
|
+ 'cart_rule_coupons.usage_per_customer',
|
|
|
+ 'cart_rule_coupons.times_used',
|
|
|
+ 'cart_rule_coupons.created_at',
|
|
|
+ 'cart_rules.name as rule_name',
|
|
|
+ 'cart_rules.description as rule_description',
|
|
|
+ 'cart_rules.action_type',
|
|
|
+ 'cart_rules.discount_amount',
|
|
|
+ 'cart_rules.ends_till',
|
|
|
+ 'cart_rules.starts_from',
|
|
|
+ 'cart_rule_coupon_usage.times_used as my_used'
|
|
|
+ )
|
|
|
+ ->orderBy('cart_rule_coupons.created_at', 'desc')
|
|
|
+ ->get()
|
|
|
+ ->map(function ($coupon) {
|
|
|
+ $now = Carbon::now();
|
|
|
+ $status = 'available';
|
|
|
+
|
|
|
+ if ($coupon->expired_at && Carbon::parse($coupon->expired_at)->lt($now)) {
|
|
|
+ $status = 'expired';
|
|
|
+ } elseif ($coupon->ends_till && Carbon::parse($coupon->ends_till)->lt($now)) {
|
|
|
+ $status = 'expired';
|
|
|
+ } elseif ($coupon->starts_from && Carbon::parse($coupon->starts_from)->gt($now)) {
|
|
|
+ $status = 'not_started';
|
|
|
+ } elseif ($coupon->usage_limit && $coupon->times_used >= $coupon->usage_limit) {
|
|
|
+ $status = 'exhausted';
|
|
|
+ } elseif ($coupon->usage_per_customer && $coupon->my_used >= $coupon->usage_per_customer) {
|
|
|
+ $status = 'used';
|
|
|
+ }
|
|
|
+
|
|
|
+ $discountText = match ($coupon->action_type) {
|
|
|
+ 'by_percent' => floor($coupon->discount_amount).'%',
|
|
|
+ 'by_fixed' => '$'.floor($coupon->discount_amount),
|
|
|
+ 'cart_fixed' => '$'.floor($coupon->discount_amount).' (whole cart)',
|
|
|
+ 'buy_x_get_y' => 'Buy X Get Y Free',
|
|
|
+ default => $coupon->discount_amount,
|
|
|
+ };
|
|
|
+
|
|
|
+ return [
|
|
|
+ 'id' => $coupon->id,
|
|
|
+ 'code' => $coupon->code,
|
|
|
+ 'cart_rule_id' => $coupon->cart_rule_id,
|
|
|
+ 'rule_name' => $coupon->rule_name,
|
|
|
+ 'rule_description'=> $coupon->rule_description,
|
|
|
+ 'discount_text' => $discountText,
|
|
|
+ 'status' => $status,
|
|
|
+ 'expired_at' => $coupon->expired_at,
|
|
|
+ 'starts_from' => $coupon->starts_from,
|
|
|
+ 'ends_till' => $coupon->ends_till,
|
|
|
+ 'created_at' => $coupon->created_at,
|
|
|
+ ];
|
|
|
+ });
|
|
|
+
|
|
|
+ return ApiResponse::success($coupons);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Claim a coupon by rule ID. Auto-generates a short code and binds to the current customer.
|
|
|
+ */
|
|
|
+ public function claim(): JsonResponse
|
|
|
+ {
|
|
|
+ try {
|
|
|
+ $customer = auth()->guard('customer')->user();
|
|
|
+ if (! $customer) {
|
|
|
+ return ApiResponse::unauthorized();
|
|
|
+ }
|
|
|
+
|
|
|
+ $validated = request()->validate([
|
|
|
+ 'rule_id' => 'required|integer|exists:cart_rules,id',
|
|
|
+ ]);
|
|
|
+
|
|
|
+ $rule = $this->cartRuleRepository->find($validated['rule_id']);
|
|
|
+ if (! $rule) {
|
|
|
+ return ApiResponse::notFound('Cart rule not found');
|
|
|
+ }
|
|
|
+ if (! $rule->status) {
|
|
|
+ return ApiResponse::error('This coupon rule is currently inactive');
|
|
|
+ }
|
|
|
+ if ($rule->starts_from && Carbon::parse($rule->starts_from)->gt(Carbon::now())) {
|
|
|
+ return ApiResponse::error('This coupon rule has not started yet');
|
|
|
+ }
|
|
|
+ if ($rule->ends_till && Carbon::parse($rule->ends_till)->lt(Carbon::now())) {
|
|
|
+ return ApiResponse::error('This coupon rule has expired');
|
|
|
+ }
|
|
|
+ if (! $rule->coupon_type) {
|
|
|
+ return ApiResponse::error('This rule does not require a coupon code');
|
|
|
+ }
|
|
|
+
|
|
|
+ $existing = $this->cartRuleCouponRepository->findOneWhere([
|
|
|
+ 'cart_rule_id' => $rule->id,
|
|
|
+ 'customer_id' => $customer->id,
|
|
|
+ ]);
|
|
|
+ if ($existing) {
|
|
|
+ return ApiResponse::success([
|
|
|
+ 'id' => $existing->id,
|
|
|
+ 'code' => $existing->code,
|
|
|
+ 'rule_name' => $rule->name,
|
|
|
+ 'action_type' => $rule->action_type,
|
|
|
+ 'discount_amount' => $rule->discount_amount,
|
|
|
+ 'expired_at' => $existing->expired_at,
|
|
|
+ ], 'You have already claimed this coupon');
|
|
|
+ }
|
|
|
+
|
|
|
+ $code = $this->generateUniqueCode();
|
|
|
+ $coupon = $this->createCoupon([
|
|
|
+ 'cart_rule_id' => $rule->id,
|
|
|
+ 'code' => $code,
|
|
|
+ 'usage_limit' => $rule->uses_per_coupon ?: 1,
|
|
|
+ 'usage_per_customer' => $rule->usage_per_customer ?: 1,
|
|
|
+ 'customer_id' => $customer->id,
|
|
|
+ 'is_primary' => 0,
|
|
|
+ 'expired_at' => $rule->ends_till ?: null,
|
|
|
+ ]);
|
|
|
+
|
|
|
+ return ApiResponse::success([
|
|
|
+ 'id' => $coupon->id,
|
|
|
+ 'code' => $coupon->code,
|
|
|
+ 'rule_name' => $rule->name,
|
|
|
+ 'action_type' => $rule->action_type,
|
|
|
+ 'discount_amount' => $rule->discount_amount,
|
|
|
+ 'expired_at' => $coupon->expired_at,
|
|
|
+ ], 'Coupon claimed successfully');
|
|
|
+ } catch (\Exception $e) {
|
|
|
+ return ApiResponse::error($e->getMessage());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ protected function generateUniqueCode(): string
|
|
|
+ {
|
|
|
+ $retries = 5;
|
|
|
+ do {
|
|
|
+ $code = '';
|
|
|
+ for ($i = 0; $i < $this->codeLength; $i++) {
|
|
|
+ $code .= $this->charset[random_int(0, strlen($this->charset) - 1)];
|
|
|
+ }
|
|
|
+ } while (DB::table('cart_rule_coupons')->where('code', $code)->exists() && --$retries > 0);
|
|
|
+
|
|
|
+ return $code;
|
|
|
+ }
|
|
|
+
|
|
|
+ protected function createCoupon(array $data)
|
|
|
+ {
|
|
|
+ $retries = 3;
|
|
|
+ while ($retries > 0) {
|
|
|
+ try {
|
|
|
+ return $this->cartRuleCouponRepository->create($data);
|
|
|
+ } catch (\Illuminate\Database\QueryException $e) {
|
|
|
+ if ($e->getCode() == 23000 && $retries > 1) {
|
|
|
+ $retries--;
|
|
|
+ $data['code'] = $this->generateUniqueCode();
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ throw $e;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ throw new \RuntimeException('Failed to generate unique coupon code');
|
|
|
+ }
|
|
|
+}
|