Receipt or invoice at checkout (Απόδειξη / Τιμολόγιο)
Customers choose a receipt or an invoice. For an invoice they fill in company name, VAT number (ΑΦΜ, checked), tax office (ΔΟΥ) and business activity. The details are saved with the order and shown in the admin, the order emails and the order page.
<?php
// Labels are in Greek; change them if you like.
$fields = array(
'sf_invoice_company' => 'Επωνυμία εταιρείας',
'sf_invoice_vat' => 'ΑΦΜ',
'sf_invoice_tax_office' => 'ΔΟΥ',
'sf_invoice_activity' => 'Δραστηριότητα',
);
// A Greek VAT number (ΑΦΜ): 9 digits with a check digit.
$valid_vat = static function ( string $vat ): bool {
if ( ! preg_match( '/^\d{9}$/', $vat ) || '000000000' === $vat ) {
return false;
}
$sum = 0;
for ( $i = 0; $i < 8; $i++ ) {
$sum += (int) $vat[ $i ] * ( 2 ** ( 8 - $i ) );
}
return ( $sum % 11 ) % 10 === (int) $vat[8];
};
add_filter(
'woocommerce_checkout_fields',
static function ( $checkout ) use ( $fields ) {
$checkout['billing']['sf_document'] = array(
'type' => 'radio',
'label' => 'Παραστατικό',
'options' => array(
'receipt' => 'Απόδειξη',
'invoice' => 'Τιμολόγιο',
),
'default' => 'receipt',
'required' => true,
'class' => array( 'form-row-wide', 'sf-document' ),
'priority' => 5,
);
$priority = 6;
foreach ( $fields as $key => $label ) {
$checkout['billing'][ $key ] = array(
'type' => 'text',
'label' => $label . ' <abbr class="required" title="υποχρεωτικό">*</abbr>',
'required' => false, // Required for invoices only: checked below.
'class' => array( 'form-row-wide', 'sf-invoice-field' ),
'priority' => $priority++,
);
}
return $checkout;
}
);
add_action(
'woocommerce_after_checkout_validation',
static function ( $data, $errors ) use ( $fields, $valid_vat ) {
if ( 'invoice' !== ( $data['sf_document'] ?? '' ) ) {
return;
}
foreach ( $fields as $key => $label ) {
if ( '' === trim( (string) ( $data[ $key ] ?? '' ) ) ) {
$errors->add( 'validation', sprintf( 'Συμπληρώστε το πεδίο «%s» για το τιμολόγιο.', $label ) );
}
}
$vat = preg_replace( '/\D/', '', (string) ( $data['sf_invoice_vat'] ?? '' ) );
if ( '' !== $vat && ! $valid_vat( $vat ) ) {
$errors->add( 'validation', 'Ο ΑΦΜ δεν είναι έγκυρος.' );
}
},
10,
2
);
add_action(
'woocommerce_checkout_create_order',
static function ( $order, $data ) use ( $fields ) {
$document = 'invoice' === ( $data['sf_document'] ?? '' ) ? 'invoice' : 'receipt';
$order->update_meta_data( '_sf_document', $document );
if ( 'invoice' === $document ) {
foreach ( array_keys( $fields ) as $key ) {
$value = (string) ( $data[ $key ] ?? '' );
$order->update_meta_data( '_' . $key, 'sf_invoice_vat' === $key ? preg_replace( '/\D/', '', $value ) : sanitize_text_field( $value ) );
}
}
},
10,
2
);
// The details, as HTML or plain text.
$details = static function ( $order, bool $plain = false ) use ( $fields ): string {
if ( ! $order instanceof WC_Order || ! $order->get_meta( '_sf_document' ) ) {
return '';
}
$lines = array( 'Παραστατικό' => 'invoice' === $order->get_meta( '_sf_document' ) ? 'Τιμολόγιο' : 'Απόδειξη' );
if ( 'invoice' === $order->get_meta( '_sf_document' ) ) {
foreach ( $fields as $key => $label ) {
$lines[ $label ] = (string) $order->get_meta( '_' . $key );
}
}
$out = '';
foreach ( $lines as $label => $value ) {
$out .= $plain ? $label . ': ' . $value . "\n" : '<strong>' . esc_html( $label ) . ':</strong> ' . esc_html( $value ) . '<br>';
}
return $plain ? "\n" . $out : '<p class="sf-invoice-details">' . $out . '</p>';
};
add_action(
'woocommerce_admin_order_data_after_billing_address',
static function ( $order ) use ( $details ) {
echo $details( $order ); // phpcs:ignore WordPress.Security.EscapeOutput -- Escaped above.
}
);
// The order page for customers: classic templates show it with the
// customer's details, block themes in the thank-you page's extra information.
$show_once = static function ( $order ) use ( $details ) {
static $shown = array();
$order = $order instanceof WC_Order ? $order : wc_get_order( $order );
if ( $order && empty( $shown[ $order->get_id() ] ) ) {
$shown[ $order->get_id() ] = true;
echo $details( $order ); // phpcs:ignore WordPress.Security.EscapeOutput -- Escaped above.
}
};
add_action( 'woocommerce_order_details_after_customer_details', $show_once );
add_action( 'woocommerce_thankyou', $show_once, 20 );
add_action(
'woocommerce_email_after_order_table',
static function ( $order, $sent_to_admin = false, $plain_text = false ) use ( $details ) {
echo $plain_text ? esc_html( $details( $order, true ) ) : $details( $order ); // phpcs:ignore WordPress.Security.EscapeOutput -- Escaped above.
},
10,
3
);
// Show the invoice fields only when "Τιμολόγιο" is picked.
add_action(
'wp_footer',
static function () {
if ( ! function_exists( 'is_checkout' ) || ! is_checkout() || is_order_received_page() ) {
return;
}
?>
<style>.sf-invoice-field .optional{display:none}.sf-document .woocommerce-input-wrapper label{display:inline;margin:0 1em 0 .3em}</style>
<script>
jQuery( function ( $ ) {
var toggle = function () {
$( '.sf-invoice-field' ).toggle( $( 'input[name="sf_document"]:checked' ).val() === 'invoice' );
};
$( document.body ).on( 'change', 'input[name="sf_document"]', toggle );
toggle();
} );
</script>
<?php
}
);
Good to know
For the classic checkout (the [woocommerce_checkout] shortcode or the Classic Checkout block). The details are saved as order fields (_sf_document, _sf_invoice_vat…), which invoicing plugins and exports can read.
In these packs
- Greek e-shop: For shops in Greece: receipt or invoice with a checked VAT number (ΑΦΜ), Greek phone and postcode checks, a cash on delivery fee, and greeklish addresses.