1 package Business::OnlinePayment::PaymenTech;
5 use Business::OnlinePayment::HTTPS;
8 use vars qw($VERSION $DEBUG @ISA $me);
10 @ISA = qw(Business::OnlinePayment::HTTPS);
13 $me='Business::OnlinePayment::PaymenTech';
15 my %request_header = (
16 'MIME-VERSION' => '1.0',
17 'Content-Transfer-Encoding' => 'text',
18 'Request-Number' => 1,
19 'Document-Type' => 'Request',
20 'Interface-Version' => "$me $VERSION",
21 ); # Content-Type has to be passed separately
23 tie my %new_order, 'Tie::IxHash', (
24 OrbitalConnectionUsername => ':login',
25 OrbitalConnectionPassword => ':password',
26 IndustryType => 'EC', # Assume industry = Ecommerce
27 MessageType => ':message_type',
29 MerchantID => ':merchant_id',
30 TerminalID => ':terminal_id',
32 AccountNum => ':card_number',
34 CurrencyCode => ':currency_code',
35 CurrencyExponent => ':currency_exp',
36 CardSecValInd => ':cvvind',
37 CardSecVal => ':cvv2',
39 AVSaddress1 => ':address',
42 OrderID => ':invoice_number',
44 Comments => ':email', # as per B:OP:WesternACH
45 TxRefNum => ':order_number', # used only for Refund
48 tie my %mark_for_capture, 'Tie::IxHash', (
49 OrbitalConnectionUsername => ':login',
50 OrbitalConnectionPassword => ':password',
51 OrderID => ':invoice_number',
54 MerchantID => ':merchant_id',
55 TerminalID => ':terminal_id',
56 TxRefNum => ':order_number',
59 tie my %reversal, 'Tie::IxHash', (
60 OrbitalConnectionUsername => ':login',
61 OrbitalConnectionPassword => ':password',
62 TxRefNum => ':order_number',
64 OrderID => ':invoice_number',
66 MerchantID => ':merchant_id',
67 TerminalID => ':terminal_id',
68 # Always attempt to reverse authorization.
69 OnlineReversalInd => 'Y',
90 # Per ISO 4217. Add to this as needed.
99 $self->server('orbitalvar1.paymentech.net') unless $self->server; # this is the test server.
100 $self->port('443') unless $self->port;
101 $self->path('/authorize') unless $self->path;
103 $self->build_subs(qw(
119 my %content = $self->content();
121 tie my %data, 'Tie::IxHash';
122 ref($skel) eq 'HASH' or die 'Tried to build non-hash';
123 foreach my $k (keys(%$skel)) {
125 # Not recursive like B:OP:WesternACH; Paymentech requests are only one layer deep.
127 # Get the content field with that name.
128 $data{$k} = $content{$1};
140 my %content = $self->content();
141 foreach(qw(merchant_id terminal_id currency)) {
142 $content{$_} = $self->{$_} if exists($self->{$_});
145 $self->required_fields('action');
147 ('normal authorization' => 'AC',
148 'authorization only' => 'A',
151 'post authorization' => 'MFC', # for our use, doesn't go in the request
153 $content{'message_type'} = $message_type{lc($content{'action'})}
154 or die "unsupported action: '".$content{'action'}."'";
156 foreach (keys(%defaults) ) {
157 $content{$_} = $defaults{$_} if !defined($content{$_});
159 if(length($content{merchant_id}) == 12) {
160 $content{bin} = '000002' # PNS
162 elsif(length($content{merchant_id}) == 6) {
163 $content{bin} = '000001' # Salem
166 die "invalid merchant ID: '".$content{merchant_id}."'";
169 @content{qw(currency_code currency_exp)} = @{$currency_code{$content{currency}}}
170 if $content{currency};
172 if($content{card_number} =~ /^(4|6011)/) { # Matches Visa and Discover transactions
173 if(defined($content{cvv2})) {
174 $content{cvvind} = 1; # "Value is present"
177 $content{cvvind} = 9; # "Value is not available"
180 $content{amount} = int($content{amount}*100);
181 $content{name} = $content{first_name} . ' ' . $content{last_name};
182 # According to the spec, the first 8 characters of this have to be unique.
183 # The test server doesn't enforce this, but we comply anyway to the extent possible.
184 if(! $content{invoice_number}) {
185 # Choose one arbitrarily
186 $content{invoice_number} ||= sprintf("%04x%04x",time % 2**16,int(rand() * 2**16));
189 $content{expiration} =~ s/\D//g; # Because Freeside sends it as mm/yy, not mmyy.
191 $self->content(%content);
197 $DB::single = $DEBUG;
200 my %content = $self->content;
202 my @required_fields = @required;
205 if( $content{'message_type'} eq 'MFC' ) {
206 $request = { MarkForCapture => $self->build(\%mark_for_capture) };
207 push @required_fields, 'order_number';
209 elsif( $content{'message_type'} eq 'V' ) {
210 $request = { Reversal => $self->build(\%reversal) };
213 $request = { NewOrder => $self->build(\%new_order) };
214 push @required_fields, qw(
224 $self->required_fields(@required_fields);
226 my $post_data = XMLout({ Request => $request }, KeepRoot => 1, NoAttr => 1, NoSort => 1);
228 if (!$self->test_transaction()) {
229 $self->server('orbital1.paymentech.net');
232 warn $post_data if $DEBUG;
233 $DB::single = $DEBUG;
234 my($page,$server_response,%headers) =
235 $self->https_post( { 'Content-Type' => 'application/PTI47',
236 'headers' => \%request_header } ,
239 warn $page if $DEBUG;
243 if ($server_response =~ /200/){
244 $response = XMLin($page, KeepRoot => 0);
245 $self->Response($response);
246 my ($r) = values(%$response);
247 foreach(qw(ProcStatus RespCode AuthCode AVSRespCode CVV2RespCode)) {
248 if(exists($r->{$_}) and
253 if(!exists($r->{'ProcStatus'})) {
254 $error = "Malformed response: '$page'";
255 $self->is_success(0);
257 elsif( $r->{'ProcStatus'} != 0 or
258 # NewOrders get ApprovalStatus, Reversals don't.
259 ( exists($r->{'ApprovalStatus'}) ?
260 $r->{'ApprovalStatus'} != 1 :
261 $r->{'StatusMsg'} ne 'Approved' )
263 $error = "Transaction error: '". ($r->{'ProcStatusMsg'} || $r->{'StatusMsg'}) . "'";
264 $self->is_success(0);
268 $self->is_success(1);
269 # For credits, AuthCode is empty and gets converted to a hashref.
270 $self->authorization($r->{'AuthCode'}) if !ref($r->{'AuthCode'});
271 $self->order_number($r->{'TxRefNum'});
274 $error = "Server error: '$server_response'";
276 $self->error_message($error);
284 Business::OnlinePayment::PaymenTech - Chase Paymentech backend for Business::OnlinePayment
288 $trans = new Business::OnlinePayment('PaymenTech');
291 password => "password",
292 merchant_id => "000111222333",
293 terminal_id => "001",
295 card_number => "5500000000000004",
296 expiration => "0211",
297 address => "123 Anystreet",
298 city => "Sacramento",
300 action => "Normal Authorization",
306 if($trans->is_approved) {
307 print "Approved: ".$trans->authorization;
310 print "Failed: ".$trans->error_message;
316 The only supported transaction types are Normal Authorization and Credit. Paymentech
317 supports separate Authorize and Capture actions as well as recurring billing, but
318 those are not yet implemented.
320 Electronic check processing is not yet supported.
324 Mark Wells, mark@freeside.biz
328 perl(1). L<Business::OnlinePayment>.