| 123456789101112131415161718192021222324252627282930313233343536373839404142 |
- <?php
- namespace App\Services;
- use Illuminate\Support\Facades\DB;
- use Illuminate\Support\Facades\Schema;
- /**
- * Backend side of F1 own-game coupons. Coupons are issued and redeemed by melbet_sport-api;
- * here refunds return the paid amount and give the coupon back.
- */
- class PromotionCouponService
- {
- /** What the member actually paid for an order: stake minus its coupon share. */
- public static function paid($stake, $discount): string
- {
- return bcsub(bcadd((string)$stake, '0', 2), bcadd((string)($discount ?? '0'), '0', 2), 2);
- }
- /** The coupon returns once every order sharing it is refunded; an expired coupon simply stays unusable. */
- public static function release(string $source, int $orderId): void
- {
- if (!Schema::hasTable('promotion_coupon_uses')) return;
- DB::transaction(function () use ($source, $orderId) {
- // This immutable link identifies the coupon; always lock the coupon before any use row.
- $link = DB::table('promotion_coupon_uses')->where('source', $source)->where('order_id', $orderId)->first();
- if (!$link) return;
- $coupon = DB::table('promotion_coupons')->where('id', $link->coupon_id)->lockForUpdate()->first();
- if (!$coupon) throw new \RuntimeException('退款优惠券记录不存在');
- $use = DB::table('promotion_coupon_uses')->where('id', $link->id)->whereNull('refunded_at')->lockForUpdate()->first();
- if (!$use) return;
- $now = PromotionFacts::now();
- DB::table('promotion_coupon_uses')->where('id', $use->id)->update(['refunded_at' => $now, 'updated_at' => $now]);
- // Current read: an outer REPEATABLE READ transaction may already have an older snapshot.
- $remaining = DB::table('promotion_coupon_uses')->where('coupon_id', $use->coupon_id)
- ->whereNull('refunded_at')->lockForUpdate()->get(['id']);
- if ($remaining->isNotEmpty()) return;
- DB::table('promotion_coupons')->where('id', $use->coupon_id)->where('status', 'used')
- ->update(['status' => 'unused', 'used_source' => null, 'used_stake' => null, 'used_at' => null, 'updated_at' => $now]);
- });
- }
- }
|