Revision 554

Date:
2016/03/16 22:57:06
Author:
ahitrov
Revision Log:
Payture InPay gate API (init + pay) & Notification backend

Files:

Legend:

 
Added
 
Removed
 
Modified
  • utf8/plugins/payments/comps/www/payment.backend/payture_pay.html

     
    1 <%once>
    2
    3 my %NAMES = (
    4 'MerchantBlock' => 'Authorized',
    5 'MerchantPay' => 'Charged',
    6 'MerchantRefund' => 'Refunded',
    7 );
    8
    9 </%once>
    10 <%args>
    11
    12 $SessionId => undef
    13 $OrderId => undef
    14 $Notification => undef
    15 $Success => undef
    16 $Amount => undef
    17 $CardNumber => undef
    18 $MerchantContract => undef
    19
    20 </%args>
    21 <%init>
    22
    23 warn Dumper \%ARGS;
    24
    25 my $provider = payments::Provider::PayTure->new;
    26 my (@operations, $transaction);
    27 if ( $OrderId ) {
    28 @operations = $keeper->get_documents(
    29 class => 'payments::Operation',
    30 order_id => $OrderId,
    31 order_by => 'ctime',
    32 );
    33 ($transaction) = $keeper->get_documents (
    34 class => 'payments::Transaction',
    35 provider => $provider->payment_system,
    36 order_id => $OrderId,
    37 order_by => 'ctime desc',
    38 limit => 1,
    39 );
    40 }
    41 if ( ref $transaction ) {
    42 $transaction->name( $NAMES{$Notification} );
    43 $transaction->success( $Success eq 'True' ? 1 : 0 );
    44 $transaction->store;
    45
    46 if ( $keeper->can('_payture_handler') ) {
    47 $keeper->_payture_handler( $transaction );
    48 }
    49 }
    50
    51 </%init>
  • utf8/plugins/payments/lib/payments/Provider/PayTure.pm

     
    10 10 use Data::Dumper;
    11 11 use URI;
    12 12 use URI::QueryParam;
    13 use XML::Fast;
    13 14
    14 15 our $init_url = '';
    15 16
     
    26 27 $self->{currency} = $state->{payments}->{$prefix."_currency_code"};
    27 28 $self->{test_mode} = $state->{payments}->{$prefix."_test_mode"};
    28 29
    29 $self->{api}{init} = 'https://sandbox.payture.com/apim/Init';
    30 $self->{api} = $self->{test_mode} ?
    31 {
    32 init => 'https://sandbox.payture.com/apim/Init',
    33 pay => 'https://sandbox.payture.com/apim/Pay',
    34 status => 'https://sandbox.payture.com/apim/PayStatus',
    35 } : {
    36 };
    37 $self->{result} = {};
    30 38
    31 39 bless $self, $class;
    32 40
     
    43 51 my $session_type = $opts{session_type} || 'Pay';
    44 52
    45 53 my $order_id = $opts{order_id};
    46 return 0 unless $order_id;
    54 unless ( $order_id ) {
    55 $self->{result}{error} = 'Не указан order_id';
    56 return $self;
    57 }
    47 58
    59 my $uid = $opts{uid};
    60 unless ( $uid ) {
    61 $self->{result}{error} = 'Не указан user id';
    62 return $self;
    63 }
    64
    48 65 ### Сумма в копейках. Если дробное - преобразуем
    66 my $total;
    49 67 my $sum = $opts{sum};
    50 return 0 unless $sum;
    51 if ( $sum =~ /[\.\,]/ ) {
    52 $sum =~ s/\,/\./;
    53 $sum = int($sum * 100);
    68 if ( !$sum || $sum !~ /^[\d\,\.]+$/ ) {
    69 $self->{result}{error} = 'Не указана или неправильно указана сумма транзакции';
    70 return $self;
    54 71 }
    72 $sum =~ s/\,/\./;
    73 $total = sprintf("%.02f", $sum);
    74 $sum = int($sum * 100);
    55 75
    76 my $operation = $keeper->get_documents(
    77 class => 'payments::Operation',
    78 status => $state->{payments}->{payture_test_mode},
    79 order_id => $order_id,
    80 order_by => 'ctime',
    81 return_mode => 'array_ref',
    82 );
    83 if ( ref $operation eq 'ARRAY' && @$operation ) {
    84 my $last = $operation->[-1];
    85 if ( $last->name eq 'suspend' || $last->name eq 'cancel' ) {
    86 $self->{result}{error} = 'Заказ был отменен или заморожен';
    87 return $self;
    88 } else {
    89 $operation = $last;
    90 }
    91 } else {
    92 $operation = payments::Operation->new( $keeper );
    93 $operation->status( $state->{payments}->{payture_test_mode} );
    94 $operation->name( 'create' );
    95 $operation->order_id( $order_id );
    96 $operation->uid( $uid );
    97 $operation->sum( $sum );
    98 $operation->store;
    99 }
    100
    56 101 my $ip = $opts{ip};
    57 return 0 unless $ip && $ip =~ /(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})/;
    58 warn "IP: $ip\n";
    102 unless ( $ip && $ip =~ /(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})/ ) {
    103 $self->{result}{error} = 'Неверный IP-адрес';
    104 return $self;
    105 }
    106 warn "IP: $ip\n" if $DEBUG;
    59 107
    60 108 ### Пример: http://yoursite.com/result?orderid={orderid}&result={success}
    61 109 my $url = $opts{url};
     
    64 112 my $lang = $opts{lang};
    65 113 my $params = $opts{params};
    66 114
    67 my $ua = LWP::UserAgent->new;
    68 $ua->agent('Mozilla/5.0');
    69 $ua->ssl_opts( verify_hostname => 0, SSL_ca_path => '/usr/local/share/certs' );
    70 my $req = URI->new( $self->{api}{init} );
    71 # $req->query_param( Key => $self->id );
    72 my @data = ( "SessionType=$session_type", "OrderId=$order_id", "Amount=$sum", "IP=$ip");
    73 push @data, "Url=$url" if $url;
    74 push @data, "TemplateTag=$template" if $template;
    75 push @data, "Url=$url" if $url;
    76 push @data, "Language=$lang" if $lang;
    77 if ( ref $params eq 'HASH' ) {
    78 while ( my ($param, $val) = each %$params ) {
    79 push @data, "$param=$val";
    115 my ($transaction) = $keeper->get_documents(
    116 class => 'payments::Transaction',
    117 status => $state->{payments}->{payture_test_mode},
    118 limit => 1,
    119 order_id => $order_id,
    120 provider => $self->{payment_system},
    121 );
    122 if ( ref $transaction ) {
    123 ### Init already exists
    124 $self->{result}{success} = 1;
    125 $self->{result}{session_id} = $transaction->session_id;
    126 $self->{result}{transaction} = $transaction;
    127 } else {
    128 my $ua = LWP::UserAgent->new;
    129 my $is_https = $ua->is_protocol_supported( 'https' );
    130 warn "PayTure Init: protocol 'https' is $is_https\n" if $DEBUG;
    131 $ua->agent('Mozilla/5.0');
    132 $ua->ssl_opts( verify_hostname => 0, SSL_ca_path => '/usr/local/share/certs' );
    133 my $req = URI->new( $self->{api}{init} );
    134 my @data = ( "SessionType=$session_type", "OrderId=$order_id", "Amount=$sum", "IP=$ip");
    135 push @data, "Url=$url" if $url;
    136 push @data, "TemplateTag=$template" if $template;
    137 push @data, "Url=$url" if $url;
    138 push @data, "Language=$lang" if $lang;
    139 push @data, "Total=$total" unless exists $params->{Total};
    140 if ( ref $params eq 'HASH' ) {
    141 while ( my ($param, $val) = each %$params ) {
    142 push @data, "$param=$val";
    143 }
    80 144 }
    145 my $data_unescaped = join ';', @data;
    146 warn Dumper "PayTure Init data: $data_unescaped\n" if $DEBUG;
    147 my $data_str = URI::Escape::uri_escape( $data_unescaped );
    148 warn "PayTure Init query: ".Dumper($req) if $DEBUG;
    149
    150 my $result = $ua->post( $req, Content => { Key => $self->id, Data => $data_str } );
    151 my $return_data = {};
    152 if ( $result->code == 200 ) {
    153 warn "PayTure Init result: [".$result->content."]\n" if $DEBUG;
    154 my $content = xml2hash $result->content;
    155 warn Dumper $content if $DEBUG;
    156 if ( ref $content && exists $content->{'Init'} && exists $content->{'Init'}{'-Success'} ) {
    157 if ( $content->{'Init'}{'-Success'} eq 'True' ) {
    158 my $now = Contenido::DateTime->new;
    159 my $transaction = payments::Transaction->new( $keeper );
    160 $transaction->dtime( $now->ymd('-').' '.$now->hms );
    161 $transaction->provider( $self->{payment_system} );
    162 $transaction->session_id( $content->{'Init'}{'-SessionId'} );
    163 $transaction->status( $state->{payments}->{payture_test_mode} );
    164 $transaction->order_id( $order_id );
    165 $transaction->operation_id( $operation->id );
    166 $transaction->currency_code( 'RUR' );
    167 $transaction->sum( $sum );
    168 $transaction->name( 'Init' );
    169 $transaction->store;
    170
    171 $self->{result}{success} = 1;
    172 $self->{result}{session_id} = $content->{'Init'}{'-SessionId'};
    173 $self->{result}{transaction} = $transaction;
    174 } else {
    175 $self->{result}{error} = $content->{'Init'}{'-ErrCode'};
    176 }
    177 } else {
    178 $self->{result}{error} = 'PayTure Init failed';
    179 $self->{result}{responce} = $content;
    180 warn $self->{result}{error}."\n";
    181 warn "[$content]\n";
    182 }
    183 } else {
    184 $self->{result}{error} = 'PayTure Init failed';
    185 $self->{result}{responce} = $result->status_line;
    186 warn $self->{result}{error}.": ".$result->status_line."\n";
    187 warn Dumper $result;
    188 }
    81 189 }
    82 my $data_unescaped = join ';', @data;
    83 warn Dumper "PayTure Init data: $data_unescaped\n";
    84 my $data_str = URI::Escape::uri_escape( $data_unescaped );
    85 # $req->query_param( Data => $data_str );
    86 warn "PayTure Init query: ".Dumper($req) if $DEBUG;
    190 return $self;
    191 }
    87 192
    88 my $result = $ua->post( $req, Content => { Key => $self->id, Data => $data_str } );
    89 if ( $result->code == 200 ) {
    90 warn Dumper $result->content;
    91 return 1;
    193 sub pay {
    194 my $self = shift;
    195 my (%opts) = @_;
    196
    197 unless ( exists $self->{result} ) {
    198 $self->init( %opts );
    199 }
    200 if ( $self->{result}{success} ) {
    201 return $self->{api}{pay}.'?SessionId='.$self->{result}{session_id};
    202 }
    203 return undef;
    204 }
    205
    206 sub status {
    207 my $self = shift;
    208 my (%opts) = @_;
    209
    210 warn "PayTure Status: ".Dumper(\%opts) if $DEBUG;
    211
    212 my $order_id = $opts{order_id};
    213 unless ( $order_id ) {
    214 $self->{result} = { error => 'Не указан order_id' };
    215 return $self;
    216 }
    217
    218 my ($transaction) = exists $self->{result}{transaction} ? ($self->{result}{transaction}) : $keeper->get_documents(
    219 class => 'payments::Transaction',
    220 status => $state->{payments}->{payture_test_mode},
    221 limit => 1,
    222 order_id => $order_id,
    223 provider => $self->{payment_system},
    224 );
    225 if ( ref $transaction ) {
    226 my $ua = LWP::UserAgent->new;
    227 $ua->agent('Mozilla/5.0');
    228 my $req = URI->new( $self->{api}{status} );
    229 my $result = $ua->post( $req, Content => { Key => $self->id, OrderId => $order_id } );
    230 my $return_data = {};
    231 if ( $result->code == 200 ) {
    232 warn "PayTure Status result: [".$result->content."]\n" if $DEBUG;
    233 my $content = xml2hash $result->content;
    234 warn Dumper $content if $DEBUG;
    235 if ( ref $content && exists $content->{'PayStatus'} && exists $content->{'PayStatus'}{'-Success'} ) {
    236 if ( $content->{'PayStatus'}{'-Success'} eq 'True' ) {
    237 $self->{result} = {
    238 success => 1,
    239 status => $content->{'PayStatus'}{'-State'},
    240 amount => $content->{'PayStatus'}{'-Amount'},
    241 transaction => $transaction,
    242 };
    243 } else {
    244 $self->{result} = {
    245 error => $content->{'PayStatus'}{'-ErrCode'},
    246 transaction => $transaction,
    247 };
    248 }
    249 } else {
    250 $self->{result}{error} = 'PayTure Status failed';
    251 $self->{result}{responce} = $content;
    252 warn $self->{result}{error}."\n";
    253 warn "[$content]\n";
    254 }
    255 } else {
    256 $self->{result}{error} = 'PayTure Status failed';
    257 $self->{result}{responce} = $result->status_line;
    258 warn $self->{result}{error}.": ".$result->status_line."\n";
    259 warn Dumper $result;
    260 }
    92 261 } else {
    93 warn Dumper $result;
    94 warn "PayTure Init failed; ".$result->status_line."\n";
    262 $self->{result}{error} = "Не найдена транзакция для order_id=$order_id";
    263 return $self;
    95 264 }
    96 return 0;
    265
    266 return $self;
    97 267 }
    98 268
    99 269 1;
  • utf8/plugins/payments/lib/payments/SQL/TransactionsTable.pm

     
    26 26
    27 27 _operation_id_filter
    28 28 _order_id_filter
    29 _session_id_filter
    30 _success_filter
    29 31 _provider_filter
    32 _name_exact_filter
    30 33 );
    31 34
    32 35 return \@available_filters;
     
    66 69 'db_type' => 'numeric',
    67 70 'db_opts' => "default 0",
    68 71 },
    72 {
    73 'attr' => 'session_id',
    74 'type' => 'string',
    75 'rusname' => 'Ключ сессии',
    76 'db_field' => 'session_id',
    77 'db_type' => 'text',
    78 },
    69 79 { # ID заказа
    70 80 'attr' => 'order_id',
    71 81 'type' => 'integer',
     
    74 84 'db_type' => 'integer',
    75 85 'db_opts' => "not null",
    76 86 },
    77 { # ID заказа
    87 { # ID операции в таблице операций
    78 88 'attr' => 'operation_id',
    79 89 'type' => 'integer',
    80 90 'rusname' => 'ID транзакции',
     
    117 127 'db_field' => 'payment_system',
    118 128 'db_type' => 'text',
    119 129 },
    130 { # Результат транзакции
    131 'attr' => 'success',
    132 'type' => 'checkbox',
    133 'rusname' => 'Транзакция прошла успешно',
    134 'db_field' => 'success',
    135 'db_type' => 'smallint',
    136 'db_opts' => "default 1",
    137 },
    120 138 );
    121 139 }
    122 140
     
    134 152 return &SQL::Common::_generic_int_filter('d.order_id', $opts{order_id});
    135 153 }
    136 154
    155 sub _success_filter {
    156 my ($self,%opts)=@_;
    157 return undef unless ( exists $opts{success} );
    158 return &SQL::Common::_generic_int_filter('d.success', $opts{success});
    159 }
    160
    137 161 sub _provider_filter {
    138 162 my ($self,%opts)=@_;
    139 163 return undef unless ( exists $opts{provider} );
    140 164 return &SQL::Common::_generic_text_filter('d.provider', $opts{provider});
    141 165 }
    142 166
    167 sub _session_id_filter {
    168 my ($self,%opts)=@_;
    169 return undef unless ( exists $opts{session_id} );
    170 return &SQL::Common::_generic_int_filter('d.session_id', $opts{session_id});
    171 }
    172
    173 sub _name_exact_filter {
    174 my ($self,%opts)=@_;
    175 return undef unless ( exists $opts{name_exact} );
    176 return &SQL::Common::_generic_text_filter('d.name', $opts{name_exact});
    177 }
    178
    143 179 1;
  • utf8/plugins/payments/sql/TOAST/transactions.552.sql

     
    1 ALTER TABLE payments_transactions ADD COLUMN session_id text;
    2 ALTER TABLE payments_transactions ADD COLUMN success smallint default 1;
    3 CREATE INDEX payments_transactions_sessions ON payments_transactions USING btree (provider, session_id) WHERE session_id is not null;
  • utf8/plugins/payments/sql/TOAST/transactions.sql

     
    9 9 provider text,
    10 10 name text,
    11 11 account_id numeric,
    12 session_id text,
    12 13 order_id integer not null,
    13 14 operation_id numeric not null,
    14 15 currency_code varchar(4),
     
    16 17 account_user text,
    17 18 payment_system text,
    18 19 account_corr text,
    20 success smallint default 1,
    19 21 data text
    20 22 );
    21 23 CREATE INDEX payments_transactions_operations ON payments_transactions USING btree (provider, operation_id);
    24 CREATE INDEX payments_transactions_sessions ON payments_transactions USING btree (provider, session_id) WHERE session_id is not null;
    22 25 CREATE INDEX payments_transactions_orders ON payments_transactions USING btree (order_id);

Небольшая справка по веткам

cnddist – контейнер, в котором хранятся все дистрибутивы всех библиотек и программных пакетов, которые использовались при построении различных версий Contenido. Если какой-то библиотеки в данном хранилище нет, инсталлятор сделает попытку "подтянуть" ее с веба (например, с CPAN). Если библиотека слишком старая, есть очень большая вероятность, что ее там уже нет. Поэтому мы храним весь хлам от всех сборок. Если какой-то дистрибутив вдруг отсутствует в cnddist - напишите нам, мы положим его туда.

koi8 – отмирающая ветка, чей код, выдача и все внутренние библиотеки заточены на кодировку KOI8-R. Вносятся только те дополнения, которые касаются внешнего вида и функционала админки, баги ядра, обязательные обновления портов и мелочи, которые легко скопипастить. В дальнейшем планируется полная остановка поддержки по данной ветке.

utf8 – актуальная ветка, заточенная под UTF-8.

Внутри каждой ветки: core – исходники ядра; install – скрипт установки инсталляции; plugins – плагины; samples – "готовые к употреблению" проекты, которые можно поставить, запустить и посмотреть, как они работают.