* @param array $plan Plan settings.
protected function is_subscription_plan_valid( array $plan ): bool {
return ! empty( $plan['customer_email'] ) && $this->is_recurring_settings_ok( $plan );
* Check if recurring settings is configured correctly.
* @param array $settings Settings data.
protected function is_recurring_settings_ok( array $settings ): bool {
// Check subscription settings are provided.
if ( empty( $settings['phase_cadence'] ) || empty( $settings['customer_email'] ) || empty( $settings['customer_name'] ) ) {
$error = esc_html__( 'Square subscription payment stopped, missing form settings.', 'wpforms-lite' );
// Check for required customer email.
if ( ! $error && empty( $this->fields[ $settings['customer_email'] ]['value'] ) ) {
$error = esc_html__( 'Square subscription payment stopped, customer email not found.', 'wpforms-lite' );
// Check for required customer name.
if ( ! $error && empty( $this->fields[ $settings['customer_name'] ]['value'] ) ) {
$error = esc_html__( 'Square subscription payment stopped, customer name not found.', 'wpforms-lite' );
// Before proceeding, check if any basic errors were detected.
$this->log_errors( $error, $settings );
* Retrieve single payment args.
private function get_payment_args_single(): array {
$args = $this->get_payment_args_general();
$customer_name = $this->get_customer_name();
$customer_email = $this->get_customer_email();
if ( isset( $customer_name['first_name'] ) ) {
$args['billing']['first_name'] = sanitize_text_field( $customer_name['first_name'] );
if ( isset( $customer_name['last_name'] ) ) {
$args['billing']['last_name'] = sanitize_text_field( $customer_name['last_name'] );
if ( ! empty( $this->fields[ $this->settings['billing_address'] ] ) ) {
$args['billing']['address'] = $this->fields[ $this->settings['billing_address'] ];
if ( ! empty( $customer_email ) ) {
$args['buyer_email'] = sanitize_email( $customer_email );
$description = empty( $this->settings['payment_description'] ) ? $this->get_form_name() : html_entity_decode( $this->settings['payment_description'], ENT_COMPAT, 'UTF-8' );
// The maximum length for the Square notes field is 500 characters.
$args['note'] = wp_html_excerpt( Square::APP_NAME . ': ' . $description, 500 );
$args['order_items'] = $this->get_order_items();
* Filter single payment arguments.
* @param array $args The single payment arguments.
* @param Process $process The Process instance.
return (array) apply_filters( 'wpforms_square_process_get_payment_args_single', $args, $this ); // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName
* Retrieve arguments for any type of payment.
private function get_payment_args_general(): array {
* Filter arguments for any type of payment.
* @param array $args The general payment arguments.
* @param Process $process The Process instance.
return (array) apply_filters( // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName
'wpforms_square_process_get_payment_args_general',
'amount' => Helpers::format_amount( $this->amount ),
'currency' => $this->currency,
'location_id' => Helpers::get_location_id(),
* Retrieve a payment currency.
private function get_currency(): string {
return strtoupper( wpforms_get_currency() );
* Retrieve a payment amount.
private function get_amount(): string {
$amount = wpforms_get_total_payment( $this->fields );
return $amount === false ? wpforms_sanitize_amount( 0 ) : $amount;
private function get_order_items(): array {
* Filter order items types.
* @param array $types The order items types.
$types = (array) apply_filters( 'wpforms_square_process_get_order_items_types', wpforms_payment_fields() ); // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName
foreach ( $this->fields as $field_id => $field ) {
empty( $field['type'] ) ||
! in_array( $field['type'], $types, true )
// Skip payment field that is not filled in.
! isset( $this->entry['fields'][ $field_id ] ) ||
wpforms_is_empty_string( $this->entry['fields'][ $field_id ] )
$items[] = $this->prepare_order_line_item( $field );
private function get_form_name(): string {
if ( ! empty( $this->form_data['settings']['form_title'] ) ) {
return sanitize_text_field( $this->form_data['settings']['form_title'] );
$fallback = sprintf( /* translators: %d - Form ID. */
esc_html__( 'Form #%d', 'wpforms-lite' ),
$form_obj = wpforms()->obj( 'form' );
$form = $form_obj->get( $this->form_id );
return $form instanceof WP_Post ? $form->post_title : $fallback;
* Retrieve a Square credit card field.
private function get_credit_card_field(): array {
if ( ! is_array( $this->fields ) ) {
foreach ( $this->fields as $field ) {
if ( ! empty( $field['type'] ) && $field['type'] === 'square' ) {
* Prepare order line item.
* @param array $field Field data.
private function prepare_order_line_item( array $field ): array {
$field_id = absint( $field['id'] );
$quantity = isset( $field['quantity'] ) ? (int) $field['quantity'] : 1;
$name = empty( $field['name'] ) ? sprintf( /* translators: %d - Field ID. */ esc_html__( 'Field #%d', 'wpforms-lite' ), $field_id ) : $field['name'];
if ( empty( $field['value_raw'] ) ) {
$item['amount'] = Helpers::format_amount( $field['amount_raw'] );
return $this->prepare_order_line_item_variations( $item, $field, $field_id );
* Prepare order line item variations.
* @param array $item Item data.
* @param array $field Field data.
* @param int $field_id Field ID.
private function prepare_order_line_item_variations( array $item, array $field, int $field_id ): array {
$values = explode( ',', $field['value_raw'] );
foreach ( $values as $value ) {
if ( empty( $this->form_data['fields'][ $field_id ]['choices'][ $value ] ) ) {
$choice = $this->form_data['fields'][ $field_id ]['choices'][ $value ];
$item['variations'][] = [
'quantity' => $item['quantity'],
'variation_name' => empty( $choice['label'] ) ? sprintf( /* translators: %d - Choice ID. */ esc_html__( 'Choice %d', 'wpforms-lite' ), absint( $value ) ) : $choice['label'],
'amount' => Helpers::format_amount( $choice['value'] ),
* @param array $errors Errors to display.
private function display_errors( array $errors = [] ) {
if ( ! $errors || ! is_array( $errors ) ) {
// Check if the form contains a required credit card. If it does
// and there was an error, return the error to the user and prevent
// the form from being submitted. This should not occur under normal
if ( empty( $this->cc_field ) || empty( $this->form_data['fields'][ $this->cc_field['id'] ] ) ) {
if ( ! empty( $this->form_data['fields'][ $this->cc_field['id'] ]['required'] ) ) {
wpforms()->obj( 'process' )->errors[ $this->form_id ]['footer'] = implode( '<br>', $errors );
* Collect errors from API and turn it into form errors.
* @param string $type Payment type (e.g. 'single').
private function process_api_errors( string $type ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.Found
$errors = $this->api->get_errors();
if ( empty( $errors ) || ! is_array( $errors ) ) {
$this->display_errors( $errors );
if ( $type === 'subscription' ) {
$title = esc_html__( 'Square subscription payment stopped', 'wpforms-lite' );
$title = esc_html__( 'Square payment stopped', 'wpforms-lite' );
$_errors = $this->api->get_response_errors();
if ( ! empty( $_errors ) ) {
$this->process_api_errors_codes( $_errors );
// Log transaction specific errors.
$this->log_errors( $title, $errors );
* Check specific error codes.
* @param array $errors The last API call errors.
private function process_api_errors_codes( array $errors ) {
$codes = $this->get_oauth_error_codes();
foreach ( $errors as $error ) {
empty( $error['code'] ) ||
! in_array( $error['code'], $codes, true )
// If the error indicates that access token is bad, set a connection as invalid.
->set_status( Connection::STATUS_INVALID )
* Retrieve OAuth-related errors.
* @link https://developer.squareup.com/docs/oauth-api/best-practices#ensure-api-calls-made-with-oauth-tokens-handle-token-based-errors-appropriately
private function get_oauth_error_codes(): array {
return [ ErrorCode::ACCESS_TOKEN_EXPIRED, ErrorCode::ACCESS_TOKEN_REVOKED, ErrorCode::UNAUTHORIZED ];
* @param string $title Error title.
* @param array|string $messages Error messages.
* @param string $level Error level to add to 'payment' error level.
protected function log_errors( string $title, $messages = [], string $level = 'error' ) {
'type' => [ 'payment', $level ],
'form_id' => $this->form_id,
* Update the credit card field value to contain basic details.
private function update_credit_card_field_value() {
if ( $this->errors || ! $this->api ) {
$card = $this->get_card();
'brand' => $card->getCardBrand(),
'last4' => $card->getLast4(),
'holder' => $this->get_card_holder( $card ),
$details = implode( "\n", array_filter( $details ) );
* Filter a credit card field value by card details.
* @param string $details Card details.
* @param Process $process Process object.
$details = apply_filters( // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName
'wpforms_square_process_update_credit_card_field_value',
wpforms()->obj( 'process' )->fields[ $this->cc_field['id'] ]['value'] = $details;
private function get_card() {
$resource = $this->api->get_response_resource();
if ( empty( $resource ) ) {
$type = Helpers::array_key_first( $resource );
return $type === 'subscription' ? $this->api->get_subscription_card( $resource[ $type ] ) : $resource[ $type ]->getCardDetails()->getCard();
* Update entry details and add meta for a successful payment.