# easy-invoice/2.4.2/includes/Gateways/OfflineGateway.php

Easy Invoice – Invoice Generator, PDF Quotes &amp; Payments, version 2.4.2. 369 lines.

- Page: https://pluginprobe.com/plugins/easy-invoice/2.4.2/code/includes/Gateways/OfflineGateway.php
- Raw: https://pluginprobe.com/plugins/easy-invoice/2.4.2/raw/includes/Gateways/OfflineGateway.php
- Modified: 2026-09-29T05:21:04+00:00

Line numbers below start at 1. Link to a line or a range by appending a fragment to the
page URL, for example `https://pluginprobe.com/plugins/easy-invoice/2.4.2/code/includes/Gateways/OfflineGateway.php#L10-L20`.

```php
<?php
/**
 * One offline payment method — bank transfer, cheque, cash, or a method the
 * merchant added — as a gateway the payment panel can offer.
 *
 * The client reads the instructions, optionally types the reference their
 * bank gave them and attaches a receipt, and submits. A pending payment
 * record is written against the invoice; staff confirm or reject it on the
 * Payments screen and only a confirmed payment counts toward the balance.
 *
 * @package EasyInvoice
 */

namespace EasyInvoice\Gateways;

use EasyInvoice\Abstracts\AbstractPaymentGateway;
use EasyInvoice\Interfaces\PaymentGatewayInterface;
use EasyInvoice\Services\InvoiceBalance;
use EasyInvoice\Services\OfflinePayments;

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

class OfflineGateway extends AbstractPaymentGateway implements PaymentGatewayInterface {

	/** @var array{id:string,title:string,description:string,icon:string} */
	private $method;

	/**
	 * @param array $method One entry of OfflinePayments::methods().
	 */
	public function __construct( array $method ) {
		parent::__construct();
		$this->method = $method;
		add_action( 'easy_invoice_payment_gateways_after', [ $this, 'maybeRenderInstructions' ], 10, 2 );
	}

	public function init(): void {
		$this->settings = $this->getSettings();
	}

	public function getName(): string {
		return (string) $this->method['id'];
	}

	public function getTitle(): string {
		return (string) $this->method['title'];
	}

	public function getDescription(): string {
		return (string) ( $this->method['description'] ?? '' );
	}

	public function getIcon(): string {
		return (string) ( $this->method['icon'] ?? 'bank' );
	}

	/** Built-in or merchant-added? The settings screen lets the latter be removed. */
	public function isCustom(): bool {
		return ! isset( OfflinePayments::builtin()[ $this->getName() ] );
	}

	public function isAvailable(): bool {
		return $this->isEnabled();
	}

	public function getSettings(): array {
		return OfflinePayments::settings( $this->getName() );
	}

	public function handleCallback( array $data ): array {
		return [
			'success' => true,
			'message' => __( 'Offline payments have no callback.', 'easy-invoice' ),
		];
	}

	/**
	 * Fields shown under the method on Settings → Payment Methods.
	 */
	public function getSettingsConfig(): array {
		$id     = $this->getName();
		$fields = [];

		if ( 'bank_transfer' === $id ) {
			$fields += [
				'easy_invoice_manual_bank_name'      => [ 'label' => __( 'Bank name', 'easy-invoice' ), 'type' => 'text', 'default' => '', 'col_span' => 'sm:col-span-6 md:col-span-3' ],
				'easy_invoice_manual_account_name'   => [ 'label' => __( 'Account name', 'easy-invoice' ), 'type' => 'text', 'default' => '', 'col_span' => 'sm:col-span-6 md:col-span-3' ],
				'easy_invoice_manual_account_number' => [ 'label' => __( 'Account number', 'easy-invoice' ), 'type' => 'text', 'default' => '', 'col_span' => 'sm:col-span-6 md:col-span-3' ],
				'easy_invoice_manual_routing_number' => [ 'label' => __( 'Routing number / sort code', 'easy-invoice' ), 'type' => 'text', 'default' => '', 'col_span' => 'sm:col-span-6 md:col-span-3' ],
				'easy_invoice_manual_iban'           => [ 'label' => __( 'IBAN', 'easy-invoice' ), 'type' => 'text', 'default' => '', 'col_span' => 'sm:col-span-6 md:col-span-3' ],
				'easy_invoice_manual_swift_code'     => [ 'label' => __( 'SWIFT / BIC', 'easy-invoice' ), 'type' => 'text', 'default' => '', 'col_span' => 'sm:col-span-6 md:col-span-3' ],
				'easy_invoice_manual_bank_address'   => [ 'label' => __( 'Bank address', 'easy-invoice' ), 'type' => 'textarea', 'default' => '', 'col_span' => 'sm:col-span-6' ],
			];
		} elseif ( 'cheque' === $id ) {
			$fields += [
				'easy_invoice_cheque_payable_to'      => [ 'label' => __( 'Make cheques payable to', 'easy-invoice' ), 'type' => 'text', 'default' => '', 'col_span' => 'sm:col-span-6 md:col-span-3' ],
				'easy_invoice_cheque_mailing_address' => [ 'label' => __( 'Mailing address', 'easy-invoice' ), 'type' => 'textarea', 'default' => '', 'col_span' => 'sm:col-span-6 md:col-span-3' ],
			];
		}

		$fields[ OfflinePayments::optionKey( $id, 'instructions' ) ] = [
			'label'       => __( 'Instructions shown to the client', 'easy-invoice' ),
			'type'        => 'textarea',
			'default'     => $this->settings['instructions'] ?? '',
			'col_span'    => 'sm:col-span-6',
			'description' => __( 'Shown when the client picks this method. Plain text or simple HTML; the invoice number is always shown as the reference to quote.', 'easy-invoice' ),
		];
		$fields[ OfflinePayments::optionKey( $id, 'reference' ) ] = [
			'label'       => __( 'Ask for a transaction reference', 'easy-invoice' ),
			'type'        => 'checkbox',
			'default'     => 'cash' === $id ? 'no' : 'yes',
			'col_span'    => 'sm:col-span-6 md:col-span-3',
			'description' => __( 'The reference or confirmation number the client\'s bank gave them.', 'easy-invoice' ),
		];
		$fields[ OfflinePayments::optionKey( $id, 'proof' ) ] = [
			'label'    => __( 'Receipt upload', 'easy-invoice' ),
			'type'     => 'select',
			'default'  => 'cash' === $id ? 'off' : 'optional',
			'col_span' => 'sm:col-span-6 md:col-span-3',
			'options'  => [
				'off'      => __( 'Not offered', 'easy-invoice' ),
				'optional' => __( 'Optional', 'easy-invoice' ),
				'required' => __( 'Required', 'easy-invoice' ),
			],
		];

		return [ 'fields' => $fields ];
	}

	/* ------------------------------------------------------------------ */
	/* Client side                                                          */
	/* ------------------------------------------------------------------ */

	/**
	 * What the panel shows once this method is picked: details, instructions
	 * and the fields the client fills in. Printed on
	 * easy_invoice_payment_gateways_after for this gateway only.
	 *
	 * @param object $invoice          Invoice model.
	 * @param string $selected_gateway Gateway the client chose.
	 */
	public function maybeRenderInstructions( $invoice, $selected_gateway = '' ): void {
		if ( $selected_gateway !== $this->getName() ) {
			return;
		}
		$settings = OfflinePayments::settings( $this->getName() );
		$details  = OfflinePayments::details( $this->getName() );
		$number   = is_object( $invoice ) && method_exists( $invoice, 'getNumber' ) ? (string) $invoice->getNumber() : '';
		$due      = is_object( $invoice ) ? InvoiceBalance::due( $invoice ) : 0.0;
		$formatter = class_exists( '\EasyInvoice\Helpers\InvoiceFormatter' ) && is_object( $invoice ) ? new \EasyInvoice\Helpers\InvoiceFormatter( $invoice ) : null;
		$label    = OfflinePayments::label( $this->getName() );
		?>
		<div class="ei-offline" data-ei-offline="1" data-ei-submit-label="<?php echo esc_attr( __( 'Submit payment details', 'easy-invoice' ) ); ?>">
			<h4><?php echo esc_html( $label ); ?></h4>
			<?php if ( $details ) : ?>
				<table class="bank-details-table ei-offline__details">
					<tbody>
					<?php foreach ( $details as $row_label => $value ) : ?>
						<tr><th scope="row"><?php echo esc_html( $row_label ); ?></th><td><?php echo nl2br( esc_html( $value ) ); ?></td></tr>
					<?php endforeach; ?>
					</tbody>
				</table>
			<?php endif; ?>
			<?php if ( '' !== trim( $settings['instructions'] ) ) : ?>
				<div class="ei-offline__instructions"><?php echo wp_kses_post( wpautop( $settings['instructions'] ) ); ?></div>
			<?php endif; ?>
			<?php if ( '' !== $number ) : ?>
				<p class="payment-reference"><strong><?php esc_html_e( 'Reference to quote:', 'easy-invoice' ); ?></strong> <?php echo esc_html( $number ); ?>
					<?php if ( $formatter && $due > 0 ) : ?>
						· <strong><?php esc_html_e( 'Amount due:', 'easy-invoice' ); ?></strong> <?php echo esc_html( $formatter->format( $due ) ); ?>
					<?php endif; ?>
				</p>
			<?php endif; ?>

			<div class="ei-offline__form">
				<p class="ei-offline__lead"><?php esc_html_e( 'Once you have paid, tell us here so we can match it to your invoice.', 'easy-invoice' ); ?></p>
				<?php if ( $settings['reference'] ) : ?>
					<div class="form-group">
						<label for="ei-payment-reference"><?php esc_html_e( 'Transaction reference', 'easy-invoice' ); ?> <span class="ei-offline__optional"><?php esc_html_e( '(optional)', 'easy-invoice' ); ?></span></label>
						<input type="text" id="ei-payment-reference" name="payment_reference" maxlength="120" autocomplete="off" placeholder="<?php esc_attr_e( 'e.g. the confirmation number from your bank', 'easy-invoice' ); ?>">
					</div>
				<?php endif; ?>
				<?php if ( 'off' !== $settings['proof'] ) : ?>
					<div class="form-group">
						<label for="ei-payment-proof"><?php esc_html_e( 'Receipt or proof of payment', 'easy-invoice' ); ?>
							<?php if ( 'required' === $settings['proof'] ) : ?><span class="ei-offline__required">*</span><?php else : ?><span class="ei-offline__optional"><?php esc_html_e( '(optional)', 'easy-invoice' ); ?></span><?php endif; ?>
						</label>
						<input type="file" id="ei-payment-proof" name="payment_proof" accept="image/jpeg,image/png,image/gif,image/webp,application/pdf" <?php echo 'required' === $settings['proof'] ? 'data-ei-required="1"' : ''; ?>>
						<p class="reference-note"><?php esc_html_e( 'JPG, PNG, GIF, WebP or PDF, up to 5 MB. Only our staff can open it.', 'easy-invoice' ); ?></p>
					</div>
				<?php endif; ?>
				<div class="form-group">
					<label for="ei-payment-notes"><?php esc_html_e( 'Note to us', 'easy-invoice' ); ?> <span class="ei-offline__optional"><?php esc_html_e( '(optional)', 'easy-invoice' ); ?></span></label>
					<textarea id="ei-payment-notes" name="payment_notes" rows="2" maxlength="1000"></textarea>
				</div>
			</div>
		</div>
		<?php
	}

	/**
	 * Cut a string to a number of characters (mbstring when available).
	 */
	private static function clip( string $value, int $max ): string {
		return function_exists( 'mb_substr' ) ? mb_substr( $value, 0, $max ) : substr( $value, 0, $max );
	}

	/**
	 * The client pressed "Submit payment details": record what they told us
	 * as a pending payment and let staff know.
	 *
	 * @param float $amount Amount the panel is submitting (the balance, or an instalment when allowed).
	 * @param array $data   Request fields.
	 */
	public function processPayment( float $amount, array $data = [] ): array {
		$invoice_id = isset( $data['invoice_id'] ) ? absint( $data['invoice_id'] ) : 0;
		$post       = $invoice_id ? get_post( $invoice_id ) : null;
		if ( ! $post || \EasyInvoice\Constants\PostTypes::EASY_INVOICE_POST_TYPE !== $post->post_type ) {
			return [
				'success' => false,
				'message' => __( 'Invalid invoice.', 'easy-invoice' ),
			];
		}
		$invoice  = new \EasyInvoice\Models\Invoice( $post );
		$settings = OfflinePayments::settings( $this->getName() );

		// A client can tell us about a payment more than once (a second
		// instalment, a corrected reference) but not flood the invoice.
		$open = OfflinePayments::pendingForInvoice( $invoice_id );
		/**
		 * Filter how many offline submissions may await confirmation on one invoice.
		 *
		 * @param int $max Default 3.
		 */
		if ( count( $open ) >= (int) apply_filters( 'easy_invoice_offline_pending_limit', 3 ) ) {
			return [
				'success' => false,
				'message' => __( 'You have already told us about a payment on this invoice and we are checking it. Please contact us if something has changed.', 'easy-invoice' ),
			];
		}

		$reference = isset( $data['payment_reference'] ) ? sanitize_text_field( wp_unslash( (string) $data['payment_reference'] ) ) : '';
		$notes     = isset( $data['payment_notes'] ) ? sanitize_textarea_field( wp_unslash( (string) $data['payment_notes'] ) ) : '';
		$reference = self::clip( $reference, 120 );
		$notes     = self::clip( $notes, 1000 );

		$proof = null;
		// The upload is validated in storeProof() (is_uploaded_file, size, real
		// file type) — nothing from $_FILES is used before that.
		// phpcs:disable WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput
		$upload = isset( $_FILES['payment_proof'] ) && is_array( $_FILES['payment_proof'] ) ? $_FILES['payment_proof'] : [];
		// phpcs:enable
		if ( 'off' !== $settings['proof'] && ! empty( $upload['name'] ) ) {
			$proof = OfflinePayments::storeProof( $upload );
			if ( is_wp_error( $proof ) ) {
				return [
					'success' => false,
					'message' => $proof->get_error_message(),
				];
			}
		}
		if ( 'required' === $settings['proof'] && null === $proof ) {
			return [
				'success' => false,
				'message' => __( 'Please attach your receipt or proof of payment.', 'easy-invoice' ),
			];
		}

		$due = InvoiceBalance::due( $invoice );
		if ( $amount <= 0 || $amount > $due + 0.005 ) {
			$amount = $due;
		}
		if ( $amount <= 0 ) {
			return [
				'success' => false,
				'message' => __( 'Nothing is owed on this invoice.', 'easy-invoice' ),
			];
		}

		$currency_code = $invoice->getCurrencyCode();
		if ( '' === (string) $currency_code || 'global' === $currency_code ) {
			$currency_code = get_option( 'easy_invoice_currency_code', 'USD' );
		}
		$currency_symbol = \EasyInvoice\Helpers\CurrencyHelper::getCurrencySymbol( $currency_code );
		$method          = $this->getName();
		$transaction_id  = strtoupper( $method ) . '-' . $invoice_id . '-' . time();

		$meta = [
			'_invoice_id'       => $invoice_id,
			'_amount'           => round( $amount, 2 ),
			'_payment_method'   => $method,
			'_status'           => 'pending',
			'_transaction_id'   => $transaction_id,
			'_payment_date'     => current_time( 'mysql' ),
			'_notes'            => $notes,
			'_payment_type'     => 'offline',
			'_currency'         => $currency_code,
			'_currency_symbol'  => $currency_symbol,
			'_submitted_by'     => get_current_user_id(),
			'_gateway_response' => wp_json_encode(
				[
					'method'    => $method,
					'submitted' => current_time( 'mysql' ),
					'reference' => $reference,
					'ip'        => isset( $_SERVER['REMOTE_ADDR'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REMOTE_ADDR'] ) ) : '',
				]
			),
		];
		if ( '' !== $reference ) {
			$meta[ OfflinePayments::META_REFERENCE ] = $reference;
		}
		if ( $proof ) {
			$meta[ OfflinePayments::META_PROOF_FILE ] = $proof['file'];
			$meta[ OfflinePayments::META_PROOF_NAME ] = $proof['name'];
		}

		$payment_id = wp_insert_post(
			[
				/* translators: 1: payment method, 2: invoice number. */
				'post_title'  => sprintf( __( '%1$s for invoice %2$s', 'easy-invoice' ), OfflinePayments::label( $method ), $invoice->getNumber() ),
				'post_type'   => 'easy_invoice_payment',
				'post_status' => 'publish',
				'post_author' => get_current_user_id() ?: (int) $post->post_author,
				'meta_input'  => $meta,
			],
			true
		);
		if ( is_wp_error( $payment_id ) || ! $payment_id ) {
			if ( $proof ) {
				$abs = trailingslashit( wp_upload_dir()['basedir'] ) . $proof['file'];
				if ( file_exists( $abs ) ) {
					wp_delete_file( $abs );
				}
			}
			return [
				'success' => false,
				'message' => __( 'Your payment details could not be saved. Please try again or contact us.', 'easy-invoice' ),
			];
		}

		// The invoice keeps its status (a pending payment is not money yet);
		// the panel and the page read this to say a submission is waiting.
		update_post_meta( $invoice_id, '_payment_status', 'pending' );
		update_post_meta( $invoice_id, '_easy_invoice_payment_method', $method );

		/**
		 * Fires when a client says they have paid (or will pay) by an
		 * offline method. The payment record is pending until staff confirm it.
		 *
		 * @param int    $invoice_id Invoice.
		 * @param string $method     Method id (bank_transfer, cheque, cash, or a custom id).
		 * @param int    $payment_id Pending payment record.
		 */
		do_action( 'easy_invoice_manual_payment_submitted', $invoice_id, $method, (int) $payment_id );

		$formatter = new \EasyInvoice\Helpers\InvoiceFormatter( $invoice );
		return [
			'success'        => true,
			'offline'        => true,
			'payment_id'     => (int) $payment_id,
			'payment_status' => 'pending',
			/* translators: 1: amount, 2: payment method. */
			'message'        => sprintf( __( 'Thank you — we have noted your %1$s payment by %2$s. It will show as paid once we have confirmed it.', 'easy-invoice' ), $formatter->format( $amount ), OfflinePayments::label( $method ) ),
		];
	}
}

```
