Migrate billing from Paddle to Lemon Squeezy
Some checks failed
linter / quality (push) Has been cancelled
tests / ci (push) Has been cancelled
tests / ui (push) Has been cancelled

This commit is contained in:
Codex Agent
2026-02-03 10:59:54 +01:00
parent c96a73d884
commit 10c99de1e2
228 changed files with 4369 additions and 4067 deletions

View File

@@ -3,14 +3,12 @@
namespace Tests\Unit;
use App\Enums\CouponType;
use App\Jobs\SyncCouponToPaddle;
use App\Mail\GiftVoucherIssued;
use App\Models\Coupon;
use App\Models\GiftVoucher;
use App\Models\Package;
use App\Services\GiftVouchers\GiftVoucherService;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Bus;
use Illuminate\Support\Facades\Mail;
use Tests\TestCase;
@@ -18,37 +16,38 @@ class GiftVoucherServiceTest extends TestCase
{
use RefreshDatabase;
public function test_it_issues_voucher_and_coupon_from_paddle_payload(): void
public function test_it_issues_voucher_and_coupon_from_lemonsqueezy_payload(): void
{
$package = Package::factory()->create([
'type' => 'endcustomer',
'paddle_price_id' => 'pri_pkg_001',
'lemonsqueezy_variant_id' => 'pri_pkg_001',
'price' => 59,
]);
$payload = [
'id' => 'txn_123',
'event_type' => 'transaction.completed',
'currency_code' => 'EUR',
'totals' => [
'grand_total' => [
'amount' => 5900,
'data' => [
'id' => 'ord_123',
'attributes' => [
'total' => 5900,
'currency' => 'EUR',
'checkout_id' => 'chk_abc',
'variant_id' => 'pri_pkg_001',
'user_email' => 'buyer@example.com',
],
],
'custom_data' => [
'type' => 'gift_card',
'purchaser_email' => 'buyer@example.com',
'recipient_email' => 'friend@example.com',
'recipient_name' => 'Friend',
'message' => 'Happy Day',
'meta' => [
'custom_data' => [
'type' => 'gift_card',
'purchaser_email' => 'buyer@example.com',
'recipient_email' => 'friend@example.com',
'recipient_name' => 'Friend',
'message' => 'Happy Day',
],
],
'checkout_id' => 'chk_abc',
];
Bus::fake([SyncCouponToPaddle::class]);
$service = $this->app->make(GiftVoucherService::class);
$voucher = $service->issueFromPaddle($payload);
$voucher = $service->issueFromLemonSqueezy($payload);
$this->assertInstanceOf(GiftVoucher::class, $voucher);
$this->assertSame(59.00, (float) $voucher->amount);
@@ -56,7 +55,6 @@ class GiftVoucherServiceTest extends TestCase
$this->assertSame($voucher->code, $voucher->coupon->code);
$this->assertTrue($voucher->expires_at->greaterThan(now()->addYears(4)));
$this->assertTrue($voucher->coupon->packages()->whereKey($package->id)->exists());
Bus::assertDispatched(SyncCouponToPaddle::class);
}
public function test_redeeming_coupon_marks_voucher_redeemed(): void
@@ -71,7 +69,7 @@ class GiftVoucherServiceTest extends TestCase
'type' => CouponType::FLAT,
'amount' => 29,
'currency' => 'EUR',
'paddle_discount_id' => null,
'lemonsqueezy_discount_id' => null,
]);
$voucher->coupon()->associate($coupon)->save();
@@ -86,45 +84,48 @@ class GiftVoucherServiceTest extends TestCase
public function test_it_sends_notifications_to_purchaser_and_recipient_once(): void
{
Mail::fake();
Bus::fake([SyncCouponToPaddle::class]);
config()->set('gift-vouchers.reminder_days', 0);
config()->set('gift-vouchers.expiry_reminder_days', 0);
Package::factory()->create([
'type' => 'endcustomer',
'paddle_price_id' => 'pri_pkg_001',
'lemonsqueezy_variant_id' => 'pri_pkg_001',
'price' => 29,
]);
$payload = [
'id' => 'txn_456',
'currency_code' => 'EUR',
'totals' => [
'grand_total' => [
'amount' => 2900,
'data' => [
'id' => 'ord_456',
'attributes' => [
'total' => 2900,
'currency' => 'EUR',
'checkout_id' => 'chk_notif',
'variant_id' => 'pri_pkg_001',
'user_email' => 'buyer@example.com',
],
],
'custom_data' => [
'type' => 'gift_voucher',
'purchaser_email' => 'buyer@example.com',
'recipient_email' => 'friend@example.com',
'app_locale' => 'de',
'meta' => [
'custom_data' => [
'type' => 'gift_voucher',
'purchaser_email' => 'buyer@example.com',
'recipient_email' => 'friend@example.com',
'app_locale' => 'de',
],
],
'checkout_id' => 'chk_notif',
];
$service = $this->app->make(GiftVoucherService::class);
$voucher = $service->issueFromPaddle($payload);
$voucher = $service->issueFromLemonSqueezy($payload);
Mail::assertQueued(GiftVoucherIssued::class, 2);
$this->assertTrue((bool) ($voucher->metadata['notifications_sent'] ?? false));
// Second call (duplicate webhook) should not resend
$service->issueFromPaddle($payload);
$service->issueFromLemonSqueezy($payload);
Mail::assertQueued(GiftVoucherIssued::class, 2);
}
public function test_it_resolves_amount_from_tier_by_price_id(): void
public function test_it_resolves_amount_from_tier_by_variant_id(): void
{
config()->set('gift-vouchers.tiers', [
[
@@ -132,21 +133,25 @@ class GiftVoucherServiceTest extends TestCase
'label' => 'Gift Classic (USD)',
'amount' => 65.00,
'currency' => 'USD',
'paddle_price_id' => 'pri_usd_123',
'lemonsqueezy_variant_id' => 'pri_usd_123',
],
]);
Bus::fake([SyncCouponToPaddle::class]);
Mail::fake();
$payload = [
'id' => 'txn_usd',
'price_id' => 'pri_usd_123',
'currency_code' => 'USD',
'data' => [
'id' => 'ord_usd',
'attributes' => [
'variant_id' => 'pri_usd_123',
'currency' => 'USD',
'total' => 6500,
],
],
];
$service = $this->app->make(GiftVoucherService::class);
$voucher = $service->issueFromPaddle($payload);
$voucher = $service->issueFromLemonSqueezy($payload);
$this->assertSame(65.00, (float) $voucher->amount);
$this->assertSame('USD', $voucher->currency);