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',
21 'Interface-Version' => "$me $VERSION",
22 ); # Content-Type has to be passed separately
24 tie my %new_order, 'Tie::IxHash', (
25 OrbitalConnectionUsername => ':login',
26 OrbitalConnectionPassword => ':password',
27 IndustryType => 'EC', # Assume industry = Ecommerce
28 MessageType => ':message_type',
30 MerchantID => ':merchant_id',
31 TerminalID => ':terminal_id',
33 AccountNum => ':card_number',
35 CurrencyCode => ':currency_code',
36 CurrencyExponent => ':currency_exp',
37 CardSecValInd => ':cvvind',
38 CardSecVal => ':cvv2',
39 # AVSname => ':name', not needed
41 AVSaddress1 => ':address',
44 OrderID => ':invoice_number',
46 Comments => ':email', # as per B:OP:WesternACH
73 # Per ISO 4217. Add to this as needed.
82 $self->server('orbitalvar1.paymentech.net') unless $self->server; # this is the test server.
83 $self->port('443') unless $self->port;
84 $self->path('/authorize') unless $self->path;
86 $self->build_subs(qw( TxRefNum ProcStatus ApprovalStatus StatusMsg Response ));
92 my %content = $self->content();
94 tie my %data, 'Tie::IxHash';
95 ref($skel) eq 'HASH' or die 'Tried to build non-hash';
96 foreach my $k (keys(%$skel)) {
98 # Not recursive like B:OP:WesternACH; Paymentech requests are only one layer deep.
100 # Get the content field with that name.
101 $data{$k} = $content{$1};
113 my %content = $self->content();
114 foreach(qw(merchant_id terminal_id currency)) {
115 $content{$_} = $self->{$_} if exists($self->{$_});
118 $self->required_fields('action');
120 ('normal authorization' => 'AC',
121 'authorization only' => 'A',
123 'post authorization' => 'MFC', # for our use, doesn't go in the request
125 $content{'message_type'} = $message_type{lc($content{'action'})}
126 or die "unsupported action: '".$content{'action'}."'";
127 if($content{'message_type'} eq 'MFC') {
128 die 'MarkForCapture not implemented';
129 # for later implementation
132 foreach (keys(%defaults) ) {
133 $content{$_} = $defaults{$_} if !defined($content{$_});
136 if(length($content{merchant_id}) == 12) {
137 $content{bin} = '000002' # PNS
139 elsif(length($content{merchant_id}) == 6) {
140 $content{bin} = '000001' # Salem
143 die "invalid merchant ID: '".$content{merchant_id}."'";
146 @content{qw(currency_code currency_exp)} = @{$currency_code{$content{currency}}}
147 if $content{currency};
149 if($content{card_number} =~ /^(4|6011)/) { # Matches Visa and Discover transactions
150 if(defined($content{cvv2})) {
151 $content{cvvind} = 1; # "Value is present"
154 $content{cvvind} = 9; # "Value is not available"
157 $content{amount} = int($content{amount}*100);
158 $content{name} = $content{first_name} . ' ' . $content{last_name};
159 # According to the spec, the first 8 characters of this have to be unique.
160 # The test server doesn't enforce this, but we comply anyway to the extent possible.
161 if($content{invoice_number}) {
162 # Mark it so that it's obvious that this is an invoice number
163 $content{invoice_number} = 'INV '.$content{invoice_number};
166 # Otherwise, make something up!
167 $content{invoice_number} ||= sprintf("%04x%04x",time % 2**16,int(rand() * 2**16));
170 $content{expiration} =~ s/\D//g; # Because Freeside sends it as mm/yy, not mmyy.
172 $self->content(%content);
178 $DB::single = $DEBUG;
182 # This will change when we add e-check support
183 my @required_fields = @required;
185 $self->required_fields(@required_fields);
187 # This will change when we add mark-for-capture support
188 my $request = { NewOrder => $self->build(\%new_order) };
190 my $post_data = XMLout({ Request => $request }, KeepRoot => 1, NoAttr => 1, NoSort => 1);
192 if (!$self->test_transaction()) {
193 $self->server('orbital1.paymentech.net');
196 warn $post_data if $DEBUG;
197 $DB::single = $DEBUG;
198 my($page,$server_response,%headers) =
199 $self->https_post( { 'Content-Type' => 'application/PTI47',
200 'headers' => \%request_header } ,
203 warn $page if $DEBUG;
207 if ($server_response =~ /200/){
208 $response = XMLin($page, KeepRoot => 0);
209 $self->Response($response);
210 my ($r) = values(%$response);
211 if(!exists($r->{'ProcStatus'})) {
212 $error = "Malformed response: '$page'";
214 elsif($r->{'ProcStatus'} != 0 || $r->{'ApprovalStatus'} != 1) {
215 $error = "Transaction error: '". ($r->{'ProcStatusMsg'} || $r->{'StatusMsg'}) . "'";
219 $self->is_success(1);
220 $self->authorization($r->{'TxRefNum'});
223 $error = "Server error: '$server_response'";
225 $self->error_message($error);
226 $self->is_success(0) if $error;
235 Business::OnlinePayment::PaymenTech - Chase Paymentech backend for Business::OnlinePayment
239 $trans = new Business::OnlinePayment('PaymenTech');
242 password => "password",
243 merchant_id => "000111222333",
244 terminal_id => "001",
246 card_number => "5500000000000004",
247 expiration => "0211",
248 address => "123 Anystreet",
249 city => "Sacramento",
251 action => "Normal Authorization",
257 if($trans->is_approved) {
258 print "Approved: ".$trans->authorization;
261 print "Failed: ".$trans->error_message;
267 The only supported transaction types are Normal Authorization and Credit. Paymentech
268 supports separate Authorize and Capture actions as well as recurring billing, but
269 those are not yet implemented.
271 Electronic check processing is not yet supported.
275 Mark Wells, mark@freeside.biz
279 perl(1). L<Business::OnlinePayment>.