Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine
This course is archived!
This tutorial uses an older version of Symfony of the stripe-php SDK. The majority of the concepts are still valid, though there *are* differences. We've done our best to add notes & comments that describe these changes.

So, When is my Next Invoice?

Keep on Learning!

If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.

Start your All-Access Pass
Buy just this tutorial for $12.00

Back on the account page, our customers would love us if we would show them when they will be billed next. In other words: when will my subscription renew?

Open the Subscription class - aka the subscription table. I've already added a billingPeriodEndsAt column:

... lines 1 - 10
class Subscription
{
... lines 13 - 40
/**
* @ORM\Column(type="datetime", nullable=true)
*/
private $billingPeriodEndsAt;
... lines 45 - 86
/**
* @return \DateTime
*/
public function getBillingPeriodEndsAt()
{
return $this->billingPeriodEndsAt;
}
... lines 94 - 100
}

All we need to do is set this when the subscription is first created. Ok, we also need to update it each month when the subscription is renewed - but we'll talk about that later with webhooks.

The Subscription is created - or retrieved - right here in SubscriptionHelper. This is the spot to set that date.

And hey! This method is passed a \Stripe\Subscription object:

... lines 1 - 8
class SubscriptionHelper
{
... lines 11 - 45
public function addSubscriptionToUser(\Stripe\Subscription $stripeSubscription, User $user)
{
... lines 48 - 60
}
... lines 62 - 70
}

Let's check out the API docs to see what that gives us. Oh yes, it has a current_period_end property, which is a UNIX timestamp. Bingo!

Setting the billingPeriodEndsAt

In SubscriptionHelper, before we activate the subscription, add a new $periodEnd variable. To convert the timestamp to a \DateTime object, say $periodEnd = \DateTime::createFromFormat('U') - for UNIX timestamp - $stripeSubscription->current_period_end:

... lines 1 - 8
class SubscriptionHelper
{
... lines 11 - 45
public function addSubscriptionToUser(\Stripe\Subscription $stripeSubscription, User $user)
{
$subscription = $user->getSubscription();
if (!$subscription) {
$subscription = new Subscription();
$subscription->setUser($user);
}
$periodEnd = \DateTime::createFromFormat('U', $stripeSubscription->current_period_end);
... lines 55 - 62
}
... lines 64 - 72
}

Now, pass that into the activateSubscription() method as a new argument:

... lines 1 - 53
$periodEnd = \DateTime::createFromFormat('U', $stripeSubscription->current_period_end);
$subscription->activateSubscription(
$stripeSubscription->plan->id,
$stripeSubscription->id,
$periodEnd
);
... lines 60 - 74

Open that function in Subscription and add a new \DateTime argument called $periodEnd. Set the property with $this->billingPeriodEndsAt = $periodEnd:

... lines 1 - 10
class Subscription
{
... lines 13 - 94
public function activateSubscription($stripePlanId, $stripeSubscriptionId, \DateTime $periodEnd)
{
... lines 97 - 98
$this->billingPeriodEndsAt = $periodEnd;
... line 100
}
}

Done!

Rendering the Next Billing Date

To celebrate, open the account.html.twig template. For "Next Billing At", add if app.user.subscription - so if they have a subscription - then print app.user.subscription.billingPeriodEndsAt|date('F jS') to format the date nicely:

... lines 1 - 2
{% block body %}
<div class="nav-space">
<div class="container">
... lines 6 - 11
<div class="row">
<div class="col-xs-6">
<table class="table">
<tbody>
... lines 16 - 25
<tr>
<th>Next Billing at:</th>
<td>
{% if app.user.subscription %}
{{ app.user.subscription.billingPeriodEndsAt|date('F jS') }}
{% else %}
n/a
{% endif %}
</td>
</tr>
... lines 36 - 45
</tbody>
</table>
</div>
... lines 49 - 51
</div>
</div>
</div>
{% endblock %}
... lines 56 - 57

OK team! Refresh that page! The "Next Billing At" is... wrong! August 9th! That's today! But no worries, that's just because the field is blank in the database, so it's using today. To really test if this is working, we need to checkout with a new subscription.

Now, in real life, you probably won't allow your users to buy multiple subscriptions. Afterall, we're only storing info in the database about one Subscription, per user. But, for testing, it's really handy to be able to checkout over and over again.

The checkout worked! Click "Account". Yes! There is the correct date: September 9th, one month from today. And the VISA card ends in 4242.

Alright: the informational part of the account page is done. But, the user still needs to be able to do some pretty important stuff, like cancelling their subscription - yes, this does happen, it's nothing personal - and updating their credit card. Let's get to it.

Leave a comment!

0
Login or Register to join the conversation
Cat in space

"Houston: no signs of life"
Start the conversation!

This tutorial uses an older version of Symfony of the stripe-php SDK. The majority of the concepts are still valid, though there *are* differences. We've done our best to add notes & comments that describe these changes.

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": ">=5.5.9, <7.4",
        "symfony/symfony": "3.1.*", // v3.1.10
        "doctrine/orm": "^2.5", // v2.7.2
        "doctrine/doctrine-bundle": "^1.6", // 1.6.8
        "doctrine/doctrine-cache-bundle": "^1.2", // 1.3.0
        "symfony/swiftmailer-bundle": "^2.3", // v2.6.2
        "symfony/monolog-bundle": "^2.8", // v2.12.1
        "symfony/polyfill-apcu": "^1.0", // v1.3.0
        "sensio/distribution-bundle": "^5.0", // v5.0.22
        "sensio/framework-extra-bundle": "^3.0.2", // v3.0.26
        "incenteev/composer-parameter-handler": "^2.0", // v2.1.2
        "friendsofsymfony/user-bundle": "~2.0.1", // v2.0.1
        "stof/doctrine-extensions-bundle": "^1.2", // v1.2.2
        "stripe/stripe-php": "^3.15", // v3.23.0
        "doctrine/doctrine-migrations-bundle": "^1.1", // v1.2.1
        "phpunit/phpunit": "^5.5", // 5.7.20
        "composer/package-versions-deprecated": "^1.11" // 1.11.99
    },
    "require-dev": {
        "sensio/generator-bundle": "^3.0", // v3.1.4
        "symfony/phpunit-bridge": "^3.0", // v3.3.0
        "hautelook/alice-bundle": "^1.3", // v1.4.1
        "doctrine/data-fixtures": "^1.2" // v1.2.2
    }
}
userVoice