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.
Before you start
Section titled “Before you start”- 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.
Create the recurring payment
Section titled “Create the recurring payment”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.
Line items and total
Section titled “Line items and total”Always pass lineItems and lineTotal, so the payment page shows clear amounts.
- Each line item has a
name, anamount, and aninterval, all as strings.quantityis optional.amountis in normal currency units, such as'20.00'.intervalis required and describes how often the item repeats, such as'Every 1 month'. lineTotalis 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.
Take the first payment
Section titled “Take the first payment”- 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.
- Staff take the first payment on the Jovvie payment page. See Take a payment on the Jovvie payment page.
- When the browser comes back to your return URL, fetch the payment with
payments()->get($paymentId), using the payment ID you saved. - Start the subscription in your app only if
statusisactive.
Stripe saves the card used for the first payment and charges it on each renewal.
Handle renewals
Section titled “Handle renewals”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:
- 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.
- 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. - Check that
getInvoicePaid()istrue. - Record the renewal in your app, with the amount from
getInvoiceAmount()and the Stripe payment ID fromgetPaymentIntentId(). - 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.
Cancel a recurring payment
Section titled “Cancel a recurring payment”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.
Refunds
Section titled “Refunds”The SDK cannot refund payments yet. SDK support for refunds is planned. Refunds for recurring payments are made in the Stripe Dashboard. See Refunds.