Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: add renewal methods to subscription model #7681

Merged
merged 4 commits into from
Jan 17, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 32 additions & 1 deletion src/Donations/Properties/BillingAddress.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,14 @@

namespace Give\Donations\Properties;

final class BillingAddress
use Give\Framework\Support\Contracts\Arrayable;
use JsonSerializable;

/**
* @unreleased added JsonSerializable an Arrayable
* @since 2.19.6
*/
final class BillingAddress implements JsonSerializable, Arrayable
{
/**
* @var string
Expand Down Expand Up @@ -49,4 +56,28 @@ public static function fromArray($array)

return $self;
}

/**
* @unreleased
*/
public function toArray(): array
{
return [
'country' => $this->country,
'address1' => $this->address1,
'address2' => $this->address2,
'city' => $this->city,
'state' => $this->state,
'zip' => $this->zip,
];
}

/**
* @unreleased
*/
#[\ReturnTypeWillChange]
public function jsonSerialize()
{
return $this->toArray();
}
}
30 changes: 30 additions & 0 deletions src/Subscriptions/Models/Subscription.php
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,36 @@ public static function create(array $attributes): Subscription
return $subscription;
}

/**
* @unreleased
* @throws Exception
*/
public function createRenewal(array $attributes = []): Donation
{
return give()->subscriptions->createRenewal($this, $attributes);
}

/**
* @unreleased
*/
public function shouldCreateRenewal(): bool
{
$billTimes = $this->installments;
$totalPayments = count($this->donations);
JasonTheAdams marked this conversation as resolved.
Show resolved Hide resolved

return $this->status->isActive() && (0 === $billTimes || $totalPayments < $billTimes);
}

/**
* @unreleased
*/
public function shouldEndSubscription(): bool
{
return $this->installments !== 0 && (count(
$this->donations
) >= $this->installments);
}

/**
* @since 2.20.0 mutate model in repository and return void
* @since 2.19.6
Expand Down
45 changes: 45 additions & 0 deletions src/Subscriptions/Repositories/SubscriptionRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@
namespace Give\Subscriptions\Repositories;

use Exception;
use Give\Donations\Models\Donation;
use Give\Donations\ValueObjects\DonationMetaKeys;
use Give\Donations\ValueObjects\DonationMode;
use Give\Donations\ValueObjects\DonationStatus;
use Give\Donations\ValueObjects\DonationType;
use Give\Framework\Database\DB;
use Give\Framework\Exceptions\Primitives\InvalidArgumentException;
use Give\Framework\Models\ModelQueryBuilder;
Expand Down Expand Up @@ -375,6 +379,47 @@ public function getInitialDonationId(int $subscriptionId)
return (int)$query->ID;
}

/**
* @unreleased
* @throws Exception
*/
public function createRenewal(Subscription $subscription, array $attributes = []): Donation
{
$initialDonation = $subscription->initialDonation();

$donation = Donation::create(
array_merge([
'subscriptionId' => $subscription->id,
'gatewayId' => $subscription->gatewayId,
'amount' => $subscription->amount,
'status' => DonationStatus::COMPLETE(),
'type' => DonationType::RENEWAL(),
'donorId' => $subscription->donorId,
'formId' => $subscription->donationFormId,
'honorific' => $initialDonation->honorific,
'firstName' => $initialDonation->firstName,
'lastName' => $initialDonation->lastName,
'email' => $initialDonation->email,
'phone' => $initialDonation->phone,
'anonymous' => $initialDonation->anonymous,
'levelId' => $initialDonation->levelId,
'company' => $initialDonation->company,
'comment' => $initialDonation->comment,
'billingAddress' => $initialDonation->billingAddress,
'feeAmountRecovered' => $subscription->feeAmountRecovered,
'exchangeRate' => $initialDonation->exchangeRate,
'formTitle' => $initialDonation->formTitle,
'mode' => $subscription->mode->isLive() ? DonationMode::LIVE() : DonationMode::TEST(),
'donorIp' => $initialDonation->donorIp,
], $attributes)
);

$subscription->bumpRenewalDate();
$subscription->save();

return $donation;
}

/**
* @since 2.19.6
*
Expand Down
117 changes: 117 additions & 0 deletions tests/Unit/Subscriptions/Models/TestSubscription.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use Give\Framework\Support\ValueObjects\Money;
use Give\Subscriptions\Models\Subscription;
use Give\Subscriptions\ValueObjects\SubscriptionPeriod;
use Give\Subscriptions\ValueObjects\SubscriptionStatus;
use Give\Tests\TestCase;
use Give\Tests\TestTraits\RefreshDatabase;

Expand Down Expand Up @@ -37,6 +38,122 @@ public function testCreateShouldInsertSubscription()
$this->assertEquals($subscription->toArray(), $subscriptionFromDatabase->toArray());
}

/**
* @unreleased
* @throws Exception
*/
public function testCreateRenewalShouldCreateRenewalWithAttributes(): void
{
$subscription = Subscription::factory()->createWithDonation();

/** @var Subscription $subscriptionFromDatabase */
$subscriptionFromDatabase = Subscription::find($subscription->id);
$renewal = $subscriptionFromDatabase->createRenewal(['gatewayTransactionId' => 'transaction-id-1234']);

$this->assertEquals('transaction-id-1234', $renewal->gatewayTransactionId);
$this->assertEquals($subscriptionFromDatabase->id, $renewal->subscriptionId);
}

/**
* @unreleased
*/
public function testShouldCreateRenewalShouldReturnTrueWhenInstallmentsIsZero()
{
$subscription = Subscription::factory()->createWithDonation([
'status' => SubscriptionStatus::ACTIVE(),
'installments' => 0,
]);

$this->assertTrue($subscription->shouldCreateRenewal());


}

/**
* @unreleased
*/
public function testShouldCreateRenewalShouldReturnTrueWhenInstallmentsIsLessThanDonations(): void
{
$subscription = Subscription::factory()->createWithDonation([
'status' => SubscriptionStatus::ACTIVE(),
'installments' => 3,
]);

Subscription::factory()->createRenewal($subscription);

$this->assertTrue($subscription->shouldCreateRenewal());
}

/**
* @unreleased
*/
public function testShouldCreateRenewalShouldReturnFalseWhenInstallmentsAreReached(): void
{
$subscription = Subscription::factory()->createWithDonation([
'status' => SubscriptionStatus::ACTIVE(),
'installments' => 2,
]);

Subscription::factory()->createRenewal($subscription);

$this->assertFalse($subscription->shouldCreateRenewal());
}

/**
* @unreleased
*/
public function testShouldCreateRenewalShouldReturnFalseWhenStatusIsNotActive(): void
{
$subscription = Subscription::factory()->createWithDonation([
'status' => SubscriptionStatus::PENDING(),
'installments' => 0,
]);

$this->assertFalse($subscription->shouldCreateRenewal());
}

/**
* @unreleased
*/
public function testShouldEndSubscriptionShouldReturnTrueWhenInstallmentsAreReached(): void
{
$subscription = Subscription::factory()->createWithDonation([
'installments' => 2,
]);

Subscription::factory()->createRenewal($subscription);

$this->assertTrue($subscription->shouldEndSubscription());
}

/**
* @unreleased
*/
public function testShouldEndSubscriptionShouldReturnFalseWhenInstallmentsAreNotReached(): void
{
$subscription = Subscription::factory()->createWithDonation([
'installments' => 4,
]);

Subscription::factory()->createRenewal($subscription);

$this->assertFalse($subscription->shouldEndSubscription());
}

/**
* @unreleased
*/
public function testShouldEndSubscriptionShouldReturnFalseWhenInstallmentsAreZero(): void
{
$subscription = Subscription::factory()->createWithDonation([
'installments' => 0,
]);

Subscription::factory()->createRenewal($subscription);

$this->assertFalse($subscription->shouldEndSubscription());
}

/**
* @return void
* @throws Exception
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,15 @@
namespace Give\Tests\Unit\Subscriptions\Repositories;

use Exception;
use Give\Donations\Models\Donation;
use Give\Donations\ValueObjects\DonationStatus;
use Give\Donations\ValueObjects\DonationType;
use Give\Donors\Models\Donor;
use Give\Framework\Database\DB;
use Give\Framework\Exceptions\Primitives\InvalidArgumentException;
use Give\Framework\Support\Facades\DateTime\Temporal;
use Give\Framework\Support\ValueObjects\Money;
use Give\Subscriptions\Actions\GenerateNextRenewalForSubscription;
use Give\Subscriptions\Models\Subscription;
use Give\Subscriptions\Repositories\SubscriptionRepository;
use Give\Subscriptions\ValueObjects\SubscriptionPeriod;
Expand Down Expand Up @@ -199,4 +203,57 @@ public function testDeleteShouldRemoveSubscriptionFromTheDatabase()

$this->assertNull($subscriptionQuery);
}

/**
* @unreleased
* @throws Exception
*/
public function testCreateRenewalShouldCreateNewRenewal(): void
{
$subscription = Subscription::factory()->createWithDonation();
$repository = new SubscriptionRepository();

$renewalCreatedAt = Temporal::getCurrentDateTime();
$gatewayTransactionId = 'transaction-id';

$renewal = $repository->createRenewal($subscription, [
'gatewayTransactionId' => $gatewayTransactionId,
'createdAt' => $renewalCreatedAt,
]);

$nextRenewalDate = (new GenerateNextRenewalForSubscription())(
$subscription->period,
$subscription->frequency,
$subscription->renewsAt
);

$initialDonation = $subscription->initialDonation();

$this->assertCount(2, $subscription->donations);
$this->assertTrue($renewal->status->isComplete());
$this->assertTrue($renewal->type->isRenewal());
$this->assertSame($subscription->id, $renewal->subscriptionId);
$this->assertSame($subscription->gatewayId, $renewal->gatewayId);
$this->assertSame($subscription->donorId, $renewal->donorId);
$this->assertSame($subscription->donationFormId, $renewal->formId);
$this->assertTrue($renewal->type->isRenewal());
$this->assertTrue($renewal->status->isComplete());
$this->assertSame($gatewayTransactionId, $renewal->gatewayTransactionId);
$this->assertSame($initialDonation->honorific, $renewal->honorific);
$this->assertSame($initialDonation->firstName, $renewal->firstName);
$this->assertSame($initialDonation->lastName, $renewal->lastName);
$this->assertSame($initialDonation->email, $renewal->email);
$this->assertSame($initialDonation->phone, $renewal->phone);
$this->assertSame($initialDonation->anonymous, $renewal->anonymous);
$this->assertSame($initialDonation->levelId, $renewal->levelId);
$this->assertSame($initialDonation->company, $renewal->company);
$this->assertSame($subscription->feeAmountRecovered, $renewal->feeAmountRecovered);
$this->assertSame($initialDonation->exchangeRate, $renewal->exchangeRate);
$this->assertSame($initialDonation->formTitle, $renewal->formTitle);
$this->assertSame($subscription->mode->getValue(), $renewal->mode->getValue());
$this->assertSame($initialDonation->donorIp, $renewal->donorIp);
$this->assertSame($initialDonation->billingAddress->toArray(), $renewal->billingAddress->toArray());
$this->assertSame($renewalCreatedAt->getTimestamp(), $renewal->createdAt->getTimestamp());
$this->assertSame($subscription->renewsAt->getTimestamp(), $nextRenewalDate->getTimestamp());
}
}
Loading