File Coverage

blib/lib/Lib/Pepper/Instance.pm
Criterion Covered Total %
statement 44 296 14.8
branch 0 112 0.0
condition 0 36 0.0
subroutine 15 32 46.8
pod 15 15 100.0
total 74 491 15.0


line stmt bran cond sub pod time code
1             package Lib::Pepper::Instance;
2             #---AUTOPRAGMASTART---
3 5     5   2084 use v5.42;
  5         21  
4 5     5   42 use strict;
  5         11  
  5         182  
5 5     5   27 use diagnostics;
  5         10  
  5         35  
6 5     5   198 use mro 'c3';
  5         12  
  5         34  
7 5     5   193 use English;
  5         10  
  5         35  
8 5     5   2445 use Carp qw[carp croak confess cluck longmess shortmess];
  5         11  
  5         593  
9             our $VERSION = 0.5;
10 5     5   37 use autodie qw( close );
  5         11  
  5         42  
11 5     5   1977 use Array::Contains;
  5         32  
  5         318  
12 5     5   32 use utf8;
  5         11  
  5         41  
13 5     5   213 use Data::Dumper;
  5         14  
  5         277  
14 5     5   29 use Data::Printer;
  5         11  
  5         50  
15             #---AUTOPRAGMAEND---
16              
17              
18 5     5   514 use Lib::Pepper;
  5         13  
  5         228  
19 5     5   3332 use Lib::Pepper::Exception;
  5         19  
  5         299  
20 5     5   4017 use Lib::Pepper::OptionList;
  5         16  
  5         400  
21 5     5   37 use Lib::Pepper::Constants qw(:all);
  5         11  
  5         26401  
22              
23 0     0 1   sub new($class, %params) {
  0            
  0            
  0            
24 0           my $terminalType = $params{terminal_type};
25 0   0       my $instanceId = $params{instance_id} || 1;
26              
27 0 0         if(!defined $terminalType) {
28 0           croak('new: terminal_type parameter is required');
29             }
30              
31             my $self = {
32             handle => undef,
33             terminal_type => $terminalType,
34             instance_id => $instanceId,
35             configured => 0,
36             callback => undef,
37             userdata => undef,
38             reph => $params{reph}, # Optional reporting handler
39 0           };
40              
41 0           bless $self, $class;
42              
43             # Create the instance
44 0           my ($result, $handle) = Lib::Pepper::pepCreateInstance($terminalType, $instanceId);
45 0           Lib::Pepper::Exception->checkResult($result, 'pepCreateInstance');
46              
47 0           $self->{handle} = $handle;
48              
49 0           return $self;
50             }
51              
52 0     0 1   sub getHandle($self) {
  0            
  0            
53 0           return $self->{handle};
54             }
55              
56 0     0 1   sub isConfigured($self) {
  0            
  0            
57 0           return $self->{configured};
58             }
59              
60 0     0 1   sub configure($self, %params) {
  0            
  0            
  0            
61 0 0         if(!defined $self->{handle}) {
62 0           croak('configure: instance not initialized');
63             }
64              
65 0           my $callback = $params{callback};
66 0   0       my $options = $params{options} || {};
67 0           my $userdata = $params{userdata};
68              
69 0 0 0       if(!defined $callback || ref($callback) ne 'CODE') {
70 0           croak('configure: callback parameter must be a code reference');
71             }
72              
73             # Create option list manually to handle callback values properly
74 0           my $inputOptions = Lib::Pepper::OptionList->new();
75              
76             # Add callback configuration (mandatory for pepConfigure)
77             # Default to receiving both output and input callbacks with all options
78             my $callbackEvent = $options->{iCallbackEventValue} //
79 0   0       (PEP_CALLBACK_EVENT_OUTPUT | PEP_CALLBACK_EVENT_INPUT);
80             my $callbackOption = $options->{iCallbackOptionValue} //
81 0   0       (PEP_CALLBACK_OPTION_INTERMEDIATE_STATUS | PEP_CALLBACK_OPTION_OPERATION_FINISHED |
82             PEP_CALLBACK_OPTION_INTERMEDIATE_TICKET |
83             PEP_CALLBACK_OPTION_SELECTION_LIST | PEP_CALLBACK_OPTION_NUMERICAL_INPUT |
84             PEP_CALLBACK_OPTION_ALPHANUMERICAL_INPUT | PEP_CALLBACK_OPTION_COMPLEX_INPUT);
85              
86 0           $inputOptions->addInt('iCallbackEventValue', $callbackEvent);
87 0           $inputOptions->addInt('iCallbackOptionValue', $callbackOption);
88              
89             # Add other options
90 0           for my $key (keys %{$options}) {
  0            
91 0 0         next if $key eq 'iCallbackEventValue';
92 0 0         next if $key eq 'iCallbackOptionValue';
93              
94 0           my $value = $options->{$key};
95 0 0         if(!defined $value) {
    0          
    0          
96 0           next;
97             } elsif(ref($value) eq 'HASH') {
98 0           my $childList = Lib::Pepper::OptionList->fromHashref($value);
99 0           $inputOptions->addChild($key, $childList);
100             } elsif(ref($value) eq '') {
101             # Use key prefix to determine type: i=int, s=string, h=handle/child
102 0 0         if($key =~ /^i/) {
103 0           $inputOptions->addInt($key, $value);
104             } else {
105 0           $inputOptions->addString($key, $value);
106             }
107             }
108             }
109              
110             # Store callback and userdata
111 0           $self->{callback} = $callback;
112 0           $self->{userdata} = $userdata;
113              
114             # Configure with callback
115             my ($result, $outputOptions) = Lib::Pepper::pepConfigureWithCallback(
116             $self->{handle},
117 0           $inputOptions->getHandle(),
118             $callback,
119             $userdata
120             );
121              
122 0           Lib::Pepper::Exception->checkResult($result, 'pepConfigure');
123              
124 0           $self->{configured} = 1;
125              
126             # Return output options as OptionList object
127 0 0         if(defined $outputOptions) {
128 0           return Lib::Pepper::OptionList->new($outputOptions);
129             }
130              
131 0           return;
132             }
133              
134 0     0 1   sub prepareOperation($self, $operation, $inputOptions = undef) {
  0            
  0            
  0            
  0            
135 0 0         if(!defined $self->{handle}) {
136 0           croak('prepareOperation: instance not initialized');
137             }
138              
139 0 0         if(!$self->{configured}) {
140 0           croak('prepareOperation: instance not configured');
141             }
142              
143 0 0         my $inputHandle = defined $inputOptions ? $inputOptions->getHandle() : PEP_INVALID_HANDLE;
144              
145             my ($result, $operationHandle, $outputHandle) = Lib::Pepper::pepPrepareOperation(
146             $self->{handle},
147 0           $operation,
148             $inputHandle
149             );
150              
151             # Provide helpful context for state transition errors
152 0 0         if($result == -1301) {
153 0   0       my $termType = $self->{terminal_type} || 'unknown';
154 0 0         my $opName = $operation == 4 ? 'TRANSACTION' :
    0          
    0          
    0          
155             $operation == 5 ? 'SETTLEMENT' :
156             $operation == 1 ? 'OPEN' :
157             $operation == 2 ? 'CLOSE' : $operation;
158              
159 0           my $extraMsg = '';
160 0 0 0       if($termType == 999 || $termType eq PEP_TERMINAL_TYPE_MOCK) {
161 0           $extraMsg = "\n\nNOTE: Mock terminals (TT999) do NOT support transaction operations.\n" .
162             "This is expected behavior. For transaction testing, use:\n" .
163             " - PEP_TERMINAL_TYPE_GENERIC_ZVT (118) - Generic ZVT terminals\n" .
164             " - PEP_TERMINAL_TYPE_HOBEX_ZVT (120) - Hobex ZVT terminals\n" .
165             "See examples/README.md and IMPLEMENTATION_STATUS.md for details.";
166             }
167              
168 0           croak("pepPrepareOperation: Invalid state transition (code: -1301)\n" .
169             "Operation: $opName\n" .
170             "Terminal Type: $termType\n" .
171             "The instance is not in the correct state for this operation.$extraMsg");
172             }
173              
174 0           Lib::Pepper::Exception->checkResult($result, 'pepPrepareOperation');
175              
176             return (
177 0 0         $operationHandle,
178             defined $outputHandle ? Lib::Pepper::OptionList->new($outputHandle) : undef
179             );
180             }
181              
182 0     0 1   sub startOperation($self, $operation, $inputOptions = undef) {
  0            
  0            
  0            
  0            
183 0 0         if(!defined $self->{handle}) {
184 0           croak('startOperation: instance not initialized');
185             }
186              
187 0 0         if(!$self->{configured}) {
188 0           croak('startOperation: instance not configured');
189             }
190              
191 0 0         my $inputHandle = defined $inputOptions ? $inputOptions->getHandle() : PEP_INVALID_HANDLE;
192              
193             my ($result, $operationHandle, $outputHandle) = Lib::Pepper::pepStartOperation(
194             $self->{handle},
195 0           $operation,
196             $inputHandle
197             );
198              
199 0           Lib::Pepper::Exception->checkResult($result, 'pepStartOperation');
200              
201             return (
202 0 0         $operationHandle,
203             defined $outputHandle ? Lib::Pepper::OptionList->new($outputHandle) : undef
204             );
205             }
206              
207 0     0 1   sub executeOperation($self, $operation, $inputOptions = undef) {
  0            
  0            
  0            
  0            
208 0 0         if(!defined $self->{handle}) {
209 0           croak('executeOperation: instance not initialized');
210             }
211              
212 0 0         if(!$self->{configured}) {
213 0           croak('executeOperation: instance not configured');
214             }
215              
216 0 0         my $inputHandle = defined $inputOptions ? $inputOptions->getHandle() : PEP_INVALID_HANDLE;
217              
218             my ($result, $operationHandle, $outputHandle) = Lib::Pepper::pepExecuteOperation(
219             $self->{handle},
220 0           $operation,
221             $inputHandle
222             );
223              
224 0           Lib::Pepper::Exception->checkResult($result, 'pepExecuteOperation');
225              
226             return (
227 0 0         $operationHandle,
228             defined $outputHandle ? Lib::Pepper::OptionList->new($outputHandle) : undef
229             );
230             }
231              
232 0     0 1   sub finalizeOperation($self, $operation, $inputOptions = undef) {
  0            
  0            
  0            
  0            
233 0 0         if(!defined $self->{handle}) {
234 0           croak('finalizeOperation: instance not initialized');
235             }
236              
237 0 0         if(!$self->{configured}) {
238 0           croak('finalizeOperation: instance not configured');
239             }
240              
241 0 0         my $inputHandle = defined $inputOptions ? $inputOptions->getHandle() : PEP_INVALID_HANDLE;
242              
243             my ($result, $operationHandle, $outputHandle) = Lib::Pepper::pepFinalizeOperation(
244             $self->{handle},
245 0           $operation,
246             $inputHandle
247             );
248              
249 0           Lib::Pepper::Exception->checkResult($result, 'pepFinalizeOperation');
250              
251             return (
252 0 0         $operationHandle,
253             defined $outputHandle ? Lib::Pepper::OptionList->new($outputHandle) : undef
254             );
255             }
256              
257 0     0 1   sub operationStatus($self, $operationHandle, $waitForCompletion = 0) {
  0            
  0            
  0            
  0            
258 0 0         if(!defined $self->{handle}) {
259 0           croak('operationStatus: instance not initialized');
260             }
261              
262             my ($result, $status) = Lib::Pepper::pepOperationStatus(
263             $self->{handle},
264 0           $operationHandle,
265             $waitForCompletion
266             );
267              
268 0           Lib::Pepper::Exception->checkResult($result, 'pepOperationStatus');
269              
270 0           return $status;
271             }
272              
273 0     0 1   sub openConnection($self, %params) {
  0            
  0            
  0            
274 0 0         if(!defined $self->{handle}) {
275 0           croak('openConnection: instance not initialized');
276             }
277              
278 0 0         if(!$self->{configured}) {
279 0           croak('openConnection: instance not configured');
280             }
281              
282 0   0       my $options = $params{options} || {};
283 0           my $inputOptions = Lib::Pepper::OptionList->fromHashref($options);
284              
285             # Execute the OPEN operation using the 4-step workflow
286             # All 4 steps should be called in sequence, then wait for completion at the end
287 0           my ($opHandle1, $output1) = $self->prepareOperation(PEP_OPERATION_OPEN, $inputOptions);
288 0           my ($opHandle2, $output2) = $self->startOperation(PEP_OPERATION_OPEN, $inputOptions);
289 0           my ($opHandle3, $output3) = $self->executeOperation(PEP_OPERATION_OPEN, $inputOptions);
290 0           my ($opHandle4, $output4) = $self->finalizeOperation(PEP_OPERATION_OPEN, $inputOptions);
291              
292             # Wait for the final step to complete
293 0           my $status4 = $self->operationStatus($opHandle4, 1);
294              
295             return {
296 0           operation_handle => $opHandle4,
297             status => $status4,
298             output => $output4,
299             };
300             }
301              
302 0     0 1   sub closeConnection($self, %params) {
  0            
  0            
  0            
303 0 0         if(!defined $self->{handle}) {
304 0           croak('closeConnection: instance not initialized');
305             }
306              
307 0 0         if(!$self->{configured}) {
308 0           croak('closeConnection: instance not configured');
309             }
310              
311 0   0       my $options = $params{options} || {};
312 0           my $inputOptions = Lib::Pepper::OptionList->fromHashref($options);
313              
314             # Execute the CLOSE operation using the 4-step workflow
315             # All 4 steps should be called in sequence, then wait for completion at the end
316 0           my ($opHandle1, $output1) = $self->prepareOperation(PEP_OPERATION_CLOSE, $inputOptions);
317 0           my ($opHandle2, $output2) = $self->startOperation(PEP_OPERATION_CLOSE, $inputOptions);
318 0           my ($opHandle3, $output3) = $self->executeOperation(PEP_OPERATION_CLOSE, $inputOptions);
319 0           my ($opHandle4, $output4) = $self->finalizeOperation(PEP_OPERATION_CLOSE, $inputOptions);
320              
321             # Wait for the final step to complete
322 0           my $status4 = $self->operationStatus($opHandle4, 1);
323              
324             return {
325 0           operation_handle => $opHandle4,
326             status => $status4,
327             output => $output4,
328             };
329             }
330              
331 0     0 1   sub transaction($self, %params) {
  0            
  0            
  0            
332 0 0         if(!defined $self->{handle}) {
333 0           croak('transaction: instance not initialized');
334             }
335              
336 0 0         if(!$self->{configured}) {
337 0           croak('transaction: instance not configured');
338             }
339              
340 0   0       my $transactionType = $params{transaction_type} || PEP_TRANSACTION_TYPE_GOODS_PAYMENT;
341 0           my $amount = $params{amount};
342 0           my $currency = $params{currency};
343 0   0       my $additionalOptions = $params{options} || {};
344              
345 0 0         if(!defined $amount) {
346 0           croak('transaction: amount parameter is required');
347             }
348              
349             # Build input options for transaction
350             # Real ZVT terminals require transaction parameters in operation options
351 0           my $inputOptions = Lib::Pepper::OptionList->new();
352              
353             # Add mandatory transaction parameters
354 0           $inputOptions->addInt('iTransactionTypeValue', $transactionType);
355 0           $inputOptions->addInt('iAmount', $amount);
356              
357             # Add currency if specified (some terminals require it, others handle it via config)
358 0 0         if(defined $currency) {
359 0           $inputOptions->addInt('iCurrency', $currency);
360             }
361              
362             # Add any additional user-specified options
363 0           for my $key (keys %{$additionalOptions}) {
  0            
364 0           my $value = $additionalOptions->{$key};
365 0 0         next unless defined $value;
366              
367 0 0         if($key =~ /^i/) {
    0          
368 0           $inputOptions->addInt($key, $value);
369             } elsif($key =~ /^h/) {
370 0 0 0       if(ref($value) && $value->isa('Lib::Pepper::OptionList')) {
371 0           $inputOptions->addChild($key, $value);
372             }
373             } else {
374 0           $inputOptions->addString($key, $value);
375             }
376             }
377              
378             # Execute the full 4-step workflow
379             # All 4 steps should be called in sequence, then wait for completion at the end
380 0           my ($opHandle1, $output1) = $self->prepareOperation(PEP_OPERATION_TRANSACTION, $inputOptions);
381 0           my ($opHandle2, $output2) = $self->startOperation(PEP_OPERATION_TRANSACTION, $inputOptions);
382 0           my ($opHandle3, $output3) = $self->executeOperation(PEP_OPERATION_TRANSACTION, $inputOptions);
383 0           my ($opHandle4, $output4) = $self->finalizeOperation(PEP_OPERATION_TRANSACTION, $inputOptions);
384              
385             # Wait for the final step to complete
386 0           my $status4 = $self->operationStatus($opHandle4, 1);
387              
388             # CRITICAL: Return value does NOT indicate payment success!
389             # Check $output4->getInt('iTransactionResultValue') == 0 for payment authorization
390             return {
391 0           operation_handle => $opHandle4,
392             status => $status4, # API completion status, NOT payment status
393             output => $output4, # Contains iTransactionResultValue (0=authorized, -1=declined/aborted)
394             };
395             }
396              
397 0     0 1   sub settlement($self, %params) {
  0            
  0            
  0            
398 0 0         if(!defined $self->{handle}) {
399 0           croak('settlement: instance not initialized');
400             }
401              
402 0 0         if(!$self->{configured}) {
403 0           croak('settlement: instance not configured');
404             }
405              
406 0   0       my $options = $params{options} || {};
407              
408 0           my $inputOptions = Lib::Pepper::OptionList->fromHashref($options);
409              
410             # Execute the full 4-step workflow
411             # All 4 steps should be called in sequence, then wait for completion at the end
412 0           my ($opHandle1, $output1) = $self->prepareOperation(PEP_OPERATION_SETTLEMENT, $inputOptions);
413 0           my ($opHandle2, $output2) = $self->startOperation(PEP_OPERATION_SETTLEMENT, $inputOptions);
414 0           my ($opHandle3, $output3) = $self->executeOperation(PEP_OPERATION_SETTLEMENT, $inputOptions);
415 0           my ($opHandle4, $output4) = $self->finalizeOperation(PEP_OPERATION_SETTLEMENT, $inputOptions);
416              
417             # Wait for the final step to complete
418 0           my $status4 = $self->operationStatus($opHandle4, 1);
419              
420             return {
421 0           operation_handle => $opHandle4,
422             status => $status4,
423             output => $output4,
424             };
425             }
426              
427 0     0 1   sub utility($self, %params) {
  0            
  0            
  0            
428 0 0         if(!defined $self->{handle}) {
429 0           croak('utility: instance not initialized');
430             }
431              
432 0 0         if(!$self->{configured}) {
433 0           croak('utility: instance not configured');
434             }
435              
436 0   0       my $options = $params{options} || {};
437 0           my $inputOptions = Lib::Pepper::OptionList->fromHashref($options);
438              
439             my ($result, $outputHandle) = Lib::Pepper::pepUtility(
440             $self->{handle},
441 0           $inputOptions->getHandle()
442             );
443              
444 0           Lib::Pepper::Exception->checkResult($result, 'pepUtility');
445              
446 0 0         return defined $outputHandle ? Lib::Pepper::OptionList->new($outputHandle) : undef;
447             }
448              
449 0     0 1   sub auxiliary($self, %params) {
  0            
  0            
  0            
450 0 0         if(!defined $self->{handle}) {
451 0           croak('auxiliary: instance not initialized');
452             }
453              
454 0 0         if(!$self->{configured}) {
455 0           croak('auxiliary: instance not configured');
456             }
457              
458 0   0       my $options = $params{options} || {};
459 0           my $inputOptions = Lib::Pepper::OptionList->fromHashref($options);
460              
461             my ($result, $operationHandle, $outputHandle) = Lib::Pepper::pepAuxiliary(
462             $self->{handle},
463 0           $inputOptions->getHandle()
464             );
465              
466 0           Lib::Pepper::Exception->checkResult($result, 'pepAuxiliary');
467              
468             return (
469 0 0         $operationHandle,
470             defined $outputHandle ? Lib::Pepper::OptionList->new($outputHandle) : undef
471             );
472             }
473              
474 0     0     sub DESTROY($self) {
  0            
  0            
475 0 0         if(defined $self->{handle}) {
476 0           my $result = Lib::Pepper::pepFreeInstance($self->{handle});
477             # Don't croak in DESTROY - just warn on failure
478 0 0         if($result < 0) {
479 0           carp("Warning: pepFreeInstance failed with code: $result");
480             }
481 0           $self->{handle} = undef;
482             }
483 0           return;
484             }
485              
486             # Internal logging helper - uses reph if available, falls back to STDERR
487 0     0     sub _log($self, @parts) {
  0            
  0            
  0            
488 0 0 0       if($self->{reph} && $self->{reph}->can('debuglog')) {
489 0           $self->{reph}->debuglog(@parts);
490             } else {
491             # Fallback to STDERR if no reph provided
492 0           print STDERR join('', @parts), "\n";
493             }
494 0           return;
495             }
496              
497             1;
498              
499             __END__