%1$s%2$s
%3$s
%4$s
%5$s
%6$s %7$s
%6$s %7$s
%8$s %2$s
%9$s
checkout.session.completed,
charge.refunded,
charge.refund.updated,
charge.succeeded,
invoice.paid,
invoice.payment_failed,
customer.subscription.deleted,
customer.subscription.updated,
',
__('Webhook URL: ', 'fluent-cart'), // %1$s
$webhook_url, // %2$s (reused)
__('You should configure your Stripe webhooks to get all updates of your payments remotely.', 'fluent-cart'), // %3$s
__('How to configure?', 'fluent-cart'), // %4$s
__('In your Stripe account:', 'fluent-cart'), // %5$s
__('Go to Developers > Webhooks >', 'fluent-cart'), // %6$s
__('Add endpoint', 'fluent-cart'), // %7$s
__('Enter The Webhook URL:', 'fluent-cart'), // %8$s
__('Select these events:', 'fluent-cart') // %9$s
);
}
public function processAndInsertOrderByEvent($event)
{
$eventType = $event->type;
$metaDataEvents = [
'invoice.paid', // Reviewed for subscription cycle
'charge.refunded', // reviewed
'charge.succeeded', // reviewed
'charge.dispute.created',
'charge.dispute.closed',
'checkout.session.completed',
'customer.subscription.deleted',
'customer.subscription.updated',
];
if (!in_array($eventType, $metaDataEvents)) {
return false;
}
$vendorDataObject = $event->data->object;
if ($eventType == 'invoice.paid') {
//check if subscription billing_cycle invoice paid or failed
$isSubscriptionCycle = $vendorDataObject->billing_reason === 'subscription_cycle';
if ($isSubscriptionCycle) {
if ($eventType === 'invoice.paid') {
$vendorDataObject = (new API())->getStripeObject('invoices/' . $vendorDataObject->id, ['expand' => ['payment_intent']]);
$createdOrder = $this->processSubscriptionRenewal($vendorDataObject);
if ($createdOrder) {
wp_send_json([
'message' => 'Subscription renewal processed successfully. Order ID: ' . $createdOrder->id,
], 200);
}
}
return false;
}
}
if ($eventType === 'charge.refunded' || $eventType === 'charge.succeeded') {
$paymentIntent = $vendorDataObject->payment_intent;
$orderTransaction = OrderTransaction::query()->where('vendor_charge_id', $paymentIntent)
->where('transaction_type', 'charge')
->first();
if (!$orderTransaction) {
$orderTransaction = apply_filters('fluent_cart/stripe/fallback_order_transaction', null, $vendorDataObject);
if (!$orderTransaction || $orderTransaction instanceof OrderTransaction) {
$orderTransaction = null;
}
}
if ($orderTransaction) {
$order = Order::where('id', $orderTransaction->order_id)->first();
$order->current_transaction = $orderTransaction;
return $order;
}
}
if ($eventType === 'customer.subscription.deleted' || $eventType === 'customer.subscription.updated') {
$vendorSubscriptionId = $vendorDataObject->id;
$subscription = Subscription::query()
->where('vendor_subscription_id', $vendorSubscriptionId)
->first();
if ($subscription) {
$order = Order::where('id', $subscription->parent_order_id)->first();
if ($order) {
$order->current_subscription = $subscription;
}
return $order;
}
}
if ($eventType === 'charge.dispute.created' || $eventType === 'charge.dispute.closed') {
$paymentIntent = $vendorDataObject->payment_intent;
$orderTransaction = OrderTransaction::query()->where('vendor_charge_id', $paymentIntent)
->first();
if ($orderTransaction) {
return $orderTransaction->order;
}
return null;
}
// Handle checkout.session.completed for hosted checkout
if ($eventType === 'checkout.session.completed') {
$sessionId = $vendorDataObject->id;
return StripeHelper::validateBySession($sessionId);
}
$metaData = (array)$vendorDataObject->metadata;
$orderHash = Arr::get($metaData, 'fct_ref_id', false);
if ($orderHash) {
return Order::query()->where('uuid', $orderHash)->first();
}
return null;
}
public function processSubscriptionRenewal($vendorInvoiceObject)
{
$subscription = null;
$parentOrder = null;
$vendorSubscriptionId = Arr::get($vendorInvoiceObject, 'subscription', null)
?: (Arr::get($vendorInvoiceObject, 'parent.subscription_details.subscription', null) ?? null);
if ($vendorSubscriptionId) {
$subscription = Subscription::query()->where('vendor_subscription_id', $vendorSubscriptionId)
->orderBy('id', 'DESC')
->first();
}
if ($subscription) {
$parentOrder = Order::query()->where('id', $subscription->parent_order_id)->first();
}
if (!$parentOrder) {
// let's try to find from the meta ref id
$refId = Arr::get($vendorInvoiceObject, 'subscription_details.metadata.fct_ref_id', null);
if ($refId) {
$parentOrder = Order::query()->where('uuid', $refId)->first();
}
}
if ($parentOrder && !$subscription) {
$subscription = Subscription::query()
->where('parent_order_id', $parentOrder->id)
->orderBy('id', 'DESC')
->first();
}
if (!$parentOrder || !$subscription || $subscription->current_payment_method !== 'stripe') {
fluent_cart_error_log('Stripe Webhook Error: Subscription Renewal - Order or Subscription not found.', 'Vendor Subscription ID: ' . $vendorSubscriptionId);
return false; // this is not our order
};
$paymentIntent = Arr::get($vendorInvoiceObject, 'payment_intent', null);
if (is_array($paymentIntent)) {
$paymentIntentId = Arr::get($paymentIntent, 'id', null);
} else {
$paymentIntentId = $paymentIntent;
}
if ($paymentIntent) {
$alreadyRecorded = OrderTransaction::query()
->where('subscription_id', $subscription->id)
->where('vendor_charge_id', $paymentIntentId)
->exists();
if ($alreadyRecorded) {
return null; // already recorded
}
}
$amountPaid = Arr::get($vendorInvoiceObject, 'amount_paid', 0);
$chargeCurrency = Arr::get($vendorInvoiceObject, 'currency', null);
if ($chargeCurrency && CurrenciesHelper::isZeroDecimal($chargeCurrency)) {
$amountPaid = $amountPaid * 100;
}
$transactionData = [
'payment_method' => 'stripe',
'total' => $amountPaid,
'vendor_charge_id' => $paymentIntentId
];
$paymentIntent = (new API())->getStripeObject('payment_intents/' . $paymentIntentId, [
'expand' => ['latest_charge']
], $parentOrder->mode);
if (!is_wp_error($paymentIntent)) {
$transactionData['card_last_4'] = Arr::get($paymentIntent, 'latest_charge.payment_method_details.card.last4', '');
$transactionData['card_brand'] = (string)Arr::get($paymentIntent, 'latest_charge.payment_method_details.card.brand', '');
$transactionData['payment_method_type'] = (string)Arr::get($paymentIntent, 'latest_charge.payment_method_details.type', '');
} else {
$activePaymentMethod = $subscription->getMeta('active_payment_method', []);
if (!$activePaymentMethod || !is_array($activePaymentMethod)) {
$activePaymentMethod = [];
}
if ($activePaymentMethod) {
$transactionData['card_last_4'] = Arr::get($activePaymentMethod, 'details.last_4');
$transactionData['card_brand'] = (string)Arr::get($activePaymentMethod, 'details.brand');
$transactionData['payment_method_type'] = (string)Arr::get($activePaymentMethod, 'details.type');
}
}
$subscriptionUpdateData = array_filter([
'current_payment_method' => 'stripe'
]);
$stripeSubscription = (new API())->getStripeObject('subscriptions/' . $vendorSubscriptionId, [
'expand' => ['latest_invoice']
], $parentOrder->mode);
if (!is_wp_error($stripeSubscription)) {
$subscriptionUpdateData = StripeHelper::getSubscriptionUpdateData($stripeSubscription, $subscription);
}
$createdTransaction = SubscriptionService::recordRenewalPayment($transactionData, $subscription, $subscriptionUpdateData);
$subscription = Subscription::query()->find($subscription->id);
if ($subscription && $subscription->status === Status::SUBSCRIPTION_COMPLETED) {
if (!is_wp_error($stripeSubscription)) {
if ($stripeSubscription['status'] === 'active') {
$deleted = (new API)->deleteStripeObject('subscriptions/' . $vendorSubscriptionId, [], $parentOrder->mode);
if (is_wp_error($deleted)) {
fluent_cart_error_log('Stripe Subscription Deletion Error. Subscription ID: ' . $subscription->id, $deleted->get_error_message());
}
}
}
}
if ($createdTransaction) {
return $createdTransaction->order;
}
return null;
}
}