: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
namespace WPForms\Integrations\Stripe\Api\Webhooks;
use WPForms\Vendor\Stripe\Invoice;
use WPForms\Integrations\Stripe\Helpers;
use WPForms\Db\Payments\Queries;
* Webhook invoice.created class.
class InvoiceCreated extends Base {
* Handle invoice.created webhook.
* @throws RuntimeException If original subscription not found or not updated.
public function handle() {
if ( ! isset( $this->data->billing_reason ) || $this->data->billing_reason !== 'subscription_cycle' ) {
return false; // Webhook handler for Invoice.Create supports only billing_reason = subscription_cycle.
$original_subscription = ( new Queries() )->get_subscription( $this->data->subscription );
if ( is_null( $original_subscription ) ) {
return false; // Original subscription not found.
$renewal = ( new Queries() )->get_renewal_by_invoice_id( $this->data->id );
if ( ! is_null( $renewal ) ) {
return false; // Renewal already exists.
$renewal_id = $this->insert_renewal( $original_subscription );
throw new RuntimeException( 'Subscription renewal not saved in database' );
$this->insert_renewal_meta( $renewal_id, $original_subscription );
wpforms()->get( 'payment_meta' )->add_log(
'Stripe renewal was created (Invoice ID: %1$s).',
$this->finalize_invoice();
* @param object $original_subscription Original subscription.
private function insert_renewal( $original_subscription ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh
$currency = strtoupper( $this->data->currency );
$amount = $this->data->amount_due / Helpers::get_decimals_amount( $currency );
return wpforms()->get( 'payment' )->add(
'mode' => $original_subscription->mode,
'form_id' => isset( $original_subscription->form_id ) ? $original_subscription->form_id : 0,
'entry_id' => isset( $original_subscription->entry_id ) ? $original_subscription->entry_id : 0,
'title' => $original_subscription->title,
'subtotal_amount' => $amount,
'total_amount' => $amount,
'subscription_id' => $original_subscription->subscription_id,
'customer_id' => $original_subscription->customer_id,
'date_created_gmt' => gmdate( 'Y-m-d H:i:s', $this->data->lines->data[0]->period->start ),
'date_updated_gmt' => gmdate( 'Y-m-d H:i:s' ),
* @param int $renewal_id Renewal ID.
* @param object $original_subscription Original subscription.
private function insert_renewal_meta( $renewal_id, $original_subscription ) {
$meta = $this->copy_meta_from_db( $original_subscription->id );
$meta['invoice_id'] = $this->data->id;
$meta['customer_email'] = isset( $this->data->customer_email ) ? $this->data->customer_email : '';
wpforms()->get( 'payment_meta' )->bulk_add( $renewal_id, $meta );
* Copy meta from original subscription.
* @param int $original_subscription_id Original subscription ID.
private function copy_meta_from_db( $original_subscription_id ) {
$all_meta = wpforms()->get( 'payment_meta' )->get_all( $original_subscription_id );
foreach ( $db_meta_keys as $key ) {
if ( isset( $all_meta[ $key ]->value ) ) {
$meta[ $key ] = $all_meta[ $key ]->value;
* @throws RuntimeException If invoice not finalized.
private function finalize_invoice() {
$invoice = new Invoice();
$invoice = $invoice->retrieve( $this->data->id, Helpers::get_auth_opts() );
if ( empty( $invoice->finalized_at ) ) {
$invoice->finalizeInvoice();
} catch ( Exception $e ) {
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
throw new RuntimeException( $e->getMessage() );