/** * Updates payment status of basket and of corresponding subscriptions if there is a change in status * * @param cbpaidPaymentBasket $paymentBasket Basket * @param string $eventType type of event (paypal type): 'web_accept', 'subscr_payment', 'subscr_signup', 'subscr_modify', 'subscr_eot', 'subscr_cancel', 'subscr_failed' * @param string $paymentStatus new status (Completed, RegistrationCancelled) * @param cbpaidPaymentNotification $notification notification object of the payment * @param int $occurrences renewal occurrences * @param int $autorecurring_type 0: not auto-recurring, 1: auto-recurring without payment processor notifications, 2: auto-renewing with processor notifications updating $expiry_date * @param int $autorenew_type 0: not auto-renewing (manual renewals), 1: asked for by user, 2: mandatory by configuration * @param boolean|string $txnIdMultiplePaymentDates FALSE: unique txn_id for each payment, TRUE: same txn_id can have multiple payment dates, additionally: 'SINGLEPAYMENT' will not look at txn_id at all * @param boolean $storePaymentRecord TRUE: normal case, create payment record if needed. FALSE: offline case where pending payment should not create a payment record. * @return void */ public function updatePaymentStatus($paymentBasket, $eventType, $paymentStatus, &$notification, $occurrences, $autorecurring_type, $autorenew_type, $txnIdMultiplePaymentDates, $storePaymentRecord = true) { global $_CB_framework, $_PLUGINS; $pluginsLoaded = false; $basketUpdateNulls = false; $previousUnifiedStatus = $this->mapPaymentStatus($paymentBasket->payment_status); $unifiedStatus = $this->mapPaymentStatus($paymentStatus); // get all related subscriptions being paid by this basket: $subscriptions = $paymentBasket->getSubscriptions(); $thisIsReferencePayment = false; $user = CBuser::getUserDataInstance((int) $paymentBasket->user_id); if ($paymentBasket->payment_status != $paymentStatus || $unifiedStatus == 'Partially-Refunded' || $autorecurring_type) { if ($paymentStatus && (in_array($eventType, array('web_accept', 'subscr_payment', 'subscr_signup')) || in_array($unifiedStatus, array('Reversed', 'Refunded', 'Partially-Refunded')))) { $paymentBasket->payment_status = $paymentStatus; } if (in_array($eventType, array('subscr_payment', 'subscr_signup'))) { $paymentBasket->recurring = 1; } if ($autorecurring_type == 0 && in_array($unifiedStatus, array('Completed', 'Processed', 'FreeTrial'))) { $paymentBasket->mc_amount1 = null; $paymentBasket->mc_amount3 = null; $paymentBasket->period1 = null; $paymentBasket->period3 = null; $basketUpdateNulls = true; } // if (count($subscriptions) >= 1) { $now = $_CB_framework->now(); $completed = false; $thisIsReferencePayment = false; $reason = null; switch ($unifiedStatus) { case 'FreeTrial': case 'Completed': case 'Processed': // this includes Canceled_Reversal !!! : if ($unifiedStatus == 'FreeTrial') { $paymentBasket->payment_status = 'Completed'; } if ($unifiedStatus == 'FreeTrial' || $unifiedStatus == 'Completed') { if ($notification->payment_date) { $time_completed = cbpaidTimes::getInstance()->gmStrToTime($notification->payment_date); } else { $time_completed = $now; } $paymentBasket->time_completed = Application::Database()->getUtcDateTime($time_completed); $completed = true; } if ($paymentStatus == 'Canceled_Reversal') { $paymentBasket->payment_status = 'Completed'; } if (is_object($notification) && isset($notification->txn_id)) { // real payment with transaction id: store as reference payment if not already stored: $thisIsReferencePayment = $this->_storePaymentOnce($paymentBasket, $notification, $now, $txnIdMultiplePaymentDates, 'Updating payment record because of new status of payment basket: ' . $unifiedStatus . ($paymentStatus != $unifiedStatus ? ' (new gateway-status: ' . $paymentStatus . ')' : '') . ' because of event received: ' . $eventType . '. Previous status was: ' . $previousUnifiedStatus); } else { // Free trials don't have a notification: $thisIsReferencePayment = true; } if ($thisIsReferencePayment) { // payment not yet processed: $autorenewed = $paymentBasket->recurring == 1 && $unifiedStatus == 'Completed' && $previousUnifiedStatus == 'Completed'; for ($i = 0, $n = count($subscriptions); $i < $n; $i++) { $reason = $autorenewed ? 'R' : $subscriptions[$i]->_reason; $subscriptions[$i]->activate($user, $now, $completed, $reason, $occurrences, $autorecurring_type, $autorenew_type, $autorenewed ? 1 : 0); } } break; case 'RegistrationCancelled': case 'Reversed': case 'Refunded': case 'Unsubscribed': if ($unifiedStatus == 'RegistrationCancelled') { if (!($previousUnifiedStatus == 'NotInitiated' || $previousUnifiedStatus === 'Pending' && $paymentBasket->payment_method === 'offline')) { return; } } for ($i = 0, $n = count($subscriptions); $i < $n; $i++) { $reason = $subscriptions[$i]->_reason; if ($reason != 'R' || in_array($unifiedStatus, array('Reversed', 'Refunded'))) { // Expired and Cancelled as well as Partially-Refunded are not reverted ! //TBD: really revert on refund everything ? a plan param would be nice here if (!in_array($previousUnifiedStatus, array('Pending', 'In-Progress', 'Denied', 'Reversed', 'Refunded')) && in_array($subscriptions[$i]->status, array('A', 'R', 'I')) && !$subscriptions[$i]->hasPendingPayment($paymentBasket->id)) { // not a cancelled or denied renewal: $subscriptions[$i]->revert($user, $unifiedStatus); } } } if ($unifiedStatus == 'RegistrationCancelled') { $paymentBasket->historySetMessage('Payment basket deleted because the subscriptions and payment got cancelled'); $paymentBasket->delete(); // deletes also payment_Items } $paidUserExtension = cbpaidUserExtension::getInstance($paymentBasket->user_id); $subscriptionsAnyAtAll = $paidUserExtension->getUserSubscriptions(''); $params = cbpaidApp::settingsParams(); $createAlsoFreeSubscriptions = $params->get('createAlsoFreeSubscriptions', 0); if (count($subscriptionsAnyAtAll) == 0 && !$createAlsoFreeSubscriptions) { $user = new UserTable(); $id = (int) cbGetParam($_GET, 'user'); $user->load((int) $id); if ($user->id && $user->block == 1) { $user->delete(null); } } break; case 'Denied': case 'Pending': if ($unifiedStatus == 'Denied') { // In fact when denied, it's the case as if the user attempted payment but failed it: He should be able to re-try: So just store the payment as denied for the records. if ($eventType == 'subscr_failed' || $eventType == 'subscr_cancel' && $autorecurring_type != 2) { // special case of a failed attempt: // or this is the final failed attempt of a basket with notifications: break; } } if ($previousUnifiedStatus == 'Completed') { return; // do not change a Completed payment as it cannot become Pending again. If we get "Pending" after "Completed", it is a messages chronological order mistake. } break; case 'In-Progress': case 'Partially-Refunded': default: break; } if ($eventType == 'subscr_cancel') { if (!in_array($unifiedStatus, array('Denied', 'Reversed', 'Refunded', 'Unsubscribed'))) { for ($i = 0, $n = count($subscriptions); $i < $n; $i++) { $subscriptions[$i]->autorecurring_cancelled($user, $unifiedStatus, $eventType); } } } for ($i = 0, $n = count($subscriptions); $i < $n; $i++) { $subscriptions[$i]->notifyPaymentStatus($unifiedStatus, $previousUnifiedStatus, $paymentBasket, $notification, $now, $user, $eventType, $paymentStatus, $occurrences, $autorecurring_type, $autorenew_type); } if (in_array($unifiedStatus, array('Denied', 'Reversed', 'Refunded', 'Partially-Refunded', 'Pending', 'In-Progress'))) { $thisIsReferencePayment = $this->_storePaymentOnce($paymentBasket, $notification, $now, $txnIdMultiplePaymentDates, 'Updating payment record because of new status of payment basket: ' . $unifiedStatus . ($paymentStatus != $unifiedStatus ? ' (new gateway-status: ' . $paymentStatus . ')' : '') . ' because of event received: ' . $eventType . '. Previous status was: ' . $previousUnifiedStatus); } // } foreach ($paymentBasket->loadPaymentTotalizers() as $totalizer) { $totalizer->notifyPaymentStatus($thisIsReferencePayment, $unifiedStatus, $previousUnifiedStatus, $paymentBasket, $notification, $now, $user, $eventType, $paymentStatus, $occurrences, $autorecurring_type, $autorenew_type, $txnIdMultiplePaymentDates); } if (!in_array($unifiedStatus, array('RegistrationCancelled'))) { if ($thisIsReferencePayment && in_array($unifiedStatus, array('Completed', 'Processed'))) { $paymentBasket->setPaidInvoiceNumber($reason); } $paymentBasket->historySetMessage('Updating payment basket ' . ($paymentStatus !== null ? 'status: ' . $unifiedStatus . ($paymentStatus != $unifiedStatus ? ' (new gateway-status: ' . $paymentStatus . ')' : '') : '') . ' because of event received: ' . $eventType . ($paymentStatus !== null ? '. Previous status was: ' . $previousUnifiedStatus : '')); $paymentBasket->store($basketUpdateNulls); } else { //TDB ? : $paymentBasket->delete(); in case of RegistrationCancelled done above, but should be done in case of FreeTrial ? (could be a param in future) } if (!in_array($unifiedStatus, array('Completed', 'Processed')) || $thisIsReferencePayment) { $_PLUGINS->loadPluginGroup('user', 'cbsubs.'); $_PLUGINS->loadPluginGroup('user/plug_cbpaidsubscriptions/plugin'); $pluginsLoaded = true; $_PLUGINS->trigger('onCPayAfterPaymentStatusChange', array(&$user, &$paymentBasket, &$subscriptions, $unifiedStatus, $previousUnifiedStatus, $occurrences, $autorecurring_type, $autorenew_type)); } } if (!in_array($unifiedStatus, array('Completed', 'Processed')) || $thisIsReferencePayment) { if (!$pluginsLoaded) { $_PLUGINS->loadPluginGroup('user', 'cbsubs.'); $_PLUGINS->loadPluginGroup('user/plug_cbpaidsubscriptions/plugin'); } $_PLUGINS->trigger('onCPayAfterPaymentStatusUpdateEvent', array(&$user, &$paymentBasket, &$subscriptions, $unifiedStatus, $previousUnifiedStatus, $eventType, &$notification)); } }