Statamic + Laravel Consulting

Charge Documentation


Last updated on April 7th, 2021

Overview

Charge is built on top of Cashier, and as such, the flow of how subscriptions work can be seen in its docs.

At the moment Charge only handles a subset of what Cashier can do. If you find something you need, please open an issue.

NOTE: Charge’s subscription tags require a logged in user.

Getting Started

Requirements

  • PHP 7.4
  • Laravel 7
  • Stripe account
  • Statamic users in the database

Installation

Grab from the marketplace or composer require silentz/charge

Configuration

Cashier

Ensure your users are stored in the database.

Add use Chargeable; to your User.php model like so:

use Illuminate\Foundation\Auth\User as Authenticatable;
use Silentz\Charge\Chargeable;

class User extends Authenticatable
{
    use Notifiable;
    use Chargeable;
...

Add the Stripe keys to your .env file:

STRIPE_KEY=pk_test_key
STRIPE_SECRET=sk_test_key
STRIPE_WEBHOOK_SECRET=whsec_secret

If you want to override Cashier’s default configuration, publish its configuration with php artisan vendor:publish --tag=cashier.

Run the Cashier migration, php artisan migrate.

Set up the Stripe webhook, with https://yoursite.com/!/charge/webhook as the endpoint.

Charge

Publish Charge’s config with php artisan vendor:publish --tag=charge-config, it will be in config/charge.php.

return [
    /*
     * When a user subscribes to a plan, which role should they be given?
     */
    'plans_and_roles' => [
    	[
            'plan' => 'the-stripe-plan',
            'role' => 'the-role'
        ],
    	[
            'plan' => 'another-stripe-plan',
            'role' => 'another-role'
        ],
    ],


    /*
     * For all below, which template & subject to use for which email.
     * Set to null to not send that email
     */
    'emails' => [
    	// send email address for all Charge emails
        'sender' => 'foo@bar.com',

        'customer_updated' => [
            'template' => 'emails.customer.updated',
            'subject' => '',
        ],
        'one_time_payment' => null,
        'subscription_canceled' => [
            'template' => '',
            'subject' => '',
        ],
        'subscription_created' => [
            'template' => 'emails.subscription.created',
            'subject' => 'Hello there',
        ],
        'subscription_payment_failed' => [
            'template' => '',
            'subject' => '',
        ],
        'subscription_payment_succeeded' => [
            'template' => '',
            'subject' => '',
        ],
        'subscription_upcoming' => [
            'template' => '',
            'subject' => '',
        ],
        'subscription_updated' => [
            'template' => '',
            'subject' => '',
        ],
    ],
];

General Flow

It’s important to understand how Stripe Subscriptions work, so please do read up.

Note that sometimes when you create a subscription, further action is required. When that happens, Charge will return you to a page of your choosing (see below), and set a couple of session variables, requires_action & payment_intent, so you can get the user to confirm the payment. See below for how to do that.

Usage

Create the Setup Intent

{{ subscription:setup_intent }} returns the setup intent needed to create a payment method (see below).

Create the Subscription

Please see the Cashier docs for an explanation of how to create a subscription with Stripe Elements and Javascript.

The basics are, you need to create a payment method and pass that to Charge, along with the subscription details.

Creating a payment method requires the payment details (collected via Stripe Elements) and a Setup Intent (via {{ subscription:setup_intent }})

Parameters

  • redirect - optional, go to this page when the subscription is created.
  • error_redirect - optional, go to this page if there was an error creating the subscription
  • action_redirect - optional, go to this page if there are actions that need completing (see below)

Any other attributes passed in will be added to the HTML form tag.

Variables

  • errors - if there were errors creating a subscription, this is where you’ll find them.
  • success - if the subscription was created successfully, this will be true.
  • subscription - available if successful and contains all the Stripe subscription data.
  • needs_action - occasionally, Stripe will require folks to confirm the purchase. In that instance, this will be true and you should .
  • action - this is the action needed, currently only incomplete is available.
  • payment_method - if action is needed, this is the payment method data, needed to confirm the payment.

List a User's Subscriptions

To get a list of a user’s subscriptions, use the {{ subscriptions }} tag:

{{ subscriptions }}
  {{ name }}
  {{ stripe_id }}
  {{ stripe_status }}
  {{ stripe_plan }}
  {{ quantity }}
  {{ trial_ends_at }}
  {{ ends_at }}
{{ /subscriptions }}

Please see the Cashier docs for an explanation on those fields.

Cancel a Subscription

Every subscription has an id, to cancel, pass it to the {{ subscription:cancel }} tag.

{{ subscription:cancel :id="id" }}
  <button>Cancel</button>
{{ /subscription:cancel }}

To get the id, loop through the user’s subscriptions via the {{ subscriptions }} tag.

Parameters

  • cancel_immediately - optional, set to true to cancel this subscription immediately, instead of at the end of the subscription. Defaults to false.

Subscription Requires Action

Like mentioned above, sometimes a payment requires action. To check use the {{ subscription:requires_action }} tag. It will return true if you need to get the user to confirm the payment (see below).

Payment Intent

If the payment does require confirmation, you need pass the payment intent’s secret into the confirmCardPayment Stripe method.

Variables

All fields from Stripe’s PaymentIntentare available to you, but you’ll likely only need the client_secret.

Templating

Creating a Subscription

Use the subscription:create tag to have the user subscribe to a plan. Don’t forget to pass the name and the plan, along with any other relevant information. Make sure the JS code has access to the setup intent.

Antlers

{{ subscription:create redirect="/success"  id="the-form" }}
    <input type="text" name="plan">

    {{ if errors }}
        Errors:
        <ul>
            {{ errors }}
                <li>{{ value }}</li>
            {{ /errors }}
        </ul>
    {{ /if }}

    {{ if success }}
        {{# show whatever you''d like when someone has successfully subscribed #}}!
    {{ /if }}

    <!-- Stripe Elements Placeholder -->
    <div id="card-element"></div>

    <button id="card-button" data-secret="{{ subscription:setup_intent }}">
        Subscribe
    </button>
{{ /subscription:create }}

Javascript

This is taken directly from the Cashier docs:

const stripe = Stripe('{{ config:cashier:key }}');

const elements = stripe.elements();
const cardElement = elements.create('card');

cardElement.mount('#card-element');

const cardButton = document.getElementById('card-button');
const clientSecret = cardButton.dataset.secret;
const form = document.getElementById('the-form');

cardButton.addEventListener('click', async (e) => {
    e.preventDefault();
    cardButton.disabled= true;
    const { setupIntent, error } = await stripe.confirmCardSetup(
        clientSecret, {
            payment_method: {
                card: cardElement,
                billing_details: {
                    name: '{{ first_name }} {{ last_name }}',
                    email: {{ email }}
                }
            }
        }
    );

    if (error) {
        alert(error);// Display "error.message" to the user...
    } else {
        // The card has been verified successfully...
        addToForm('payment_method', setupIntent.payment_method, form);
        form.submit();
    }
});

function addToForm(name, value, form) {
    let input = document.createElement('input');

    input.type = 'hidden';
    input.name = name;
    input.value = value;

    form.appendChild(input);
}

Confirm a Subscription

After the subscription is created, don’t forget to check {{ subscription:requires_action }} and if true, have the user confirm the payment.

Antlers

<h2>Confirm Subscription</h2>
<!-- Stripe Elements Placeholder -->
<div id="card-element"></div>

<button id="card-button" data-secret="{{ subscription:payment_intent }}{{ client_secret }}{{ /subscription:payment_intent }}">
    Confirm
</button>

Javascript

const cardButton = document.getElementById('card-button');
const clientSecret = cardButton.dataset.secret;

cardButton.addEventListener('click', (e) => {
    e.preventDefault();
    cardButton.disabled = true;
    stripe.confirmCardPayment(
        clientSecret,
        {
            payment_method: {
                card: cardElement,
                billing_details: { name: {{ first_name }} {{ last_name }} }
            }
        }
    ).then(function (result) {
        if (result.error) {
            cardButton.disabled = true;
            alert(result.error.code);
        } else {
            cardButton.disabled = true;
            alert('success');
        }
    });
});
© 2021 Silent Z Consulting