/** * Renders the rate of $this payment item * * @param string $variable * @param boolean $html TRUE: HTML rendering, FALSE: TEXT rendering * @param boolean $rounded * @return string|null */ public function renderItemRate($variable, $html, $rounded = false) { $first_var = 'first_' . $variable; $cbpaidMoney = cbpaidMoney::getInstance(); if (!$this->_paymentBasket->isAnyAutoRecurring()) { return $cbpaidMoney->renderPrice($this->{$variable}, $this->currency, $html, $rounded, false); } else { if ($this->{$first_var} || $this->{$variable}) { $first = $cbpaidMoney->renderPrice($this->{$first_var}, $this->currency, $html, $rounded, false); $then = $cbpaidMoney->renderPrice($this->{$variable}, $this->currency, $html, $rounded, false); if ($this->{$first_var} && $this->{$variable} === null) { // $ret = $first; // replacing this line by next one fixes bug #3624 at display time $ret = sprintf($html ? CBPTXT::Th("%s, then %s") : CBPTXT::T("%s, then %s"), $first, $then); } elseif ($this->{$variable} && $this->{$first_var} === null) { $ret = $then; } elseif ($this->{$first_var} === $this->{$variable} && $this->_paymentBasket->period1 == null) { $ret = $first; } else { $ret = sprintf($html ? CBPTXT::Th("%s, then %s") : CBPTXT::T("%s, then %s"), $first, $then); } } else { if ($variable == 'rate' && $this->rate !== null) { $ret = $cbpaidMoney->renderPrice($this->{$variable}, $this->currency, $html, $rounded, false); } else { $ret = null; } } } return $ret; }
/** * Handles the gateway-specific result of payments (redirects back to this site and gateway notifications). WARNING: unchecked access ! * * @param cbpaidPaymentBasket $paymentBasket New empty object. returning: includes the id of the payment basket of this callback (strictly verified, otherwise untouched) * @param array $postdata _POST data for saving edited tab content as generated with getEditTab * @param string $result result= get parameter, other than 'notify', 'success' or 'cancel'. * @return string HTML to display if frontend, text to return to gateway if notification, FALSE if registration cancelled and ErrorMSG generated, or NULL if nothing to display */ protected function handleOtherResult( $paymentBasket, $postdata, $result ) { global $_CB_framework; $privateVarsList = 'id user_id time_initiated time_completed ip_addresses mc_gross mc_currency ' . 'quantity item_number item_name shared_secret payment_status ' . 'invoice period1 period2 period3 mc_amount1 mc_amount2 mc_amount3'; $ret = null; // $privateVarsList = 'id payment_method gateway_account user_id time_initiated time_completed ip_addresses mc_gross mc_currency quantity item_number item_name shared_secret payment_status'; if ( $result == 'freetrial' ) { $paymentBasketId = (int) $this->_getReqParam( 'basket' ); if ( $paymentBasketId ) { $exists = $paymentBasket->load( (int) $paymentBasketId ); if ( $exists && ( $this->_getReqParam( 'cbpid' ) == $paymentBasket->shared_secret ) && ( $paymentBasket->payment_status == 'NotInitiated' ) ) { $isAnyAutoRecurring = $paymentBasket->isAnyAutoRecurring(); if ( ( $isAnyAutoRecurring == 2 && ( ( $paymentBasket->period1 ) && ( $paymentBasket->mc_amount1 == 0 ) ) ) || ( ( $paymentBasket->mc_amount1 == 0 ) && ( $paymentBasket->mc_amount3 == 0 ) && ( $paymentBasket->mc_gross == 0 ) ) ) { // user-choice: no need to wait for payment basket completed to activate subscriptions: $paymentBasket->payment_method = $this->getPayName(); // $paymentBasket->gateway_account = $this->getAccountParam( 'id' ); // $ipn = null; $ipn = $this->_logNotification( 'P', $_CB_framework->now(), $paymentBasket ); $paymentBasket->bindObjectToThisObject( $ipn, $privateVarsList ); $this->updatePaymentStatus( $paymentBasket, 'web_accept', 'FreeTrial', $ipn, 1, 0, 0, true ); } } } } return $ret; }
/** * Checks single payment and payment subscription possibilities depending on payment processor and on payment basket * * @param int $enable_processor payment processor state: 0: disabled, 1: only for single payments, 2: only for payment subscriptions, 3: for single and subscription payments * @param cbpaidPaymentBasket $paymentBasket for checking if this basket has recurring payments * @return int bits: 0x1 : single payment to be offered, 0x2: payment subscription to be offered, (0x3: both to be offered) : these are bits, do logical AND ( & ) */ protected function _getPaySubscribePossibilities($enable_processor, $paymentBasket) { $pay1subscribe2 = 0; $isAnyAutoRecurring = $paymentBasket->isAnyAutoRecurring(); if ($isAnyAutoRecurring == 2 && ($paymentBasket->mc_amount1 == 0 && $paymentBasket->period1 != '')) { $isAnyAutoRecurring = 1; // first period is free: nothing to pay upfront: enforce autorecurring } $paySubscriptionForced = $enable_processor == 2 || $enable_processor == 3 && $isAnyAutoRecurring == 1; $paySinglePaymentForced = $enable_processor == 1 || $enable_processor == 3 && $isAnyAutoRecurring == 0; $payUserChoicePossible = $enable_processor == 3 && $isAnyAutoRecurring == 2; if ($payUserChoicePossible || $paySinglePaymentForced || !$paySubscriptionForced) { // last condition is a safeguard to display at least a payment button $pay1subscribe2 += 1; } if ($payUserChoicePossible || $isAnyAutoRecurring && $paySubscriptionForced) { $pay1subscribe2 += 2; } return $pay1subscribe2; }
/** * perform anti fraud checks on ipn values * * @param cbpaidPaymentNotification $ipn * @param cbpaidPaymentBasket $paymentBasket * @return bool|string */ private function _validateIPN( $ipn, $paymentBasket ) { global $_CB_database; $matching = true; if ( in_array( $ipn->payment_status, array( 'Completed', 'Processed', 'Canceled_Reversal' ) ) ) { if ( $ipn->txn_type == 'subscr_payment' ) { $payments = $paymentBasket->getPaymentsTotals( $ipn->txn_id ); if ( ( $paymentBasket->mc_amount1 != 0 ) && ( $payments->count == 0 ) ) { $amount = $paymentBasket->mc_amount1; } else { $amount = $paymentBasket->mc_amount3; } if ( sprintf( '%.2f', $ipn->mc_gross ) != sprintf( '%.2f', $amount ) ) { if ( ( sprintf( '%.2f', $ipn->mc_gross ) < sprintf( '%.2f', $amount ) ) || ( sprintf( '%.2f', ( $ipn->mc_gross - $ipn->tax ) ) != sprintf( '%.2f', $amount ) ) ) { if ( ( ! ( ( $paymentBasket->mc_amount1 != 0 ) && ( $payments->count == 0 ) ) ) && ( ( (float) sprintf( '%.2f', ( $ipn->mc_gross - abs( $ipn->tax ) ) ) ) < ( (float) sprintf( '%.2f', $amount ) ) ) ) { $matching = CBPTXT::P( 'amount mismatch on recurring_payment: $amount: [amount] != IPN mc_gross: [gross] or IPN mc_gross - IPN tax: [net] where IPN tax = [tax]', array( '[amount]' => $amount, '[net]' => ( $ipn->mc_gross - $ipn->tax ), '[gross]' => $ipn->mc_gross, '[tax]' => $ipn->tax ) ); } } } } else { if ( sprintf( '%.2f', $ipn->mc_gross ) != sprintf( '%.2f', $paymentBasket->mc_gross ) ) { if ( ( sprintf( '%.2f', $ipn->mc_gross ) < sprintf( '%.2f', $paymentBasket->mc_gross ) ) || ( sprintf( '%.2f', $ipn->mc_gross - $ipn->tax ) != sprintf( '%.2f', $paymentBasket->mc_gross ) ) ) { $matching = CBPTXT::P( 'amount mismatch on webaccept: BASKET mc_gross: [basket_gross] != IPN mc_gross: [gross] or IPN mc_gross - IPN tax: [net] where IPN tax = [tax]', array( '[basket_gross]' => $paymentBasket->mc_gross, '[net]' => ( $ipn->mc_gross - $ipn->tax ), '[gross]' => $ipn->mc_gross, '[tax]' => $ipn->tax ) ); } } } } if ( in_array( $ipn->txn_type, array( 'subscr_signup', 'subscr_modify', 'subscr_eot', 'subscr_cancel', 'subscr_failed', 'subscr_payment' ) ) ) { if ( ! $paymentBasket->isAnyAutoRecurring() ) { $matching = CBPTXT::P( 'paypal subscription IPN type [txn_type] for a basket without auto-recurring items', array( '[txn_type]' => $ipn->txn_type ) ); } } if ( ! in_array( $ipn->txn_type, array( 'subscr_signup', 'subscr_modify', 'subscr_eot', 'subscr_cancel', 'subscr_failed' ) ) ) { if ( ( $ipn->txn_id === '' ) || ( $ipn->txn_id === 0 ) || ( $ipn->txn_id === null ) ) { $matching = CBPTXT::T( 'illegal transaction id' ); } else { $countBaskets = $paymentBasket->countRows( "txn_id = '" . $_CB_database->getEscaped( $ipn->txn_id ) . "' AND payment_status = 'Completed'" ); if ( ( $countBaskets == 1 ) && ( $paymentBasket->txn_id != $ipn->txn_id ) || ( $countBaskets > 1 ) ) { $matching = CBPTXT::P( 'transaction already used for [count] other already completed payment(s)', array( '[count]' => $countBaskets ) ); } } } return $matching; }
/** * Checks against frauds for PDT and for IPN * * In order to prevent fraud, PayPal recommends that your programs verify the following: * When you receive a VERIFIED response, perform the following checks: * 1. Check that the payment_status is Completed. (NOT done here, but in UpdatePayment status) * 2. If the payment_status is Completed, check the txn_id against the previous completed PayPal * transaction you have processed to ensure it is not a duplicate. * 3. After you have checked the payment_status and txn_id, make sure the * receiver_email is an email address registered in your PayPal account. * 4. Check that the price, mc_gross, and currency, mc_currency, are correct for the item, * item_name or item_number. * 5. Check the the shared secret returned to you is correct. * * @param cbpaidPaymentNotification $ipn notification verified with paypal * @param cbpaidPaymentBasket $paymentBasket matched basket * @param string $cbpid shared secret which should be returned by paypal * @return boolean|string TRUE for no fraud detected, otherwise error TEXT */ private function _checkNotPayPalFraud( $ipn, $paymentBasket, $cbpid ) { global $_CB_database; $matching = true; // 3) receiver_email is an email address registered in your PayPal account, to prevent the payment // from being sent to a fraudulent account: $receiver_email = strtolower( trim( $this->getAccountParam( 'paypal_receiver_email' ) ) ); $business = strtolower( trim( $this->getAccountParam( 'paypal_business' ) ) ); if ( $receiver_email ) { if ( strtolower( $ipn->receiver_email ) != $receiver_email ) { // let's give a second, third and fourth chance to misconfigurations: if ( ( strtolower( $ipn->business ) != $business ) && ( strtolower( $ipn->receiver_email ) != $business ) && ( strtolower( $ipn->business ) != $receiver_email ) ) { $matching = sprintf( "receiver_email mismatch: parametered business (%s) or receiver (%s) expected does not match IPN business (%s) or receiver (%s).", $business, $receiver_email, $ipn->business, $ipn->receiver_email ); } } } else { if ( strtolower( $ipn->business ) != $business ) { // let's give a second chance to misconfigurations: if ( strtolower( $ipn->receiver_email ) != $business ) { $matching = sprintf( "business email mismatch: parametered business (%s) or receiver (%s) expected does not match IPN receiver (%s).", $business, $receiver_email, $ipn->receiver_email ); } } } // 4) Check transaction details, such as the item number and price, to confirm that the price has not // been changed: mc_gross, and currency, mc_currency, item_name or item_number: if ( in_array( $ipn->payment_status, array( 'Completed', 'Processed', 'Canceled_Reversal' ) ) ) { if ( $ipn->txn_type == 'subscr_payment' ) { $checkFields = array( 'mc_currency' => 100, 'item_name' => 127, 'item_number' => 127 ); $txt_error = "currency, item name or number mismatch"; // treat 'mc_gross': $payments = $paymentBasket->getPaymentsTotals( $ipn->txn_id ); if ( ( $paymentBasket->mc_amount1 != 0 ) && ( $payments->count == 0 ) ) { $tobepaid = $paymentBasket->mc_amount1; } else { $tobepaid = $paymentBasket->mc_amount3; } if ( sprintf( '%.2f', $ipn->mc_gross ) != sprintf( '%.2f', $tobepaid ) ) { // try to check if it's a paypal tax added in paypal: if ( ( sprintf( '%.2f', $ipn->mc_gross ) < sprintf( '%.2f', $tobepaid ) ) || ( sprintf( '%.2f', $ipn->mc_gross - $ipn->tax ) != sprintf( '%.2f', $tobepaid ) ) ) { // Final attempt to say "ok": if there is an increase in this recurring payment (not first one) done at paypal's side (20% per period max): if ( ( ! ( ( $paymentBasket->mc_amount1 != 0 ) && ( $payments->count == 0 ) ) ) && ( ( (float ) sprintf( '%.2f', $ipn->mc_gross - abs( $ipn->tax ) ) ) < (float) sprintf( '%.2f', $tobepaid ) ) ) { $matching = sprintf("amount mismatch on subscr_payment: tobepaid: %s != IPN mc_gross: %s or IPN mc_gross - IPN tax: %s where IPN tax = %s", $tobepaid, $ipn->mc_gross - $ipn->tax, $ipn->mc_gross, $ipn->tax ); } } } } else { // elseif ( $ipn->txn_type == 'web_accept' ) { if ( sprintf( '%.2f', $ipn->mc_gross ) != sprintf( '%.2f', $paymentBasket->mc_gross ) ) { // try to check if it's a paypal tax added in paypal: if ( ( sprintf( '%.2f', $ipn->mc_gross ) < sprintf( '%.2f', $paymentBasket->mc_gross ) ) || ( sprintf( '%.2f', $ipn->mc_gross - $ipn->tax ) != sprintf( '%.2f', $paymentBasket->mc_gross ) ) ) { $matching = sprintf("amount mismatch on webaccept: BASKET mc_gross: %s != IPN mc_gross: %s or IPN mc_gross - IPN tax: %s where IPN tax = %s", $paymentBasket->mc_gross, $ipn->mc_gross - $ipn->tax, $ipn->mc_gross, $ipn->tax ); } } $checkFields = array( 'mc_currency' => 100, 'item_name' => 127, 'item_number' => 127, 'quantity' => 127 ); if ( $ipn->payment_status == 'Canceled_Reversal' ) { // for some reasons, Cancel_Reversal (we won!) don't provide quantity, so do not check: (bug #1099) unset( $checkFields['quantity'] ); } $txt_error = "currency, item name or number or quantity mismatch"; } foreach ( $checkFields as $cf => $csize ) { if ( !isset( $ipn->$cf) || !isset( $paymentBasket->$cf) || ( trim( substr( $ipn->$cf, 0, $csize ) ) != trim( substr( $paymentBasket->$cf, 0, $csize ) ) ) ) { // print_r($ipn); print_r($paymentBasket); $matching = $txt_error . ': ' . sprintf( "IPN %s (%s) does not match basket %s (%s) nor their trimmed sizes for IPN (%s) and basket (%s)", $cf, $ipn->$cf, $cf, $paymentBasket->$cf, trim( substr( $ipn->$cf, 0, $csize ) ), trim( substr( $paymentBasket->$cf, 0, $csize ) ) ); break; } } } else { //TBD: see what to check for other events... } if ( in_array( $ipn->txn_type, array( 'subscr_signup', 'subscr_modify', 'subscr_eot', 'subscr_cancel', 'subscr_failed', 'subscr_payment' ) ) ) { if ( ! $paymentBasket->isAnyAutoRecurring() ) { $matching = sprintf( "paypal subscription IPN type %s for a basket without auto-recurring items", $ipn->txn_type ); } } // 2) txn_id is not a duplicate to prevent someone from reusing an old, completed transaction: if ( ! in_array( $ipn->txn_type, array( 'subscr_signup', 'subscr_modify', 'subscr_eot', 'subscr_cancel', 'subscr_failed' ) ) ) { if ( ( $ipn->txn_id === '' ) || ( $ipn->txn_id === 0 ) || ( $ipn->txn_id === null ) ) { $matching = "illegal transaction id"; } else { $countBaskets = $paymentBasket->countRows( "txn_id = '" . $_CB_database->getEscaped( $ipn->txn_id ) . "' AND payment_status = 'Completed'" ); if ( ( $countBaskets == 1 ) && ( $paymentBasket->txn_id != $ipn->txn_id ) || ( $countBaskets > 1 ) ) { $matching = sprintf( "transaction already used for %d other already completed payment(s)", $countBaskets ); } } } // 5) Check the the shared secret returned to you is correct. if ( $cbpid != $paymentBasket->shared_secret ) { $matching = sprintf( "shared secret '%s' returned by Paypal does not match the value we expected", htmlspecialchars( $cbpid ) ); } return $matching; }