supports[] = 'one-time'; $this->supports[] = 'recurring'; $this->supports[] = 'fees'; $this->supports[] = 'gateway-submits-form'; $this->secret_word = isset( $rcp_options['twocheckout_secret_word'] ) ? trim( $rcp_options['twocheckout_secret_word'] ) : ''; if( $this->test_mode ) { $this->secret_key = isset( $rcp_options['twocheckout_test_private'] ) ? trim( $rcp_options['twocheckout_test_private'] ) : ''; $this->publishable_key = isset( $rcp_options['twocheckout_test_publishable'] ) ? trim( $rcp_options['twocheckout_test_publishable'] ) : ''; $this->seller_id = isset( $rcp_options['twocheckout_test_seller_id'] ) ? trim( $rcp_options['twocheckout_test_seller_id'] ) : ''; $this->environment = 'sandbox'; } else { $this->secret_key = isset( $rcp_options['twocheckout_live_private'] ) ? trim( $rcp_options['twocheckout_live_private'] ) : ''; $this->publishable_key = isset( $rcp_options['twocheckout_live_publishable'] ) ? trim( $rcp_options['twocheckout_live_publishable'] ) : ''; $this->seller_id = isset( $rcp_options['twocheckout_live_seller_id'] ) ? trim( $rcp_options['twocheckout_live_seller_id'] ) : ''; $this->environment = 'production'; } if( ! class_exists( 'Twocheckout' ) ) { require_once RCP_PLUGIN_DIR . 'pro/includes/libraries/twocheckout/Twocheckout.php'; } } // end init /** * Process registration * * @access public * @since 2.3 * @return void */ public function process_signup() { Twocheckout::privateKey( $this->secret_key ); Twocheckout::sellerId( $this->seller_id ); Twocheckout::sandbox( $this->test_mode ); /** * @var RCP_Payments $rcp_payments_db */ global $rcp_payments_db; $member = new RCP_Member( $this->user_id ); // for backwards compatibility only if( empty( $_POST['twoCheckoutToken'] ) ) { rcp_errors()->add( 'missing_card_token', __( 'Missing 2Checkout token, please try again or contact support if the issue persists.', 'rcp' ), 'register' ); return; } $paid = false; if ( $this->auto_renew ) { $payment_type = 'Credit Card'; $line_items = array( array( "recurrence" => $this->length . ' ' . ucfirst( $this->length_unit ), "type" => 'product', "price" => $this->amount, "productId" => $this->subscription_id, "name" => $this->subscription_name, "quantity" => '1', "tangible" => 'N', "startupFee" => $this->initial_amount - $this->amount ) ); } else { $payment_type = 'Credit Card One Time'; $line_items = array( array( "recurrence" => 0, "type" => 'product', "price" => $this->initial_amount, "productId" => $this->subscription_id, "name" => $this->subscription_name, "quantity" => '1', "tangible" => 'N' ) ); } try { $charge = Twocheckout_Charge::auth( array( 'merchantOrderId' => $this->subscription_key, 'token' => $_POST['twoCheckoutToken'], 'currency' => strtolower( $this->currency ), 'billingAddr' => array( 'name' => sanitize_text_field( $_POST['rcp_card_name'] ), 'addrLine1' => sanitize_text_field( $_POST['rcp_card_address'] ), 'city' => sanitize_text_field( $_POST['rcp_card_city'] ), 'state' => sanitize_text_field( $_POST['rcp_card_state'] ), 'zipCode' => sanitize_text_field( $_POST['rcp_card_zip'] ), 'country' => sanitize_text_field( $_POST['rcp_card_country'] ), 'email' => $this->email, ), "lineItems" => $line_items, )); if( $charge['response']['responseCode'] == 'APPROVED' ) { // This activates the user's account. $rcp_payments_db->update( $this->payment->id, array( 'date' => date( 'Y-m-d H:i:s', current_time( 'timestamp' ) ), 'payment_type' => $payment_type, 'transaction_id' => $charge['response']['orderNumber'], 'status' => 'complete' ) ); do_action( 'rcp_gateway_payment_processed', $member, $this->payment->id, $this ); $paid = true; } } catch ( Twocheckout_Error $e ) { $this->error_message = $e->getMessage(); do_action( 'rcp_registration_failed', $this ); wp_die( $e->getMessage(), __( 'Error', 'rcp' ), array( 'response' => '401' ) ); } if ( $paid ) { $this->membership->add_note( __( 'Subscription started in 2Checkout', 'rcp' ) ); $this->membership->set_gateway_subscription_id( '2co_' . $charge['response']['orderNumber'] ); do_action( 'rcp_2co_signup', $this->user_id, $this ); } // redirect to the success page, or error page if something went wrong wp_redirect( $this->return_url ); exit; } /** * Proccess webhooks * * @access public * @since 2.3 * @return void */ public function process_webhooks() { if ( isset( $_GET['listener'] ) && $_GET['listener'] == '2checkout' ) { rcp_log( 'Starting to process 2Checkout webhook.' ); global $wpdb; $hash = strtoupper( md5( $_POST['sale_id'] . $this->seller_id . $_POST['invoice_id'] . $this->secret_word ) ); if ( ! hash_equals( $hash, $_POST['md5_hash'] ) ) { rcp_log( 'Exiting 2Checkout webhook - invalid MD5 hash.', true ); die('-1'); } if ( empty( $_POST['message_type'] ) ) { rcp_log( 'Exiting 2Checkout webhook - empty message_type.', true ); die( '-2' ); } if ( empty( $_POST['vendor_id'] ) ) { rcp_log( 'Exiting 2Checkout webhook - empty vendor_id.', true ); die( '-3' ); } $subscription_key = sanitize_text_field( $_POST['vendor_order_id'] ); $this->membership = rcp_get_membership_by( 'subscription_key', $subscription_key ); if ( empty( $this->membership ) ) { rcp_log( sprintf( 'Exiting 2Checkout webhook - membership not found from order ID %s.', $subscription_key ), true ); die( '-4' ); } $member = new RCP_Member( $this->membership->get_user_id() ); // for backwards compatibility if( 'twocheckout' != $this->membership->get_gateway() ) { rcp_log( 'Exiting 2Checkout webhook - membership is not a 2Checkout subscription.' ); return; } rcp_log( sprintf( 'Processing webhook for membership #%d.', $this->membership->get_id() ) ); $payments = new RCP_Payments(); switch( strtoupper( $_POST['message_type'] ) ) { case 'ORDER_CREATED' : rcp_log( 'Processing 2Checkout ORDER_CREATED webhook.' ); break; case 'REFUND_ISSUED' : rcp_log( sprintf( 'Processing 2Checkout REFUND_ISSUED webhook for invoice ID %s.', $_POST['invoice_id'] ) ); $payment = $payments->get_payment_by( 'transaction_id', $_POST['invoice_id'] ); $payments->update( $payment->id, array( 'status' => 'refunded' ) ); if( ! empty( $_POST['recurring'] ) ) { $this->membership->cancel(); $this->membership->add_note( __( 'Membership cancelled via refund 2Checkout', 'rcp' ) ); } break; case 'RECURRING_INSTALLMENT_SUCCESS' : rcp_log( sprintf( 'Processing 2Checkout RECURRING_INSTALLMENT_SUCCESS webhook for membership #%d.', $this->membership->get_id() ) ); $payment_data = array( 'date' => date( 'Y-m-d H:i:s', strtotime( $_POST['timestamp'], current_time( 'timestamp' ) ) ), 'subscription' => $this->membership->get_membership_level_name(), 'payment_type' => sanitize_text_field( $_POST['payment_type'] ), 'transaction_type' => 'renewal', 'subscription_key' => $subscription_key, 'amount' => sanitize_text_field( $_POST['item_list_amount_1'] ), // don't have a total from this call, but this should be safe 'subtotal' => sanitize_text_field( $_POST['item_list_amount_1'] ), 'user_id' => $this->membership->get_user_id(), 'customer_id' => $this->membership->get_customer()->get_id(), 'membership_id' => $this->membership->get_id(), 'transaction_id' => sanitize_text_field( $_POST['invoice_id'] ), 'gateway' => 'twocheckout' ); $recurring = ! empty( $_POST['recurring'] ); $this->membership->renew( $recurring ); $payment_id = $payments->insert( $payment_data ); $this->membership->add_note( __( 'Membership renewed in 2Checkout', 'rcp' ) ); do_action( 'rcp_webhook_recurring_payment_processed', $member, $payment_id, $this ); do_action( 'rcp_gateway_payment_processed', $member, $payment_id, $this ); break; case 'RECURRING_INSTALLMENT_FAILED' : rcp_log( 'Processing 2Checkout RECURRING_INSTALLMENT_FAILED webhook.' ); if ( ! empty( $_POST['sale_id'] ) ) { $this->webhook_event_id = sanitize_text_field( $_POST['sale_id'] ); } do_action( 'rcp_recurring_payment_failed', $member, $this ); break; case 'RECURRING_STOPPED' : rcp_log( 'Processing 2Checkout RECURRING_STOPPED webhook.' ); if ( $this->membership->has_payment_plan() && $this->membership->at_maximum_renewals() ) { rcp_log( sprintf( 'Membership #%d has completed its payment plan - not cancelling.', $this->membership->get_id() ) ); } else { if ( $this->membership->is_active() ) { $this->membership->cancel(); $this->membership->add_note( __( 'Membership cancelled via 2Checkout webhook.', 'rcp' ) ); } else { rcp_log( sprintf( 'Membership #%d is not active - not cancelling.', $this->membership->get_id() ) ); } do_action( 'rcp_webhook_cancel', $member, $this ); } break; case 'RECURRING_COMPLETE' : rcp_log( 'Processing 2Checkout RECURRING_COMPLETE webhook.' ); break; case 'RECURRING_RESTARTED' : rcp_log( 'Processing 2Checkout RECURRING_RESTARTED webhook.' ); $this->membership->set_status( 'active' ); $this->membership->add_note( __( 'Subscription restarted in 2Checkout', 'rcp' ) ); do_action( 'rcp_webhook_recurring_payment_profile_created', $member, $this ); break; case 'FRAUD_STATUS_CHANGED' : rcp_log( sprintf( 'Processing 2Checkout FRAUD_STATUS_CHANGED webhook. Status: %s', $_POST['fraud_status'] ) ); switch ( $_POST['fraud_status'] ) { case 'pass': break; case 'fail': $this->membership->set_status( 'pending' ); $this->membership->add_note( __( 'Payment flagged as fraudulent in 2Checkout', 'rcp' ) ); break; case 'wait': break; } break; } do_action( 'rcp_2co_' . strtolower( $_POST['message_type'] ) . '_ins', $member ); die( 'success'); } } /** * Display fields and add extra JavaScript * * @access public * @since 2.3 * @return void */ public function fields() { ob_start(); ?> add( 'missing_card_code', __( 'The security code you have entered is invalid', 'rcp' ), 'register' ); } if( empty( $_POST['rcp_card_address'] ) ) { rcp_errors()->add( 'missing_card_address', __( 'The address you have entered is invalid', 'rcp' ), 'register' ); } if( empty( $_POST['rcp_card_city'] ) ) { rcp_errors()->add( 'missing_card_city', __( 'The city you have entered is invalid', 'rcp' ), 'register' ); } if( empty( $_POST['rcp_card_state'] ) && $this->card_needs_state_and_zip() ) { rcp_errors()->add( 'missing_card_state', __( 'The state you have entered is invalid', 'rcp' ), 'register' ); } if( empty( $_POST['rcp_card_country'] ) ) { rcp_errors()->add( 'missing_card_country', __( 'The country you have entered is invalid', 'rcp' ), 'register' ); } if( empty( $_POST['rcp_card_zip'] ) && $this->card_needs_state_and_zip() ) { rcp_errors()->add( 'missing_card_zip', __( 'The zip / postal code you have entered is invalid', 'rcp' ), 'register' ); } } /** * Load 2Checkout JS * * @access public * @since 2.3 * @return void */ public function scripts() { wp_enqueue_script( 'twocheckout', 'https://www.2checkout.com/checkout/api/2co.min.js', array( 'jquery' ) ); } /** * Determine if zip / state are required * * @access private * @since 2.3 * @return bool */ private function card_needs_state_and_zip() { $ret = true; if( ! empty( $_POST['rcp_card_country'] ) ) { $needs_zip = array( 'AR', 'AU', 'BG', 'CA', 'CH', 'CY', 'EG', 'FR', 'IN', 'ID', 'IT', 'JP', 'MY', 'ME', 'NL', 'PA', 'PH', 'PO', 'RO', 'RU', 'SR', 'SG', 'ZA', 'ES', 'SW', 'TH', 'TU', 'GB', 'US' ); if( ! in_array( $_POST['rcp_card_country'], $needs_zip ) ) { $ret = false; } } return $ret; } }