Migrate billing from Paddle to Lemon Squeezy
This commit is contained in:
@@ -8,16 +8,12 @@ use App\Models\Coupon;
|
||||
use App\Models\CouponRedemption;
|
||||
use App\Models\Package;
|
||||
use App\Models\Tenant;
|
||||
use App\Services\Paddle\Exceptions\PaddleException;
|
||||
use App\Services\Paddle\PaddleDiscountService;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
|
||||
class CouponService
|
||||
{
|
||||
public function __construct(private readonly PaddleDiscountService $paddleDiscounts) {}
|
||||
public function __construct() {}
|
||||
|
||||
/**
|
||||
* @return array{coupon: Coupon, pricing: array<string, mixed>, source: string}
|
||||
@@ -39,7 +35,7 @@ class CouponService
|
||||
|
||||
public function ensureCouponCanBeApplied(Coupon $coupon, Package $package, ?Tenant $tenant = null): void
|
||||
{
|
||||
if (! $coupon->paddle_discount_id) {
|
||||
if (! $coupon->lemonsqueezy_discount_id) {
|
||||
throw ValidationException::withMessages([
|
||||
'code' => __('marketing.coupon.errors.not_synced'),
|
||||
]);
|
||||
@@ -124,58 +120,12 @@ class CouponService
|
||||
$currency = Str::upper($package->currency ?? 'EUR');
|
||||
$subtotal = (float) $package->price;
|
||||
|
||||
if ($package->paddle_price_id) {
|
||||
try {
|
||||
$preview = $this->paddleDiscounts->previewDiscount(
|
||||
$coupon,
|
||||
[
|
||||
[
|
||||
'price_id' => $package->paddle_price_id,
|
||||
'quantity' => 1,
|
||||
],
|
||||
],
|
||||
array_filter([
|
||||
'currency' => $currency,
|
||||
'customer_id' => $tenant?->paddle_customer_id,
|
||||
])
|
||||
);
|
||||
|
||||
$mapped = $this->mapPaddlePreview($preview, $currency, $subtotal);
|
||||
|
||||
return [
|
||||
'pricing' => $mapped,
|
||||
'source' => 'paddle',
|
||||
];
|
||||
} catch (PaddleException $exception) {
|
||||
Log::warning('Paddle preview failed, falling back to manual pricing', [
|
||||
'coupon_id' => $coupon->id,
|
||||
'package_id' => $package->id,
|
||||
'message' => $exception->getMessage(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'pricing' => $this->manualPricing($coupon, $currency, $subtotal),
|
||||
'source' => 'manual',
|
||||
];
|
||||
}
|
||||
|
||||
protected function mapPaddlePreview(array $preview, string $currency, float $fallbackSubtotal): array
|
||||
{
|
||||
$totals = $this->extractTotals($preview);
|
||||
|
||||
$subtotal = $totals['subtotal'] ?? $fallbackSubtotal;
|
||||
$discount = $totals['discount'] ?? 0.0;
|
||||
$tax = $totals['tax'] ?? 0.0;
|
||||
$total = $totals['total'] ?? max($subtotal - $discount + $tax, 0);
|
||||
|
||||
return $this->formatPricing($currency, $subtotal, $discount, $tax, $total, [
|
||||
'raw' => $preview,
|
||||
'breakdown' => $totals['breakdown'] ?? [],
|
||||
]);
|
||||
}
|
||||
|
||||
protected function manualPricing(Coupon $coupon, string $currency, float $subtotal): array
|
||||
{
|
||||
$discount = match ($coupon->type) {
|
||||
@@ -199,42 +149,6 @@ class CouponService
|
||||
]);
|
||||
}
|
||||
|
||||
protected function extractTotals(array $preview): array
|
||||
{
|
||||
$totals = Arr::get($preview, 'totals', Arr::get($preview, 'details.totals', []));
|
||||
|
||||
$subtotal = $this->convertMinorAmount($totals['subtotal'] ?? ($totals['subtotal']['amount'] ?? null));
|
||||
$discount = $this->convertMinorAmount($totals['discount'] ?? ($totals['discount']['amount'] ?? null));
|
||||
$tax = $this->convertMinorAmount($totals['tax'] ?? ($totals['tax']['amount'] ?? null));
|
||||
$total = $this->convertMinorAmount($totals['total'] ?? ($totals['total']['amount'] ?? null));
|
||||
|
||||
return array_filter([
|
||||
'currency' => $totals['currency_code'] ?? Arr::get($preview, 'currency_code'),
|
||||
'subtotal' => $subtotal,
|
||||
'discount' => $discount,
|
||||
'tax' => $tax,
|
||||
'total' => $total,
|
||||
'breakdown' => Arr::get($preview, 'discounts', []),
|
||||
], static fn ($value) => $value !== null && $value !== '');
|
||||
}
|
||||
|
||||
protected function convertMinorAmount(mixed $value): ?float
|
||||
{
|
||||
if ($value === null || $value === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (is_array($value) && isset($value['amount'])) {
|
||||
$value = $value['amount'];
|
||||
}
|
||||
|
||||
if (! is_numeric($value)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return round(((float) $value) / 100, 2);
|
||||
}
|
||||
|
||||
protected function formatPricing(string $currency, float $subtotal, float $discount, float $tax, float $total, array $extra = []): array
|
||||
{
|
||||
$locale = $this->mapLocale(app()->getLocale());
|
||||
|
||||
Reference in New Issue
Block a user