Revision 690
- Date:
- 2018/07/30 10:05:12
- Files:
-
- /utf8/plugins/payments/lib/payments/Init.pm (Diff) (Checkout)
- /utf8/plugins/payments/lib/payments/Provider/Base.pm (Diff) (Checkout)
- /utf8/plugins/payments/lib/payments/Provider/Sber.pm (Diff) (Checkout)
- /utf8/plugins/payments/lib/payments/SQL/TransactionsTable.pm (Diff) (Checkout)
- /utf8/plugins/payments/lib/payments/State.pm.proto (Diff) (Checkout)
- /utf8/plugins/payments/lib/payments/Transaction.pm (Diff) (Checkout)
Legend:
- Added
- Removed
- Modified
-
utf8/plugins/payments/lib/payments/Init.pm
25 25 payments::CardSection 26 26 27 27 payments::Provider::Base 28 payments::Provider::Moneta 29 payments::Provider::Xsolla 30 28 payments::Provider::PayTure 29 payments::Provider::Sber 31 30 )); 32 31 33 32 sub init { -
utf8/plugins/payments/lib/payments/Provider/Base.pm
60 60 return $self->{payment_system}; 61 61 } 62 62 63 ################################# 64 # Пытается зарегистрировать операцию по order_id. 65 # В случае успеха возвращает объект payments::Operation 66 # В случае неуспеха выставляет ошибку и возвращает undef. 67 # Сумма заказа в копейках 68 ########################################################## 69 sub payment_operation_register { 70 my $self = shift; 71 my $opts = shift // {}; 72 unless ( $opts->{order_id} && $opts->{uid} && $opts->{sum} && $opts->{name} ) { 73 $self->{result}{error} = 'Переданы не все обязательные параметры'; 74 return undef; 75 } 76 77 my $operation = $keeper->get_documents( 78 class => 'payments::Operation', 79 status => $self->{test_mode}, 80 order_id => $opts->{order_id}, 81 order_by => 'ctime', 82 return_mode => 'array_ref', 83 ); 84 my $new = 0; 85 if ( ref $operation eq 'ARRAY' && @$operation ) { 86 my $last = $operation->[-1]; 87 if ( $opts->{name} eq 'create' && ($last->name eq 'suspend' || $last->name eq 'cancel' || $last->name eq 'close') ) { 88 $self->{result}{error} = 'Заказ закрыт, отменен или заморожен. Оплата по нему невозможна'; 89 return undef; 90 } elsif ( $opts->{name} eq 'refund' && ($last->name eq 'suspend' || $last->name eq 'close') ) { 91 $self->{result}{error} = 'Заказ закрыт или заморожен. Возврат средств по нему невозможен'; 92 return undef; 93 } elsif ( $last->name eq $opts->{name} ) { 94 $operation = $last; 95 } else { 96 if ( $opts->{name} eq 'refund' && (grep { $_->name eq 'create' } @$operation) ) { 97 $new = 1; 98 } 99 } 100 } elsif ( $opts->{name} eq 'create' ) { 101 $new = 1; 102 } 103 if ( $new ) { 104 $operation = payments::Operation->new( $keeper ); 105 $operation->status( $self->{test_mode} ); 106 $operation->name( $opts->{name} ); 107 $operation->order_id( $opts->{order_id} ); 108 $operation->uid( $opts->{uid} ); 109 $operation->sum( $opts->{sum} ); 110 $operation->store; 111 } 112 return $operation; 113 } 114 115 sub get_transaction_by_order_id { 116 my $self = shift; 117 my $order_id = shift; 118 119 my ($transaction) = $keeper->get_documents( 120 class => 'payments::Transaction', 121 status => $self->{test_mode}, 122 limit => 1, 123 order_id => $order_id, 124 order_by => 'ctime desc', 125 provider => $self->{payment_system}, 126 ); 127 128 return $transaction; 129 } 130 63 131 1; -
utf8/plugins/payments/lib/payments/Provider/Sber.pm
1 package payments::Provider::Sber; 2 3 use strict; 4 use warnings 'all'; 5 6 use base 'payments::Provider::Base'; 7 use Contenido::Globals; 8 use payments::Keeper; 9 use URI; 10 use URI::QueryParam; 11 use JSON::XS; 12 use Data::Dumper; 13 14 sub new { 15 my ($proto, %params) = @_; 16 my $class = ref($proto) || $proto; 17 my $self = {}; 18 my $prefix = $class =~ /\:\:(\w+)$/ ? lc($1) : undef; 19 return unless $prefix; 20 21 $self->{payment_system} = $prefix; 22 $self->{app_id} = $state->{payments}{$prefix."_app_id"}; 23 $self->{secret} = $state->{payments}{$prefix."_app_secret"}; 24 $self->{token} = $state->{payments}{$prefix."_app_token"}; 25 $self->{test_mode} = exists $params{test_mode} ? $params{test_mode} : $state->{payments}->{$prefix."_test_mode"}; 26 $self->{return_url} = $params{return_url} || $state->{payments}{$prefix."_return_url"}; 27 $self->{fail_url} = $params{fail_url} || $state->{payments}{$prefix."_fail_url"}; 28 29 $self->{currency} = $state->{payments}{$prefix."_currency_code"}; 30 31 my $host = 'https://'. ($self->{test_mode} ? '3dsec.sberbank.ru' : 'securepayments.sberbank.ru'); 32 $self->{api} = { 33 init => "$host/payment/rest/register.do", # Регистрация заказа 34 pay => "$host/payment/rest/deposit.do", # Запрос завершения оплаты заказа 35 cancel => "$host/payment/rest/reverse.do", # Запрос отмены оплаты заказа 36 refund => "$host/payment/rest/refund.do", # Запрос возврата средств оплаты заказа 37 status => "$host/payment/rest/getOrderStatusExtended.do", # Получение статуса заказа 38 is3ds => "$host/payment/rest/verifyEnrollment.do", # Запрос проверки вовлеченности карты в 3DS 39 }; 40 $self->{return_url} = 41 42 $self->{result} = {}; 43 44 bless $self, $class; 45 46 return $self; 47 } 48 49 50 ############################################################################################################ 51 # Одностадийные операции 52 ############################################################################################################ 53 54 =for rem INIT 55 # Регистрация заказа 56 57 $payment->init({ 58 # обязательные: 59 uid => User ID 60 orderNumber => ID заказа 61 amount => Сумма платежа в копейках или в формате 0.00 62 # необязательные: 63 returnUrl => Адрес, на который требуется перенаправить пользователя в случае успешной оплаты. 64 Если не прописан в config.mk, параметр ОБЯЗАТЕЛЬНЫЙ 65 failUrl => Адрес, на который требуется перенаправить пользователя в случае неуспешной оплаты. 66 description => Описание заказа в свободной форме. В процессинг банка для включения в финансовую 67 отчётность продавца передаются только первые 24 символа этого поля 68 language => Язык в кодировке ISO 639-1 69 pageView => DESKTOP || MOBILE (см. доку) 70 jsonParams => { хеш дополнительныех параметров } 71 sessionTimeoutSecs => Продолжительность жизни заказа в секундах. 72 expirationDate => Дата и время окончания жизни заказа. Формат: yyyy-MM-ddTHH:mm:ss. 73 }); 74 =cut 75 ########################################################## 76 sub init { 77 my $self = shift; 78 my $opts = shift // {}; 79 80 unless ( %$opts && exists $opts->{orderNumber} && exists $opts->{amount} ) { 81 $self->{result}{error} = 'Не указаны обязательные параметры: orderNumber или amount'; 82 return $self; 83 } 84 my $method = 'init'; 85 if ( !exists $opts->{returnUrl} ) { 86 if ( $self->{return_url} ) { 87 $opts->{returnUrl} = $self->{return_url}; 88 } else { 89 $self->{result}{error} = 'Не указан параметр returnUrl и не заполнено значение по умолчанию в конфиге SBER_RETURN_URL'; 90 return $self; 91 } 92 } 93 if ( !exists $opts->{failUrl} && $self->{fail_url} ) { 94 $opts->{failUrl} = $self->{fail_url}; 95 } 96 97 my $uid = delete $opts->{uid}; 98 unless ( $uid ) { 99 $self->{result}{error} = 'Не указан user id'; 100 return $self; 101 } 102 103 ### Сумма должна быть в копейках. Если дробное (рубли.копейки) - преобразуем в копейки 104 my $sum = $opts->{amount}; 105 if ( !$sum || $sum !~ /^[\d\,\.]+$/ ) { 106 $self->{result}{error} = 'Не указана или неправильно указана сумма транзакции'; 107 return $self; 108 } 109 if ( $sum =~ /[,.]/ ) { 110 $sum =~ s/\,/\./; 111 $opts->{amount} = int($sum * 100); 112 } 113 $opts->{jsonParams} = {} unless exists $opts->{jsonParams}; 114 $opts->{jsonParams}{uid} = $uid; 115 116 warn "Sberbank init args: ".Dumper($opts) if $DEBUG; 117 my $operation = $self->payment_operation_register( 118 order_id => $opts->{orderNumber}, 119 name => 'create', 120 uid => $uid, 121 sum => $opts->{amount}, 122 ); 123 return $self unless ref $operation; 124 125 my $transaction = $self->get_transaction_by_order_id( $opts->{orderNumber} ); 126 if ( ref $transaction ) { 127 ### Transaction already exists 128 $self->{result}{success} = 1; 129 $self->{result}{session_id} = $transaction->session_id; 130 $self->{result}{transaction} = $transaction; 131 } else { 132 my $req = $self->_createRequestGet( $method, $opts ); 133 my $ua = LWP::UserAgent->new; 134 $ua->agent('Mozilla/5.0'); 135 my $result = $ua->get( $req ); 136 if ( $result->code == 200 ) { 137 warn "Sberbank Init result: [".$result->decoded_content."]\n" if $DEBUG; 138 my $content = decode_json $result->decoded_content; 139 warn Dumper $content if $DEBUG; 140 141 if ( ref $content && exists $content->{orderId} ) { 142 my $now = Contenido::DateTime->new; 143 my $transaction = payments::Transaction->new( $keeper ); 144 $transaction->dtime( $now->ymd('-').' '.$now->hms ); 145 $transaction->provider( $self->{payment_system} ); 146 $transaction->session_id( $content->{orderId} ); 147 $transaction->status( $self->{test_mode} ); 148 $transaction->order_id( $opts->{orderNumber} ); 149 $transaction->operation_id( $operation->id ); 150 $transaction->currency_code( 'RUR' ); 151 $transaction->sum( $opts->{amount} ); 152 $transaction->form_url( $content->{formUrl} ); 153 $transaction->name( 'Init' ); 154 $transaction->success( 0 ); 155 $transaction->store; 156 157 $self->{result}{success} = 1; 158 $self->{result}{session_id} = $content->{orderId}; 159 $self->{result}{transaction} = $transaction; 160 } elsif ( ref $content && exists $content->{errorCode} && $content->{errorCode} ) { 161 $self->{result}{error} = $content->{errorMessage}; 162 warn "[$content]\n"; 163 } else { 164 $self->{result}{error} = 'Sberbank Init failed'; 165 $self->{result}{responce} = $content; 166 warn $self->{result}{error}."\n"; 167 warn "[$content]\n"; 168 } 169 } else { 170 $self->{result}{error} = 'PayTure Init failed'; 171 $self->{result}{responce} = $result->status_line; 172 warn $self->{result}{error}.": ".$result->status_line."\n"; 173 warn Dumper $result; 174 } 175 } 176 return $self; 177 } 178 179 180 =for rem STATUS 181 # Расширенный запрос состояния заказа 182 183 $payment->status({ 184 # обязательные: 185 orderNumber => ID заказа в магазине. Если в объекте присутствует транзакция, будет браться из транзакции 186 # необязательные: 187 language => Язык в кодировке ISO 639-1 188 }); 189 190 Результат: 191 192 orderStatus: 193 194 По значению этого параметра определяется состояние заказа в платёжной системе. Список возможных значений приведён в списке 195 ниже. Отсутствует, если заказ не был найден. 196 0 - Заказ зарегистрирован, но не оплачен; 197 1 - Предавторизованная сумма захолдирована (для двухстадийных платежей); 198 2 - Проведена полная авторизация суммы заказа; 199 3 - Авторизация отменена; 200 4 - По транзакции была проведена операция возврата; 201 5 - Инициирована авторизация через ACS банка-эмитента; 202 6 - Авторизация отклонена. 203 204 errorCode: 205 206 Код ошибки. Возможны следующие варианты. 207 0 - Обработка запроса прошла без системных ошибок; 208 1 - Ожидается [orderId] или [orderNumber]; 209 5 - Доступ запрещён; 210 5 - Пользователь должен сменить свой пароль; 211 6 - Заказ не найден; 212 7 - Системная ошибка. 213 214 =cut 215 ########################################################## 216 sub status { 217 my $self = shift; 218 my $opts = shift // {}; 219 220 unless ( %$opts && (exists $opts->{orderNumber} || exists $self->{result} && exists $self->{result}{transaction} && ref $self->{result}{transaction}) ) { 221 $self->{result}{error} = 'Не указан обязательный параметр orderNumber или не получена транзакция'; 222 return $self; 223 } 224 my $method = 'status'; 225 my $transaction; 226 if ( exists $self->{result}{transaction} ) { 227 $transaction = $self->{result}{transaction}; 228 $opts->{orderNumber} = $transaction->order_id; 229 } else { 230 $transaction = $self->get_transaction_by_order_id( $opts->{orderNumber} ); 231 } 232 unless ( ref $transaction ) { 233 $self->{result}{error} = "Не найдена транзакция для order_id=".$opts->{orderNumber}; 234 return $self; 235 } 236 $opts->{orderId} = $transaction->session_id; 237 warn "Sberbank Status: ".Dumper($opts) if $DEBUG; 238 239 my $req = $self->_createRequestGet( $method, $opts ); 240 my $ua = LWP::UserAgent->new; 241 $ua->agent('Mozilla/5.0'); 242 my $result = $ua->get( $req ); 243 my $return_data = {}; 244 if ( $result->code == 200 ) { 245 warn "Sberbank Status result: [".$result->content."]\n" if $DEBUG; 246 my $content = decode_json $result->decoded_content; 247 warn Dumper $content if $DEBUG; 248 249 if ( ref $content && exists $content->{orderStatus} && exists $content->{orderId} ) { 250 $self->{result} = { 251 success => 1, 252 status => $content->{orderStatus}, 253 action => $content->{actionCode}, 254 action_description => $content->{actionCodeDescription}, 255 amount => $content->{amount}, 256 time_ms => $content->{date}, 257 ip => $content->{ip}, 258 transaction => $transaction, 259 }; 260 } elsif ( ref $content && exists $content->{errorCode} && $content->{errorCode} ) { 261 $self->{result}{error} = $content->{errorMessage}; 262 warn "[$content]\n"; 263 } else { 264 $self->{result}{error} = 'Sberbank Status failed'; 265 $self->{result}{responce} = $content; 266 warn $self->{result}{error}."\n"; 267 warn "[$content]\n"; 268 } 269 } else { 270 $self->{result}{error} = 'Sberbank Status failed'; 271 $self->{result}{responce} = $result->status_line; 272 warn $self->{result}{error}.": ".$result->status_line."\n"; 273 warn Dumper $result; 274 } 275 276 return $self; 277 } 278 279 280 =for rem REFUND 281 # Возврат средств 282 283 $payment->refund({ 284 # обязательные: 285 uid => User ID 286 orderNumber => ID заказа 287 # orderId => Номер заказа в платежной системе. Уникален в пределах системы (session_id). 288 # Если в объекте присутствует транзакция, будет браться из транзакции 289 amount => Сумма платежа в копейках или в формате 0.00 290 }); 291 =cut 292 ########################################################## 293 sub refund { 294 my $self = shift; 295 my $opts = shift // {}; 296 297 unless ( %$opts && exists $opts->{orderNumber} && exists $opts->{amount} ) { 298 $self->{result}{error} = 'Не указаны обязательные параметры: orderNumber или amount'; 299 return $self; 300 } 301 my $method = 'refund'; 302 303 my $uid = delete $opts->{uid}; 304 unless ( $uid ) { 305 $self->{result}{error} = 'Не указан user id'; 306 return $self; 307 } 308 309 ### Сумма должна быть в копейках. Если дробное (рубли.копейки) - преобразуем в копейки 310 my $sum = $opts->{amount}; 311 if ( !$sum || $sum !~ /^[\d\,\.]+$/ ) { 312 $self->{result}{error} = 'Не указана или неправильно указана сумма транзакции'; 313 return $self; 314 } 315 if ( $sum =~ /[,.]/ ) { 316 $sum =~ s/\,/\./; 317 $opts->{amount} = int($sum * 100); 318 } 319 320 warn "Sberbank refund args: ".Dumper($opts) if $DEBUG; 321 my $operation = $self->payment_operation_register( 322 order_id => $opts->{orderNumber}, 323 name => 'refund', 324 uid => $uid, 325 sum => $opts->{amount}, 326 ); 327 return $self unless ref $operation; 328 329 my $transaction = $self->get_transaction_by_order_id( $opts->{orderNumber} ); 330 if ( ref $transaction && $transaction->name eq 'Charge' ) { 331 $opts->{orderId} = $transaction->session_id; 332 my $order_id = delete $opts->{orderNumber}; 333 my $req = $self->_createRequestGet( $method, $opts ); 334 my $ua = LWP::UserAgent->new; 335 $ua->agent('Mozilla/5.0'); 336 my $result = $ua->get( $req ); 337 if ( $result->code == 200 ) { 338 warn "Sberbank Refund result: [".$result->decoded_content."]\n" if $DEBUG; 339 my $content = decode_json $result->decoded_content; 340 warn Dumper $content if $DEBUG; 341 342 if ( ref $content && exists $content->{orderId} ) { 343 my $now = Contenido::DateTime->new; 344 my $transaction = payments::Transaction->new( $keeper ); 345 $transaction->dtime( $now->ymd('-').' '.$now->hms ); 346 $transaction->provider( $self->{payment_system} ); 347 $transaction->session_id( $opts->{orderId} ); 348 $transaction->status( $self->{test_mode} ); 349 $transaction->order_id( $order_id ); 350 $transaction->operation_id( $operation->id ); 351 $transaction->currency_code( 'RUR' ); 352 $transaction->sum( $opts->{amount} ); 353 $transaction->name( 'Refund' ); 354 $transaction->success( 0 ); 355 $transaction->store; 356 357 $self->{result}{success} = 1; 358 $self->{result}{session_id} = $content->{orderId}; 359 $self->{result}{transaction} = $transaction; 360 } elsif ( ref $content && exists $content->{errorCode} && $content->{errorCode} ) { 361 $self->{result}{error} = $content->{errorMessage}; 362 warn "[$content]\n"; 363 } else { 364 $self->{result}{error} = 'Sberbank Refund failed'; 365 $self->{result}{responce} = $content; 366 warn $self->{result}{error}."\n"; 367 warn "[$content]\n"; 368 } 369 } else { 370 $self->{result}{error} = 'PayTure Init failed'; 371 $self->{result}{responce} = $result->status_line; 372 warn $self->{result}{error}.": ".$result->status_line."\n"; 373 warn Dumper $result; 374 } 375 } 376 return $self; 377 } 378 379 380 381 382 sub _createRequestGet { 383 my ($self, $method, $opts) = @_; 384 return unless $method && exists $self->{api}{$method}; 385 $opts //= {}; 386 387 my $req = URI->new( $self->{api}{$method} ); 388 if ( $self->{token} ) { 389 $req->query_param( token => $self->{token} ); 390 } else { 391 $req->query_param( userName => $self->{app_id} ); 392 $req->query_param( password => $self->{secret} ); 393 } 394 foreach my $key ( keys %$opts ) { 395 if ( $key eq 'jsonParams' && ref $opts->{$key} ) { 396 $opts->{$key} = encode_json $opts->{$key}; 397 } 398 $req->query_param( $key => $opts->{$key} ); 399 } 400 return $req; 401 } 402 403 1; -
utf8/plugins/payments/lib/payments/SQL/TransactionsTable.pm
87 87 { # ID операции в таблице операций 88 88 'attr' => 'operation_id', 89 89 'type' => 'integer', 90 'rusname' => 'ID транзакции', 90 'rusname' => 'ID операции', 91 91 'db_field' => 'operation_id', 92 92 'db_type' => 'numeric', 93 93 'db_opts' => "not null", -
utf8/plugins/payments/lib/payments/State.pm.proto
79 79 $self->{payture_test_mode} = int('@PTR_TEST_MODE@' || 0); 80 80 $self->{payture_sig_code} = '@PTR_SIG_CODE@'; 81 81 82 $self->{sber_app_id} = '@SBER_LOGIN@'; 83 $self->{sber_app_secret} = '@SBER_PASSWORD@'; 84 $self->{sber_app_token} = '@SBER_TOKEN@'; 85 $self->{sber_return_url} = '@SBER_RETURN_URL@'; 86 $self->{sber_fail_url} = '@SBER_FAIL_URL@'; 87 $self->{sber_test_mode} = int('@SBER_TEST_MODE@' || 0); 88 $self->{sber_currency_code} = int('@SBER_CURRENCY_CODE@' || 643); 89 82 90 $self->_init_(); 83 91 $self; 84 92 } -
utf8/plugins/payments/lib/payments/Transaction.pm
10 10 [1, 'Тестовая оплата'], 11 11 ], 12 12 }, 13 { 'attr' => 'form_url', 'type' => 'string', 'rusname' => 'URL формы оплаты' }, 13 14 { 'attr' => 'custom1', 'type' => 'string', 'rusname' => 'Параметр 1' }, 14 15 { 'attr' => 'custom2', 'type' => 'string', 'rusname' => 'Параметр 2' }, 15 16 { 'attr' => 'custom3', 'type' => 'string', 'rusname' => 'Параметр 3' },