NAME

Lib::Pepper::Simple - High-level payment terminal interface

SYNOPSIS

use Lib::Pepper::Simple;
use Lib::Pepper::Constants qw(:all);

# Initialize connection to terminal
my $pepper = Lib::Pepper::Simple->new(
    terminal_type    => PEP_TERMINAL_TYPE_GENERIC_ZVT,
    terminal_address => '192.168.1.163:20008',
    license_file     => '/path/to/license.xml',
    config_file      => '/path/to/config.xml',
);

# Check terminal status
my $status = $pepper->checkStatus();
if($status->{ready_for_transactions}) {
    print "Terminal ready!"\n";
}

# Process a payment (100.50 EUR)
my $payment = $pepper->doPayment(10_050);
if($payment->{authorized}) {
    print "Payment authorized!"\n";
    print "Trace: $payment->{trace_number}"\n";
    print "Auth: $payment->{authorization_code}"\n";

    # Store trace number for potential cancellation
    my $trace = $payment->{trace_number};
    my $amount = $payment->{amount_charged};
}

# Cancel a payment (automatically handles VOID or REFUND)
my $cancel = $pepper->cancelPayment($trace, $amount);
if($cancel->{success}) {
    print "Cancellation successful via $cancel->{method_used}"\n";
}

# End of day settlement
my $settlement = $pepper->endOfDay();
if($settlement->{success}) {
    print "Settled $settlement->{transaction_count} transactions"\n";
    print ""Total: " . sprintf("%.2f", $settlement->{total_amount} / 100)\n";
}

# MULTI-TERMINAL SUPPORT: Multiple terminals in same process
my $terminal1 = Lib::Pepper::Simple->new(
    terminal_type    => PEP_TERMINAL_TYPE_GENERIC_ZVT,
    terminal_address => '192.168.1.163:20008',
    license_file     => $license,  # Must be SAME as terminal2
    config_file      => $config,   # Must be SAME as terminal2
);

my $terminal2 = Lib::Pepper::Simple->new(
    terminal_type    => PEP_TERMINAL_TYPE_GENERIC_ZVT,
    terminal_address => '192.168.1.164:20008',
    license_file     => $license,  # Must be SAME as terminal1
    config_file      => $config,   # Must be SAME as terminal1
);

# Check process-wide status
my $libStatus = Lib::Pepper::Simple->library_status();
print "Active terminals: $libStatus->{instance_count}"\n";

# Process independent payments
my $payment1 = $terminal1->doPayment(1000);
my $payment2 = $terminal2->doPayment(2000);

DESCRIPTION

Lib::Pepper::Simple provides a high-level, easy-to-use interface for payment terminal operations. It wraps the complexity of the Pepper library into 5 simple methods.

Key Features

Automatic Initialization

Single constructor call handles library initialization, instance creation, configuration, recovery operations, and connection opening.

Smart Cancellation

cancelPayment() automatically tries VOID first (same-day instant cancellation), then falls back to REFUND (settled transaction reversal) if needed.

Clear Return Values

All methods return structured hashrefs with clear field names, not raw library objects.

Automatic Recovery

Recovery operations are detected and performed automatically during initialization.

Production Ready

Comprehensive error handling, state validation, and proper cleanup.

METHODS

new(%params)

Creates and fully initializes a payment terminal connection.

Required Parameters:

terminal_type

Terminal type constant, e.g. PEP_TERMINAL_TYPE_GENERIC_ZVT (118)

terminal_address

Terminal IP and port, e.g. '192.168.1.163:20008'

license_xml OR license_file

Either the XML content of the license (license_xml) or the path to the license file (license_file). Use license_xml when storing licenses in a database.

config_xml OR config_file

Either the XML content of the configuration (config_xml) or the path to the config file (config_file). Use config_xml when storing configurations in a database.

Optional Parameters:

library_path

Path to libpepcore.so (default: empty string for auto-detect)

pos_number

POS identification string (default: '0001')

merchant_password

Merchant password (default: '000000')

language

Language constant (default: PEP_LANGUAGE_ENGLISH)

ticket_width

Receipt width in characters (default: 40)

config_byte

Terminal config byte. If not specified, automatically calculated based on ticket_printing_mode:

  • Mode 0 (POS prints): PEP_CONFIG_BYTE_DISABLE_PRINTER (0x06 - disables terminal printer)

  • Mode 1 (EFT prints): PEP_CONFIG_BYTE_NORMAL (0x00 - normal operation)

  • Other modes: PEP_CONFIG_BYTE_NORMAL (0x00 - normal operation)

You can explicitly provide a config_byte value to override this automatic behavior. Available constants: PEP_CONFIG_BYTE_NORMAL, PEP_CONFIG_BYTE_DISABLE_PRINTER.

ticket_printing_mode

Controls where transaction receipts are printed (default: 0 - POS/cash register handles printing).

Values:

  • 0 - POS/cash register prints (PEP_TICKET_PRINTING_MODE_POS) - DEFAULT

  • 1 - Terminal prints (PEP_TICKET_PRINTING_MODE_EFT)

  • 2 - Client receipt on terminal only (PEP_TICKET_PRINTING_MODE_CLIENT_ONLY_EFT)

  • 3 - No printing at all (PEP_TICKET_PRINTING_MODE_NONE)

  • 4 - Both terminal and POS (PEP_TICKET_PRINTING_MODE_ECR_AND_TERMINAL)

Default is 0 (POS prints) which disables the terminal's built-in printer and indicates that the POS/cash register will handle receipt printing. This saves paper waste when your cash register already prints transaction details on invoices. Use 1 (EFT) to enable terminal printing if you want the terminal to print receipts.

Note: The module automatically sets the config_byte parameter to PEP_CONFIG_BYTE_DISABLE_PRINTER (0x06) when mode 0 is used, which helps disable the terminal printer on many devices. This is handled internally and you don't need to set config_byte manually.

callback

CODE reference for terminal callbacks (default: silent callback)

userdata

Hashref of custom data passed to callbacks (default: {})

reph

Optional reporting handler object for audit logging. The object must implement a debuglog(@parts) method that accepts one or more strings and logs them as a single line.

If not provided, debug output (when enabled) falls back to STDERR.

Example:

package MyLogger;
sub new { bless {}, shift }
sub debuglog {
    my ($self, @parts) = @_;
    print "LOG: ", join('', @parts), "\n";
}

my $logger = MyLogger->new();
my $pepper = Lib::Pepper::Simple->new(
    ...
    reph => $logger,
);
operator_id

Operator identification string (default: 'OPERATOR01')

Returns: Blessed object reference

Dies on error: Throws exception with detailed error message

Example using file paths:

my $pepper = Lib::Pepper::Simple->new(
    terminal_type    => PEP_TERMINAL_TYPE_GENERIC_ZVT,
    terminal_address => '192.168.1.163:20008',
    license_file     => '/etc/pepper/license.xml',
    config_file      => '/etc/pepper/config.xml',
);

Example using XML content (from database):

# Load license and config from database
my $licenseXml = $dbh->selectrow_array(
    "SELECT license_xml FROM terminal_configs WHERE id = ?",
    undef, $terminal_id
);
my $configXml = $dbh->selectrow_array(
    "SELECT config_xml FROM terminal_configs WHERE id = ?",
    undef, $terminal_id
);

my $pepper = Lib::Pepper::Simple->new(
    terminal_type    => PEP_TERMINAL_TYPE_GENERIC_ZVT,
    terminal_address => '192.168.1.163:20008',
    license_xml      => $licenseXml,
    config_xml       => $configXml,
);

checkStatus($self)

Returns comprehensive terminal status information.

Returns hashref:

library_initialized

Boolean - Pepper library initialized

instance_configured

Boolean - Instance configured

connection_open

Boolean - Connection to terminal open

terminal_type

Terminal type constant

terminal_address

Terminal IP:port string

instance_id

Integer - This terminal's instance ID

ready_for_transactions

Boolean - All checks passed, ready for payments

last_error

Last error message or undef

process_instance_count

Integer - Total number of active terminals in this process (multi-terminal support)

process_library_initialized

Boolean - Library initialized at process level (multi-terminal support)

Example:

my $status = $pepper->checkStatus();
if(!$status->{ready_for_transactions}) {
    die "Terminal not ready: $status->{last_error}";
}

library_status($class)

Class method to check process-wide library status. Useful for monitoring multi-terminal systems and debugging initialization issues.

Returns hashref:

initialized

Boolean - Is the Pepper library initialized in this process?

instance_count

Integer - Number of active Lib::Pepper::Simple instances (terminals)

library_path

String - Library path used for initialization (empty string for auto-detect)

instance_ids

Hashref - Next available instance_id per terminal_type

{
    118 => 3,  # Next Generic ZVT terminal would get instance_id 3
    120 => 2,  # Next Hobex ZVT terminal would get instance_id 2
}

Example:

my $status = Lib::Pepper::Simple->library_status();

print "Library initialized: $status->{initialized}"\n";
print "Active terminals: $status->{instance_count}"\n";

if($status->{instance_count} > 0) {
    print "Library is in use, cannot safely reinitialize"\n";
}

Multi-Terminal Monitoring:

# Before creating terminals
my $before = Lib::Pepper::Simple->library_status();
die "Library already initialized!" if $before->{initialized};

# Create terminals
my $t1 = Lib::Pepper::Simple->new(...);
my $t2 = Lib::Pepper::Simple->new(...);

# Check current state
my $current = Lib::Pepper::Simple->library_status();
print "Active terminals: $current->{instance_count}"\n";  # 2

# After cleanup
undef $t1;
undef $t2;
my $after = Lib::Pepper::Simple->library_status();
print ""Library finalized: " . (!$after->{initialized})\n";  # 1

doPayment($self, $amount, %options)

Performs a payment transaction.

Parameters:

$amount (required)

Amount in smallest currency unit (cents for EUR/USD)

transaction_type (optional)

Transaction type constant (default: PEP_TRANSACTION_TYPE_GOODS_PAYMENT)

currency (optional)

Currency constant (usually from config)

options (optional)

Hashref of additional transaction options

Returns hashref:

success

Boolean - API call succeeded

authorized

Boolean - Payment was authorized (check this for actual payment success!)

amount_charged

Actual amount charged in cents (0 if not authorized)

transaction_result

Raw iTransactionResultValue (0 = authorized, -1 = declined/aborted)

transaction_text

Human-readable status text

trace_number

Trace number (CRITICAL: Store this for cancellations!)

authorization_code

Authorization code from payment processor

reference_number

Transaction reference number

terminal_id

Terminal identification

card_type

Card brand (VISA, MASTERCARD, etc.)

card_number

Masked card number

transaction_date

Transaction date (YYYY-MM-DD)

transaction_time

Transaction time (HH:MM:SS)

raw_output

Complete output hashref from library

CRITICAL - Payment Authorization Check:

The success field indicates the API call completed successfully. The authorized field indicates the payment was actually authorized.

An aborted or declined payment will have success => 1 but authorized => 0!

Always check: if($payment->{authorized}) { ... }

Example:

my $payment = $pepper->doPayment(10_050);  # 100.50 EUR

if($payment->{authorized}) {
    # Payment AUTHORIZED - money will be charged
    print "Payment successful!"\n";
    print "Trace: $payment->{trace_number}"\n";

    # Store in database for potential cancellation
    store_payment({
        invoice_id => 'INV-123',
        trace      => $payment->{trace_number},
        amount     => $payment->{amount_charged},
        auth_code  => $payment->{authorization_code},
    });
} else {
    # Payment DECLINED or ABORTED - no money charged
    print "Payment failed: $payment->{transaction_text}"\n";
}

cancelPayment($self, $trace_number, $amount, %options)

Refunds a previous payment using referenced void (card-not-present).

This method uses Transaction Type 12 (VoidGoodsPayment) with sTransactionReferenceNumberString to perform a referenced reversal that does NOT require the customer's card to be present.

IMPORTANT: Despite being called "VOID", this works AFTER settlement when using the reference number. This is the correct method for card-not-present refunds in ZVT protocol.

Parameters:

$trace_number (required)

Trace number from original payment (sTraceNumberString)

$amount (required)

Amount in cents (must match original transaction amount)

reference_number (required)

Reference number from original payment (sTransactionReferenceNumberString). This is what makes the refund work without requiring card swipe.

CRITICAL: You MUST store this value when processing the original payment!

Returns hashref:

success

Boolean - Refund succeeded

trace_number

New trace number for refund transaction

amount_refunded

Amount refunded to customer in cents

transaction_text

Status text from terminal

raw_output

Complete output hashref from transaction

How It Works:

Performs a referenced void (Transaction Type 12 - VoidGoodsPayment) using the original transaction's reference number (sTransactionReferenceNumberString). This tells the terminal to reverse a specific previous transaction without requiring the customer to swipe their card again.

Key Discovery: Although this uses "VoidGoodsPayment", it works BOTH before and after settlement when you provide the reference number. The ZVT protocol documentation does not make this clear, but testing confirms this is the correct method for card-not-present refunds.

The refunded amount will appear in the customer's account in 3-5 business days.

Important Notes:

  • Both trace_number AND reference_number from the original payment must be stored in your database

  • The refund amount must match the original transaction amount exactly

  • Customer does NOT need to be present or swipe card

  • Works before or after end-of-day settlement

  • Refund appears in customer account in 3-5 business days

Examples:

# Store payment details when processing original transaction
my $payment = $pepper->doPayment($amount);
if($payment->{authorized}) {
    save_to_database({
        order_id         => $order_id,
        trace_number     => $payment->{trace_number},
        reference_number => $payment->{reference_number},  # CRITICAL!
        amount           => $payment->{amount_charged},
    });
}

# Later: Refund the transaction (no card needed)
my $stored = get_from_database($order_id);
my $refund = $pepper->cancelPayment(
    $stored->{trace_number},
    $stored->{amount},
    reference_number => $stored->{reference_number}
);

if($refund->{success}) {
    print "Refund successful!"\n";
    print "Refund trace: $refund->{trace_number}"\n";
    print "Customer will receive refund in 3-5 business days"\n";

    update_database($order_id, status => 'refunded');
} else {
    print "Refund failed: $refund->{transaction_text}"\n";
}

endOfDay($self, %options)

Performs end-of-day settlement (batch close).

Parameters:

options (optional)

Hashref of settlement options (terminal-specific)

Returns hashref:

success

Boolean - Settlement succeeded

function_result

Raw function result code

function_text

Status text

transaction_count

Number of transactions settled

total_amount

Total amount in cents

settlement_date

Settlement date (YYYY-MM-DD)

settlement_time

Settlement time (HH:MM:SS)

raw_output

Complete output hashref

CRITICAL: Settlement must be run daily to receive payment!

What Settlement Does:

  1. Finalizes all transactions

  2. Triggers money transfer to merchant account

  3. Generates settlement reports

  4. Clears terminal transaction buffer

  5. After settlement, VOID operations become REFUND operations

Example:

my $settlement = $pepper->endOfDay();

if($settlement->{success}) {
    print "Settlement successful!"\n";
    print "Transactions: $settlement->{transaction_count}"\n";
    print "Total: " . sprintf("%.2f EUR",
        $settlement->{total_amount} / 100) . "\n";
} else {
    print "Settlement failed: $settlement->{function_text}"\n";
}

COMPLETE WORKFLOW EXAMPLE

use Lib::Pepper::Simple;
use Lib::Pepper::Constants qw(:all);

# Morning - Initialize
my $pepper = Lib::Pepper::Simple->new(
    terminal_type    => PEP_TERMINAL_TYPE_GENERIC_ZVT,
    terminal_address => '192.168.1.163:20008',
    license_file     => '/etc/pepper/license.xml',
    config_file      => '/etc/pepper/config.xml',
);

# Check status
my $status = $pepper->checkStatus();
die "Not ready" unless $status->{ready_for_transactions};

# During day - Process payment
my $payment = $pepper->doPayment(10_000);  # 100.00 EUR

if($payment->{authorized}) {
    # Store trace number in database
    my $trace = $payment->{trace_number};
    my $amount = $payment->{amount_charged};

    # If customer cancels same day
    my $cancel = $pepper->cancelPayment($trace, $amount);
    if($cancel->{success}) {
        print "Canceled via $cancel->{method_used}"\n";
    }
}

# Evening - Settlement (CRITICAL!)
my $settlement = $pepper->endOfDay();
if($settlement->{success}) {
    print "Day closed: $settlement->{transaction_count} transactions"\n";
}

MULTI-TERMINAL SUPPORT

Lib::Pepper::Simple supports multiple payment terminals in the same process, allowing a single application (e.g., PageCamel worker) to manage multiple physical terminals simultaneously.

How It Works

The Pepper C library is a process-wide singleton that can only be initialized once per process. Lib::Pepper::Simple handles this automatically using reference counting:

  1. First instance created → Library initialized, counter = 1

  2. Additional instances created → Reuse initialized library, counter increments

  3. Instances destroyed → Counter decrements, library stays initialized

  4. Last instance destroyed → Library finalized, counter = 0

Requirements

All terminals in the same process MUST use:

  • Identical license_xml (or license_file)

  • Identical config_xml (or config_file)

  • Identical library_path (if specified)

Each terminal CAN have:

  • Different terminal_address (IP:port) - REQUIRED for different physical terminals

  • Different terminal_type (Generic ZVT, Hobex ZVT, etc.)

  • Different per-terminal configuration (pos_number, merchant_password, etc.)

  • Different instance_id (automatically allocated if not specified)

Instance ID Allocation

Instance IDs are automatically allocated per terminal type:

# Terminal type 118 (Generic ZVT)
my $t1 = Lib::Pepper::Simple->new(terminal_type => 118, ...);  # instance_id = 1
my $t2 = Lib::Pepper::Simple->new(terminal_type => 118, ...);  # instance_id = 2

# Terminal type 120 (Hobex ZVT)
my $t3 = Lib::Pepper::Simple->new(terminal_type => 120, ...);  # instance_id = 1

Different terminal types maintain separate instance_id sequences.

You can manually specify an instance_id:

my $t = Lib::Pepper::Simple->new(
    terminal_type => 118,
    instance_id   => 42,  # Manual ID
    ...
);

WARNING: When manually specifying instance_id, ensure it doesn't conflict with other instances of the same terminal_type. The library will detect collisions.

Configuration Validation

When creating the second (or subsequent) terminal, Lib::Pepper::Simple validates that the configuration matches the first instance:

my $t1 = Lib::Pepper::Simple->new(license_file => $license1, ...);  # OK

my $t2 = Lib::Pepper::Simple->new(license_file => $license2, ...);  # FAILS!
# Error: Configuration mismatch: Lib::Pepper library already initialized
#        with different config

This prevents configuration conflicts that could cause unpredictable behavior.

Checking Library Status

Class method to check process-wide library status:

my $status = Lib::Pepper::Simple->library_status();

Returns hashref:

initialized

Boolean - Library initialized in this process?

instance_count

Integer - Number of active terminal instances

library_path

String - Library path used for initialization

instance_ids

Hashref - Next available instance_id per terminal_type:

{
    118 => 3,  # Next Generic ZVT would get ID 3
    120 => 2,  # Next Hobex ZVT would get ID 2
}

Example:

my $status = Lib::Pepper::Simple->library_status();
print "Active terminals: $status->{instance_count}"\n";

Instance-level status includes process info:

my $status = $terminal->checkStatus();
print "This terminal: instance_id $status->{instance_id}"\n";
print "Total active:  $status->{process_instance_count} terminals"\n";

Transaction Isolation

Each terminal maintains completely independent transaction state:

my $payment1 = $terminal1->doPayment(1000);  # Terminal 1
my $payment2 = $terminal2->doPayment(2000);  # Terminal 2

# Different trace numbers, different transactions
$payment1->{trace_number} ne $payment2->{trace_number}

Cancellations are terminal-specific and use the trace number from the original terminal:

# Refund payment from Terminal 1
$terminal1->cancelPayment($payment1->{trace_number}, 1000,
    reference_number => $payment1->{reference_number});

Complete Multi-Terminal Example

use Lib::Pepper::Simple;
use Lib::Pepper::Constants qw(:all);

# Initialize multiple terminals
my $terminalA = Lib::Pepper::Simple->new(
    terminal_type    => PEP_TERMINAL_TYPE_GENERIC_ZVT,
    terminal_address => '192.168.1.163:20008',  # Front counter
    license_file     => '/etc/pepper/license.xml',
    config_file      => '/etc/pepper/config.xml',
    pos_number       => '0001',
);

my $terminalB = Lib::Pepper::Simple->new(
    terminal_type    => PEP_TERMINAL_TYPE_GENERIC_ZVT,
    terminal_address => '192.168.1.164:20008',  # Drive-through
    license_file     => '/etc/pepper/license.xml',  # SAME!
    config_file      => '/etc/pepper/config.xml',   # SAME!
    pos_number       => '0002',  # Different POS number
);

# Check library status
my $libStatus = Lib::Pepper::Simple->library_status();
print "Library initialized: $libStatus->{initialized}"\n";
print "Active terminals: $libStatus->{instance_count}"\n";

# Process payments independently
my $paymentA = $terminalA->doPayment(1500);  # Front counter
if($paymentA->{authorized}) {
    print "Front counter: Payment authorized"\n";
    save_transaction('terminal_a', $paymentA->{trace_number},
                    $paymentA->{reference_number}, 1500);
}

my $paymentB = $terminalB->doPayment(2500);  # Drive-through
if($paymentB->{authorized}) {
    print "Drive-through: Payment authorized"\n";
    save_transaction('terminal_b', $paymentB->{trace_number},
                    $paymentB->{reference_number}, 2500);
}

# Each terminal can do end-of-day independently
my $settlementA = $terminalA->endOfDay();
my $settlementB = $terminalB->endOfDay();

# Cleanup (automatic when objects destroyed)
undef $terminalA;  # Counter = 1, library stays initialized
undef $terminalB;  # Counter = 0, library finalized

Database Schema for Multi-Terminal

When storing transactions, include terminal identification:

CREATE TABLE payment_transactions (
    id SERIAL PRIMARY KEY,
    terminal_id VARCHAR(20) NOT NULL,        -- 'terminal_a', 'terminal_b', etc.
    instance_id INTEGER NOT NULL,            -- From checkStatus()->{instance_id}
    order_id INTEGER NOT NULL,
    trace_number VARCHAR(20) NOT NULL,
    reference_number VARCHAR(50) NOT NULL,   -- For card-not-present refunds
    amount INTEGER NOT NULL,
    transaction_date TIMESTAMP DEFAULT NOW(),

    INDEX idx_terminal (terminal_id),
    INDEX idx_trace (trace_number),
    UNIQUE (terminal_id, trace_number)
);

This allows you to identify which physical terminal processed each transaction.

Use Cases

Retail with Multiple Checkout Lanes

Each checkout lane has its own terminal, all managed by a single POS application.

Drive-Through + Counter Service

Fast food restaurant with both drive-through terminal and counter terminal.

Multi-Location Kiosks

Self-service kiosks at different locations, managed by a central application.

Backup Terminal

Primary terminal with automatic failover to backup terminal on connection loss.

Limitations

Thread Safety

Not thread-safe. Do not create instances from different threads simultaneously. If using threads, protect instance creation with a mutex.

Configuration Flexibility

All terminals must use identical license and config. Cannot mix different licenses or configurations in the same process.

Instance ID Management

When manually specifying instance_id, you are responsible for avoiding conflicts.

ERROR HANDLING

Methods return structured hashrefs with error information rather than throwing exceptions for operational failures (payment declined, settlement failed, etc.).

Constructor (new()) and parameter validation errors throw exceptions with croak().

Always check the success field in return values:

my $payment = $pepper->doPayment($amount);
if(!$payment->{success}) {
    # API call failed
    die "Payment error: $payment->{error}";
}

if(!$payment->{authorized}) {
    # Payment declined/aborted
    print "Payment not authorized: $payment->{transaction_text}"\n";
}

AUTOMATIC FEATURES

Recovery Operations

Automatically detected and performed during initialization if recovery flag is set.

VOID→REFUND Fallback

cancelPayment() automatically switches from VOID to REFUND for settled transactions.

Connection Management

OPEN operation performed automatically. Connection closed in destructor.

State Validation

All operations validate state before execution with clear error messages.

WARNING: AI USE

Warning, this file was generated with the help of the 'Claude' AI (an LLM/large language model by the USA company Anthropic PBC) in November 2025. It was not reviewed line-by-line by a human, only on a functional level. It is therefore not up to the usual code quality and review standards. Different copyright laws may also apply, since the program was not created by humans but mostly by a machine, therefore the laws requiring a human creative process may or may not apply. Laws regarding AI use are changing rapidly. Before using the code provided in this file for any of your projects, make sure to check the current version of your local laws.

SEE ALSO

Lib::Pepper

Low-level library interface

Lib::Pepper::Instance

Terminal instance management

Lib::Pepper::Constants

Constants for terminal types, languages, transaction types, etc.

AUTHOR

Rene Schickbauer, <cavac@cpan.org>

COPYRIGHT AND LICENSE

Copyright (C) 2025 by Rene Schickbauer

This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.