Tribe__Tickets__Commerce__PayPal__Main::generate_tickets( string $payment_status = 'completed', bool $redirect = true )

Generate and store all the attendees information for a new order.


Parameters

$redirect

(bool) (Optional) Whether the client should be redirected or not.

Default value: true

$payment_status

(string) (Optional) The tickets payment status, defaults to completed.

Default value: 'completed'


Top ↑

Source

File: src/Tribe/Commerce/PayPal/Main.php

	public function generate_tickets( $payment_status = 'completed', $redirect = true ) {
		/** @var Tribe__Tickets__Commerce__PayPal__Gateway $gateway */
		$gateway          = tribe( 'tickets.commerce.paypal.gateway' );

		$transaction_data = $gateway->get_transaction_data();

		/** @var Tribe__Tickets__Commerce__PayPal__Cart__Interface $cart */
		$cart = tribe( 'tickets.commerce.paypal.cart' );

		/**
		 * The `invoice` variable is a passthrough one; if passed when adding items to the cart
		 * then it should be returned to us from PayPal. If we have it in the transaction data
		 * we can assume the cart associated with the invoice, if any, can be removed.
		 *
		 * @link https://developer.paypal.com/docs/classic/paypal-payments-standard/integration-guide/formbasics/#variations-on-basic-variables
		 */
		if ( ! empty( $transaction_data['custom'] ) ) {
			$decoded_custom = Tribe__Tickets__Commerce__PayPal__Custom_Argument::decode( $transaction_data['custom'], true );
			if ( isset( $decoded_custom['invoice'] ) ) {
				$cart->set_id( $decoded_custom['invoice'] );
				$cart->clear();
			}
		}

		$raw_transaction_data = $gateway->get_raw_transaction_data();

		if ( empty( $transaction_data ) || empty( $transaction_data['items'] ) ) {
			return;
		}

		$has_tickets = $post_id = false;

		/**
		 * PayPal Ticket specific action fired just before a PayPalTicket-driven attendee tickets for an order are generated
		 *
		 * @since 4.7
		 *
		 * @param array $transaction_data PayPal payment data
		 */
		do_action( 'tribe_tickets_tpp_before_order_processing', $transaction_data );

		$order_id = $transaction_data['txn_id'];

		$is_refund = Tribe__Tickets__Commerce__PayPal__Stati::$refunded === $payment_status
		             || 'refund' === Tribe__Utils__Array::get( $transaction_data, 'reason_code', '' );

		if ( $is_refund ) {
			$transaction_data['payment_status'] = $payment_status = Tribe__Tickets__Commerce__PayPal__Stati::$refunded;

			$refund_order_id = $order_id;
			$order_id        = Tribe__Utils__Array::get( $transaction_data, 'parent_txn_id', $order_id );
			$order           = Tribe__Tickets__Commerce__PayPal__Order::from_order_id( $order_id );

			$order->refund_with( $refund_order_id );

			unset( $transaction_data['txn_id'], $transaction_data['parent_txn_id'] );

			$order->hydrate_from_transaction_data( $transaction_data );
		} else {
			$order = Tribe__Tickets__Commerce__PayPal__Order::from_transaction_data( $transaction_data );
		}

		$order->set_meta( 'transaction_data', $raw_transaction_data );

		$custom = Tribe__Tickets__Commerce__PayPal__Custom_Argument::decode( $transaction_data['custom'], true );

		/*
		 * This method might run during a POST (IPN) PayPal request hence the
		 * purchasing user ID, if any, will be stored in a custom PayPal var.
		 * Let's fallback on the current user ID for GET requests (PDT); it will be always `0`
		 * during a PayPal POST (IPN) request.
		 */
		$attendee_user_id = ! isset( $custom['user_id'] ) ? get_current_user_id() : absint( $custom['user_id'] );

		$attendee_full_name = empty( $transaction_data['first_name'] ) && empty( $transaction_data['last_name'] )
			? ''
			: sanitize_text_field( "{$transaction_data['first_name']} {$transaction_data['last_name']}" );

		$attendee_email = empty( $transaction_data['payer_email'] ) ? null : sanitize_email( $transaction_data['payer_email'] );
		$attendee_email = is_email( $attendee_email ) ? $attendee_email : null;

		if ( ! empty( $attendee_user_id ) ) {
			$attendee = get_user_by( 'id', $attendee_user_id );

			// Check if the user was found.
			if ( $attendee ) {
				// Check if the user has an email address.
				if ( $attendee->user_email ) {
					$attendee_email = $attendee->user_email;
				}

				$user_full_name = trim( "{$attendee->first_name} {$attendee->last_name}" );

				// Check if the user has first/last name.
				if ( ! empty( $user_full_name ) ) {
					$attendee_full_name = $user_full_name;
				}
			}
		}

		/**
		 * This is an array of tickets IDs for which the user decided to opt-out.
		 *
		 * @see \Tribe__Tickets_Plus__Commerce__PayPal__Attendees::register_optout_choice()
		 */
		$attendee_optouts = Tribe__Utils__Array::list_to_array( Tribe__Utils__Array::get( $custom, 'oo', array() ), ',' );

		if ( ! $attendee_email || ! $attendee_full_name ) {
			$this->redirect_after_error( 101, $redirect, $post_id );
			return;
		}

		// Iterate over each product
		foreach ( (array) $transaction_data['items'] as $item ) {
			$order_attendee_id = 0;

			if ( empty( $item['ticket'] ) ) {
				continue;
			}

			/** @var \Tribe__Tickets__Ticket_Object $ticket_type */
			$ticket_type = $item['ticket'];
			$product_id  = $ticket_type->ID;

			// Get the event this tickets is for
			$post = $ticket_type->get_event();

			if ( empty( $post ) ) {
				continue;
			}

			$post_id = $post->ID;

			// if there were no PayPal tickets for the product added to the cart, continue
			if ( empty( $item['quantity'] ) ) {
				continue;
			}

			// get the PayPal status `decrease_stock_by` value
			$status_stock_size = 1;

			$ticket_qty = (int) $item['quantity'];

			// to avoid tickets from not being created on a status stock size of 0
			// let's take the status stock size into account and create a number of tickets
			// at least equal to the number of tickets the user requested
			$ticket_qty = $status_stock_size < 1 ? $ticket_qty : $status_stock_size * $ticket_qty;

			$qty = max( $ticket_qty, 0 );

			// Throw an error if Qty is bigger then Remaining
			if ( $ticket_type->managing_stock() && $payment_status === Tribe__Tickets__Commerce__PayPal__Stati::$completed ) {
				$this->ignore_pending_stock_logic( true );
				$inventory = (int) $ticket_type->inventory();
				$this->ignore_pending_stock_logic( false );

				$inventory_is_not_unlimited = - 1 !== $inventory;

				if ( $inventory_is_not_unlimited && $qty > $inventory ) {
					if ( ! $order->was_pending() ) {
						$this->redirect_after_error( 102, $redirect, $post_id );
						return;
					}

					/** @var Tribe__Tickets__Commerce__PayPal__Oversell__Policies $oversell_policies */
					$oversell_policies = tribe( 'tickets.commerce.paypal.oversell.policies' );
					$oversell_policy   = $oversell_policies->for_post_ticket_order( $post_id, $ticket_type->ID, $order_id );

					$qty = $oversell_policy->modify_quantity( $qty, $inventory );

					if ( ! $oversell_policy->allows_overselling() ) {
						$oversold_attendees = $this->get_attendees_by_order_id( $order_id );
						$oversell_policy->handle_oversold_attendees( $oversold_attendees );
						$this->redirect_after_error( 102, $redirect, $post_id );
						return;
					}
				}
			}

			if ( $qty === 0 ) {
				$this->redirect_after_error( 103, $redirect, $post_id );
				return;
			}

			$has_tickets = true;

			/**
			 * PayPal specific action fired just before a PayPal-driven attendee ticket for an event is generated
			 *
			 * @since 4.7
			 *
			 * @param int $post_id ID of event
			 * @param string $ticket_type Ticket Type object for the product
			 * @param array $data Parsed PayPal transaction data
			 */
			do_action( 'tribe_tickets_tpp_before_attendee_ticket_creation', $post_id, $ticket_type, $transaction_data );

			$existing_attendees = $this->get_attendees_by_order_id( $order_id );

			$has_generated_new_tickets = false;

			/** @var Tribe__Tickets__Commerce__Currency $currency */
			$currency        = tribe( 'tickets.commerce.currency' );
			$currency_symbol = $currency->get_currency_symbol( $product_id, true );

			// Iterate over all the amount of tickets purchased (for this product)
			for ( $i = 0; $i < $qty; $i ++ ) {
				$attendee_id = null;
				$updating_attendee = false;

				// check if we already have an attendee or not
				$post_title        = $attendee_full_name . ' | ' . ( $i + 1 );
				$criteria          = array( 'post_title' => $post_title, 'product_id' => $product_id, 'event_id' => $post_id );
				$existing_attendee = wp_list_filter( $existing_attendees, $criteria );

				if ( ! empty( $existing_attendee ) ) {
					$existing_attendee = reset( $existing_attendee );
					$updating_attendee = true;
					$attendee_id       = $existing_attendee['attendee_id'];
				} else {
					$attendee = array(
						'post_status' => 'publish',
						'post_title'  => $post_title,
						'post_type'   => $this->attendee_object,
						'ping_status' => 'closed',
					);

					// Insert individual ticket purchased
					$attendee_id = wp_insert_post( $attendee );

					// since we are creating at least one
					$has_generated_new_tickets = true;
				}

				$global_stock = new Tribe__Tickets__Global_Stock( $post_id );
				$shared_capacity = false;
				if ( $global_stock->is_enabled() ) {
					$shared_capacity = true;
				}

				if ( $status_stock_size > 0 ) {
					switch ( $payment_status ) {
						case Tribe__Tickets__Commerce__PayPal__Stati::$completed:
							$this->increase_ticket_sales_by( $product_id, 1, $shared_capacity, $global_stock );
							break;
						case Tribe__Tickets__Commerce__PayPal__Stati::$refunded:
							$this->decrease_ticket_sales_by( $product_id, 1, $shared_capacity, $global_stock );
							break;
						default:
							break;
					}
				}

				$attendee_order_status = trim( strtolower( $payment_status ) );

				if ( ! $updating_attendee ) {
					update_post_meta( $attendee_id, $this->attendee_product_key, $product_id );
					update_post_meta( $attendee_id, $this->attendee_event_key, $post_id );
					update_post_meta( $attendee_id, $this->security_code, $this->generate_security_code( $attendee_id ) );
					update_post_meta( $attendee_id, $this->order_key, $order_id );
					$attendee_optout = Tribe__Utils__Array::get( $attendee_optouts, $product_id, false );
					update_post_meta( $attendee_id, $this->attendee_optout_key, (bool) $attendee_optout );
					update_post_meta( $attendee_id, $this->email, $attendee_email );
					update_post_meta( $attendee_id, $this->full_name, $attendee_full_name );
					update_post_meta( $attendee_id, '_paid_price', get_post_meta( $product_id, '_price', true ) );
					update_post_meta( $attendee_id, '_price_currency_symbol', $currency_symbol );
				}

				update_post_meta( $attendee_id, $this->attendee_tpp_key, $attendee_order_status );

				if ( Tribe__Tickets__Commerce__PayPal__Stati::$refunded === $payment_status ) {
					$refund_order_id = Tribe__Utils__Array::get( $transaction_data, 'txn_id', '' );
					update_post_meta( $attendee_id, $this->refund_order_key, $refund_order_id );
				}

				if ( ! $updating_attendee ) {
					/**
					 * Action fired when an PayPal attendee ticket is created
					 *
					 * @since 4.7
					 *
					 * @param int    $attendee_id           Attendee post ID
					 * @param int    $order_id              PayPal Order ID
					 * @param int    $product_id            PayPal ticket post ID
					 * @param int    $order_attendee_id     Attendee number in submitted order
					 * @param string $attendee_order_status The order status for the attendee.
					 */
					do_action( 'event_tickets_tpp_attendee_created', $attendee_id, $order_id, $product_id, $order_attendee_id, $attendee_order_status );
				}

				/**
				 * Action fired when an PayPal attendee ticket is updated.
				 *
				 * This action will fire both when the attendee is created and
				 * when the attendee is updated.
				 * Hook into the `event_tickets_tpp_attendee_created` action to
				 * only act on the attendee creation.
				 *
				 * @since 4.7
				 *
				 * @param int    $attendee_id           Attendee post ID
				 * @param int    $order_id              PayPal Order ID
				 * @param int    $product_id            PayPal ticket post ID
				 * @param int    $order_attendee_id     Attendee number in submitted order
				 * @param string $attendee_order_status The order status for the attendee.
				 */
				do_action( 'event_tickets_tpp_attendee_updated', $attendee_id, $order_id, $product_id, $order_attendee_id, $attendee_order_status );

				$order->add_attendee( $attendee_id );

				$this->record_attendee_user_id( $attendee_id, $attendee_user_id );
				$order_attendee_id++;

				if ( ! empty( $existing_attendee ) ) {
					$existing_attendees = wp_list_filter( $existing_attendees, array( 'attendee_id' => $existing_attendee['attendee_id'] ), 'NOT' );
				}
			}

			if ( ! ( empty( $existing_attendees ) || empty( $oversell_policy ) ) ) {
				// an oversell policy applied: what to do with existing oversold attendees?
				$oversell_policy->handle_oversold_attendees( $existing_attendees );
			}

			if ( $has_generated_new_tickets ) {
				/**
				 * Action fired when a PayPal has had attendee tickets generated for it.
				 *
				 * @since 4.7
				 *
				 * @param int $product_id PayPal ticket post ID
				 * @param int $order_id   ID of the PayPal order
				 * @param int $qty        Quantity ordered
				 */
				do_action( 'event_tickets_tpp_tickets_generated_for_product', $product_id, $order_id, $qty );
			}

			/**
			 * Action fired when a PayPal has had attendee tickets updated for it.
			 *
			 * This will fire even when tickets are initially created; if you need to hook on the
			 * creation process only use the 'event_tickets_tpp_tickets_generated_for_product' action.
			 *
			 * @since 4.7
			 *
			 * @param int $product_id PayPal ticket post ID
			 * @param int $order_id   ID of the PayPal order
			 * @param int $qty        Quantity ordered
			 */
			do_action( 'event_tickets_tpp_tickets_generated_for_product', $product_id, $order_id, $qty );


			// After Adding the Values we Update the Transient
			Tribe__Post_Transient::instance()->delete( $post_id, Tribe__Tickets__Tickets::ATTENDEES_CACHE );
		}

		$order->update();

		/**
		 * Fires when an PayPal attendee tickets have been generated.
		 *
		 * @since 4.7
		 *
		 * @param int    $order_id              ID of the PayPal order
		 * @param int    $post_id               ID of the post the order was placed for
		 */
		do_action( 'event_tickets_tpp_tickets_generated', $order_id, $post_id );

		/**
		 * Filters whether a confirmation email should be sent or not for PayPal tickets.
		 *
		 * This applies to attendance and non attendance emails.
		 *
		 * @since 4.7
		 *
		 * @param bool $send_mail Defaults to `true`.
		 */
		$send_mail = apply_filters( 'tribe_tickets_tpp_send_mail', true );

		if (
			$send_mail
			&& $has_tickets
			&& $attendee_order_status === Tribe__Tickets__Commerce__PayPal__Stati::$completed
		) {
			$this->send_tickets_email( $order_id, $post_id );
		}

		// Redirect to the same page to prevent double purchase on refresh
		if ( ! empty( $post_id )  ) {
			/** @var \Tribe__Tickets__Commerce__PayPal__Endpoints $endpoints */
			$endpoints = tribe( 'tickets.commerce.paypal.endpoints' );
			$url       = $endpoints->success_url( $order_id, $post_id );
			if ( $redirect ) {
				wp_redirect( esc_url_raw( $url ) );
			}
			tribe_exit();
		}
	}

Top ↑

Changelog

Changelog
Version Description
4.7 Introduced.