Skip to content
jovvie.com

Stripe recurring payments with the Jovvie PHP SDK

This page shows you how to set up a recurring payment (a subscription) with the Stripe SDK. The customer pays the first payment in person on the Jovvie payment page. Stripe then charges the same card on each renewal, and Jovvie tells your server about each renewal with a webhook.

Recurring payments are available with the Stripe SDK only. The NMI SDK does not support them.

  • You can create one-time payments. See Create a payment (Stripe).
  • Your webhook endpoint is set up and verifies events. See Handle webhooks.
  • You have a customer ID from the merchant’s Stripe account for the person who will pay. You can find customer IDs in your Stripe Dashboard.

Call payments()->createSubscriptionsPayment() with these arguments, in this order:

Argument What it is
$customer The customer ID from the merchant’s Stripe account.
$items An array of one or more items. See Items.
$currency A three-letter ISO currency code, such as usd.
$description A description of the recurring payment. It cannot be empty.
$returnUrl Where the browser goes after the payment page is done. Must be a full URL.
$cancelUrl Where the browser goes when staff click ← Back and the payment is cancelled. A pending payment is first marked cancelled. Other statuses use $returnUrl. Must be a full URL.
$options Optional: lineItems, lineTotal, metadata, and preferences. See Line items and total.
<?php
$payment = $client->payments()->createSubscriptionsPayment(
'YOUR_CUSTOMER_ID',
[
[
'price' => [
'product' => ['name' => 'Monthly membership'],
'unitAmount' => 2000, // 20.00 in USD
'recurring' => [
'interval' => 'month',
'intervalCount' => 1,
],
],
],
],
'usd',
'Membership for member YOUR_MEMBER_ID',
'YOUR_RETURN_URL',
'YOUR_CANCEL_URL',
[
'lineItems' => [
['name' => 'Monthly membership', 'interval' => 'Every 1 month', 'amount' => '20.00'],
],
'lineTotal' => '20.00',
'metadata' => ['member_id' => 'YOUR_MEMBER_ID'],
]
);
// Save these with your record.
$paymentId = $payment->id;
$subscriptionId = $payment->generatedData->subscriptionId;
$checkoutLink = $payment->generatedData->checkoutLink;

The method returns a SubscriptionPayment object. Its status starts as pending. Besides the fields of a one-time payment, generatedData has:

Field What it is
subscriptionId The ID of the subscription in the merchant’s Stripe account.
subscriptionItems The items of the subscription. Each has an id and a price with its id, product, and name.

Each item in $items has a price and, optionally, a quantity (a whole number of 1 or more).

The price can be the ID of an existing price in the merchant’s Stripe account, or an array with these keys:

Key What it is
product The ID of an existing product in the merchant’s Stripe account, or an array with a name (required), and optionally a description and metadata, to create a new product.
unitAmount The amount to charge each time, as an integer in the smallest currency unit. Must be at least 1.
recurring How often to charge. interval is day, week, month, or year. intervalCount is how many intervals between charges, 1 or more. The default is 1.

If you pass a product as an array, Jovvie creates a new product in the merchant’s Stripe account each time. To reuse a product, save generatedData->subscriptionItems[0]->price->product from the first recurring payment and pass that ID next time.

Always pass lineItems and lineTotal, so the payment page shows clear amounts.

  • Each line item has a name, an amount, and an interval, all as strings. quantity is optional. amount is in normal currency units, such as '20.00'. interval is required and describes how often the item repeats, such as 'Every 1 month'.
  • lineTotal is a plain number in normal currency units, such as '20.00'. Do not add a currency code to it.

metadata is stored on the subscription in the merchant’s Stripe account. Never put card data, passwords, or keys in it.

  1. Send a staff member’s browser to the checkout link. For signed-in staff, use an authorization link so the readers are ready. See Unlock card readers for staff.
  2. Staff take the first payment on the Jovvie payment page. See Take a payment on the Jovvie payment page.
  3. When the browser comes back to your return URL, fetch the payment with payments()->get($paymentId), using the payment ID you saved.
  4. Start the subscription in your app only if status is active.

Stripe saves the card used for the first payment and charges it on each renewal.

Stripe charges each renewal on schedule. You do not create renewal payments yourself. If an initial payment or renewal has an unclear result, check the payment in your Stripe Dashboard before creating a replacement or taking payment another way. Confirm that no payment for that initial payment or renewal went through and none is still processing. If you are unsure, contact Jovvie support. Jovvie sends these webhooks for a recurring payment. For all of them, getPaymentId() returns the ID of the original recurring payment you created, so you can find your record.

Event type When it is sent Other methods
jovvie.subscription.renewal.completed A renewal was paid. getPaymentIntentId(), getSubscriptionId(), getInvoiceAmount(), getInvoiceCurrency(), getInvoicePaid(), getInvoiceCreated() (a Unix timestamp)
jovvie.subscription.renewal.failed A renewal payment failed. Stripe may try again later. See Handle renewals.
jovvie.subscription.status.update The subscription changed status. getSubscriptionId(), getSubscriptionStatus()
jovvie.subscription.cancelled The subscription was cancelled. getSubscriptionId()

When a renewal is paid:

  1. Start a database transaction and insert the event ID into your events table, which has a unique constraint on the event ID. If the insert fails because the ID already exists, roll back, answer with HTTP 200, and stop. See Process each event once.
  2. Find your record by getPaymentId(). If there is no matching record, store the event so you can reconcile it later, commit, answer with HTTP 200, and stop.
  3. Check that getInvoicePaid() is true.
  4. Record the renewal in your app, with the amount from getInvoiceAmount() and the Stripe payment ID from getPaymentIntentId().
  5. Commit the transaction, then answer with HTTP 200.

If anything fails before the commit, roll back and answer with HTTP 500 so Jovvie sends the event again.

getSubscriptionStatus() returns the status as Stripe names it: active, unpaid, canceled, incomplete, or incomplete_expired.

The SDK cannot cancel a recurring payment yet. The merchant cancels the subscription in their Stripe Dashboard. Jovvie then sends jovvie.subscription.cancelled, and the payment status usually changes:

  • To cancelled, if the recurring payment had not started.
  • To ended, if it was running.

A payment that was already refunded may keep that status, or may later change to ended. Confirm the cancellation in the Stripe Dashboard, and handle jovvie.subscription.cancelled whatever the payment’s current status is.

When you receive jovvie.subscription.cancelled, stop the subscription in your app. Do not wait for the payment’s status to become ended, because it may not. See Check a payment’s status for all statuses.

The SDK cannot refund payments yet. SDK support for refunds is planned. Refunds for recurring payments are made in the Stripe Dashboard. See Refunds.