---
title: "Stripe recurring payments with the Jovvie PHP SDK"
description: "Start a recurring payment with the Stripe SDK, take the first payment in person, and track renewals and cancellations with webhooks."
url: https://docs.jovvie.com/developers/subscriptions/
section: "Developers"
last_updated: 2026-10-26
---

# Stripe recurring payments with the Jovvie PHP SDK

Section: Developers

> Start a recurring payment with the Stripe SDK, take the first payment in person, and track renewals and cancellations with webhooks.

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

- You can create one-time payments. See [Create a payment (Stripe)](https://docs.jovvie.com/developers/create-a-payment/).
- Your webhook endpoint is set up and verifies events. See [Handle webhooks](https://docs.jovvie.com/developers/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

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](https://docs.jovvie.com/developers/subscriptions/#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](https://docs.jovvie.com/developers/subscriptions/#line-items-and-total).                   |

```php
<?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`. |

### Items

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

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.

## Take the first payment

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](https://docs.jovvie.com/developers/unlock-card-readers/).
2. Staff take the first payment on the Jovvie payment page. See [Take a payment on the Jovvie payment page](https://docs.jovvie.com/checkout/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.

## 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](https://jovvie.com/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](https://docs.jovvie.com/developers/subscriptions/#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](https://docs.jovvie.com/developers/webhooks/#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`.

> **Note**
>
> For `jovvie.subscription.renewal.failed`, rely on `getPaymentId()` only. Other values may be missing, and their methods then throw a `WebhookError`. Fetch the payment with `payments()->get()` to see its current status, and let the merchant know the renewal failed.

## 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](https://docs.jovvie.com/developers/check-payment-status/) for all statuses.

## 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](https://docs.jovvie.com/developers/refunds/).

## Related pages

- [Create a payment (Stripe)](https://docs.jovvie.com/developers/create-a-payment/)
- [Handle webhooks](https://docs.jovvie.com/developers/webhooks/)
- [Check a payment’s status](https://docs.jovvie.com/developers/check-payment-status/)
