# woocommerce-pos/1.10.0/includes/Services/Receipt_Snapshot_Store.php

WCPOS – Point of Sale (POS) plugin for WooCommerce, version 1.10.0. 271 lines.

- Page: https://pluginprobe.com/plugins/woocommerce-pos/1.10.0/code/includes/Services/Receipt_Snapshot_Store.php
- Raw: https://pluginprobe.com/plugins/woocommerce-pos/1.10.0/raw/includes/Services/Receipt_Snapshot_Store.php
- Modified: 2026-05-15T18:12:48+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/woocommerce-pos/1.10.0/code/includes/Services/Receipt_Snapshot_Store.php#L10-L20`.

```php
<?php
/**
 * Immutable receipt snapshot store.
 *
 * @package WCPOS\WooCommercePOS\Services
 */

namespace WCPOS\WooCommercePOS\Services;

use RuntimeException;
use WCPOS\WooCommercePOS\Logger;

/**
 * Receipt_Snapshot_Store class.
 */
class Receipt_Snapshot_Store {
	/**
	 * Snapshot payload meta key.
	 */
	const META_KEY_PAYLOAD = '_wcpos_receipt_snapshot';

	/**
	 * Snapshot checksum meta key.
	 */
	const META_KEY_CHECKSUM = '_wcpos_receipt_snapshot_checksum';

	/**
	 * Snapshot sequence meta key.
	 */
	const META_KEY_SEQUENCE = '_wcpos_receipt_sequence';

	/**
	 * Snapshot created timestamp meta key.
	 */
	const META_KEY_CREATED_AT = '_wcpos_receipt_snapshot_created_at_gmt';

	/**
	 * Snapshot option key for sequence counter.
	 */
	const OPTION_SEQUENCE = 'wcpos_receipt_sequence_counter';

	/**
	 * Singleton instance.
	 *
	 * @var null|self
	 */
	private static $instance = null;

	/**
	 * Data builder.
	 *
	 * @var Receipt_Data_Builder
	 */
	private $builder;

	/**
	 * Fiscal service.
	 *
	 * @var Fiscal_Receipt_Service
	 */
	private $fiscal_service;

	/**
	 * Constructor.
	 */
	private function __construct() {
		$this->builder        = new Receipt_Data_Builder();
		$this->fiscal_service = new Fiscal_Receipt_Service();
		add_action( 'woocommerce_payment_complete', array( $this, 'handle_payment_complete' ) );
	}

	/**
	 * Get singleton instance.
	 *
	 * @return self
	 */
	public static function instance(): self {
		if ( null === self::$instance ) {
			self::$instance = new self();
		}

		return self::$instance;
	}

	/**
	 * Handle payment complete event and persist snapshot if needed.
	 *
	 * @param int $order_id Order ID.
	 */
	public function handle_payment_complete( int $order_id ): void {
		$order = wc_get_order( $order_id );
		if ( ! $order ) {
			return;
		}

		if ( $this->has_snapshot( $order_id ) ) {
			return;
		}

		$snapshot = $this->builder->build( $order, 'fiscal' );
		try {
			$this->persist_snapshot( $order_id, $snapshot );
		} catch ( RuntimeException $e ) {
			Logger::log( 'Unable to persist receipt snapshot: ' . $e->getMessage() );
		}
	}

	/**
	 * Check if order has a stored snapshot.
	 *
	 * @param int $order_id Order ID.
	 *
	 * @return bool
	 */
	public function has_snapshot( int $order_id ): bool {
		$order = wc_get_order( $order_id );
		if ( ! $order ) {
			return false;
		}
		$payload = $order->get_meta( self::META_KEY_PAYLOAD, true );

		return \is_string( $payload ) && '' !== $payload;
	}

	/**
	 * Get stored snapshot.
	 *
	 * @param int $order_id Order ID.
	 *
	 * @return null|array
	 */
	public function get_snapshot( int $order_id ): ?array {
		$order = wc_get_order( $order_id );
		if ( ! $order ) {
			return null;
		}
		$payload = $order->get_meta( self::META_KEY_PAYLOAD, true );
		if ( ! \is_string( $payload ) || '' === $payload ) {
			return null;
		}

		$decoded = json_decode( $payload, true );
		if ( ! \is_array( $decoded ) ) {
			return null;
		}

		return $decoded;
	}

	/**
	 * Persist immutable snapshot fields.
	 *
	 * @param int   $order_id Order ID.
	 * @param array $snapshot Snapshot payload.
	 *
	 * @throws RuntimeException When the order snapshot lock cannot be acquired.
	 * @throws RuntimeException When JSON encoding fails.
	 * @throws RuntimeException When the order does not exist.
	 */
	public function persist_snapshot( int $order_id, array $snapshot ): void {
		global $wpdb;

		$lock_name = 'wcpos_receipt_snapshot_lock_' . $order_id;
		$acquired  = (int) $wpdb->get_var(
			$wpdb->prepare( 'SELECT GET_LOCK( %s, %d )', $lock_name, 5 )
		);
		if ( 1 !== $acquired ) {
			throw new RuntimeException( 'Unable to acquire receipt snapshot lock' );
		}

		try {
			if ( $this->has_snapshot( $order_id ) ) {
				return;
			}

			$sequence = $this->next_sequence();
			$created  = current_time( 'mysql', true );

			$snapshot['fiscal']['sequence']       = $sequence;
			$snapshot['fiscal']['receipt_number'] = (string) $sequence;
			$snapshot['fiscal']['immutable_id']   = $order_id . ':' . $sequence;

			$snapshot = $this->fiscal_service->enrich_snapshot( $snapshot, $order_id );

			$json = wp_json_encode( $snapshot );
			if ( ! \is_string( $json ) ) {
				Logger::log( 'Failed to encode receipt snapshot to JSON: ' . json_last_error_msg() );
				throw new RuntimeException( 'Failed to encode receipt snapshot to JSON' );
			}

			$checksum = hash( 'sha256', $json );
			$order    = wc_get_order( $order_id );
			if ( ! $order ) {
				throw new RuntimeException( 'Order not found when persisting snapshot' );
			}
			$order->update_meta_data( self::META_KEY_PAYLOAD, $json );
			$order->update_meta_data( self::META_KEY_CHECKSUM, $checksum );
			$order->update_meta_data( self::META_KEY_SEQUENCE, (string) $sequence );
			$order->update_meta_data( self::META_KEY_CREATED_AT, $created );
			$order->save();

			/* translators: %d: receipt sequence number. */
			$order->add_order_note( \sprintf( __( 'POS fiscal receipt snapshot created (receipt #%d).', 'woocommerce-pos' ), $sequence ) );

			$this->fiscal_service->set_submission_status( $order_id, 'pending' );
		} finally {
			$wpdb->get_var(
				$wpdb->prepare( 'SELECT RELEASE_LOCK( %s )', $lock_name )
			);
		}
	}

	/**
	 * Get the default mode from settings.
	 *
	 * @return string
	 */
	public function get_default_mode(): string {
		$checkout_settings = woocommerce_pos_get_settings( 'checkout' );
		$mode              = \is_array( $checkout_settings ) ? ( $checkout_settings['receipt_default_mode'] ?? 'fiscal' ) : 'fiscal';

		return in_array( $mode, array( 'fiscal', 'live' ), true ) ? $mode : 'fiscal';
	}

	/**
	 * Resolve effective receipt mode.
	 *
	 * @param null|string $mode Requested mode.
	 *
	 * @return string
	 */
	public function resolve_mode( ?string $mode = null ): string {
		if ( in_array( $mode, array( 'fiscal', 'live' ), true ) ) {
			return $mode;
		}

		return $this->get_default_mode();
	}

	/**
	 * Get next sequence number.
	 *
	 * @return int
	 *
	 * @throws RuntimeException When the sequence lock cannot be acquired.
	 */
	private function next_sequence(): int {
		global $wpdb;

		$lock_name = 'wcpos_receipt_sequence_lock';
		$acquired  = (int) $wpdb->get_var(
			$wpdb->prepare( 'SELECT GET_LOCK( %s, %d )', $lock_name, 5 )
		);
		if ( 1 !== $acquired ) {
			throw new RuntimeException( 'Unable to acquire receipt sequence lock' );
		}

		try {
			$sequence = (int) get_option( self::OPTION_SEQUENCE, 0 );
			$sequence++;
			update_option( self::OPTION_SEQUENCE, $sequence, false );
		} finally {
			$wpdb->get_var(
				$wpdb->prepare( 'SELECT RELEASE_LOCK( %s )', $lock_name )
			);
		}

		return $sequence;
	}
}

```
