ITS-28: update module

This commit is contained in:
avcherkasov 2017-06-01 17:01:17 +03:00
parent 81b0bf3634
commit 49556b8edc
4 changed files with 218 additions and 141 deletions

View File

@ -2,7 +2,7 @@
Платежный плагин RBKmoney для Wordpress + WP e-Commerce
Проверено на Wordpress 4.3.2, WP e-Commerce 3.12.0
Проверено на `Wordpress 4.3.2`, `WP e-Commerce 3.12.0`
### Установка и настройка модуля

View File

@ -31,16 +31,27 @@ function gateway_rbkmoney_payment($separator, $sessionid)
// e.g. RUB
$currency = WPSC_Countries::get_currency_code(get_option('currency_type'));
$form_company_name = '';
if(!empty(trim(get_option('rbkmoney_payment_form_company_name')))) {
$form_company_name = 'data-name="' . trim(get_option('rbkmoney_payment_form_company_name')).'"';
$company_name = '';
if (!empty(trim(get_option('rbkmoney_payment_form_company_name')))) {
$company_name = 'data-name="' . trim(get_option('rbkmoney_payment_form_company_name')) . '"';
}
$form_path_logo = '';
if(!empty(trim(get_option('rbkmoney_payment_form_company_name')))) {
$form_path_logo = 'data-logo="' . trim(get_option('rbkmoney_payment_form_path_logo')).'"';
$company_logo = '';
if (!empty(trim(get_option('rbkmoney_payment_form_company_name')))) {
$company_logo = 'data-logo="' . trim(get_option('rbkmoney_payment_form_path_logo')) . '"';
}
$button_label = '';
if (!empty(trim(get_option('rbkmoney_payment_form_button_label')))) {
$button_label = 'data-label="' . trim(get_option('rbkmoney_payment_form_button_label')) . '"';
}
$description = '';
if (!empty(trim(get_option('rbkmoney_payment_form_description')))) {
$description = 'data-description="' . trim(get_option('rbkmoney_payment_form_description')) . '"';
}
$amount = number_format($purchase_log[0]['totalprice'], 2, '', '');
$params = array(
'shop_id' => trim(get_option('rbkmoney_payment_shop_id')),
@ -62,17 +73,22 @@ function gateway_rbkmoney_payment($separator, $sessionid)
exit();
}
$output = '<html><body><script src="https://checkout.rbk.money/payframe/payframe.js" class="rbkmoney-checkout"
data-invoice-id="' . $invoice_id . '"
data-invoice-access-token="' . $invoice_access_token . '"
data-endpoint-success="' . home_url('/?rbkmoney_payment_results') . '"
data-endpoint-failed="' . home_url('/?rbkmoney_payment_results') . '"
data-amount="' . $purchase_log[0]['totalprice'] . '"
data-currency="' . $currency . '"
' . $form_company_name . '
' . $form_path_logo . '
>
</script></body></html>';
$output = '<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
</head>
<body>
<form action="' . home_url('/?rbkmoney_payment_results') . '" method="' . HTTP_METHOD_POST . '">
<script src="https://checkout.rbk.money/checkout.js" class="rbkmoney-checkout"
data-invoice-id="' . $invoice_id . '"
data-invoice-access-token="' . $invoice_access_token . '"
' . $company_name . '
' . $company_logo . '
' . $button_label . '
' . $description . '
>
</script>
</form></body></html>';
echo $output;
exit();
@ -88,113 +104,137 @@ function nzshpcrt_rbkmoney_payment_callback()
if (isset($_GET['rbkmoney_payment_callback'])) {
if(empty($_SERVER[RBKmoneyPayment::SIGNATURE])) {
if (empty($_SERVER[RBKmoneyPaymentVerification::SIGNATURE])) {
_rbkmoney_payment_response_with_code_and_message(
RBKmoneyPayment::HTTP_CODE_BAD_REQUEST,
'Отсутствует сигнатура'
'Webhook notification signature missing'
);
}
$params_signature = RBKmoneyPaymentVerification::get_parameters_content_signature($_SERVER[RBKmoneyPaymentVerification::SIGNATURE]);
if (empty($params_signature[RBKmoneyPaymentVerification::SIGNATURE_ALG])) {
_rbkmoney_payment_response_with_code_and_message(
RBKmoneyPayment::HTTP_CODE_BAD_REQUEST,
'Missing required parameter ' . RBKmoneyPaymentVerification::SIGNATURE_ALG
);
exit();
}
if (empty($params_signature[RBKmoneyPaymentVerification::SIGNATURE_DIGEST])) {
_rbkmoney_payment_response_with_code_and_message(
RBKmoneyPayment::HTTP_CODE_BAD_REQUEST,
'Missing required parameter ' . RBKmoneyPaymentVerification::SIGNATURE_DIGEST
);
exit();
}
$content = file_get_contents('php://input');
$required_fields = [
RBKmoneyPayment::SHOP_ID,
RBKmoneyPayment::INVOICE_ID,
RBKmoneyPayment::PAYMENT_ID,
RBKmoneyPayment::AMOUNT,
RBKmoneyPayment::CURRENCY,
RBKmoneyPayment::CREATED_AT,
RBKmoneyPayment::METADATA,
RBKmoneyPayment::STATUS,
RBKmoneyPayment::EVENT_TYPE,
];
$data = json_decode($content, TRUE);
foreach ($required_fields as $field) {
if (empty($data[$field])) {
http_response_code(RBKmoneyPayment::HTTP_CODE_BAD_REQUEST);
exit();
}
}
$current_shop_id = trim(get_option('rbkmoney_payment_shop_id'));
if($current_shop_id != $data[RBKmoneyPayment::SHOP_ID]) {
_rbkmoney_payment_response_with_code_and_message(
RBKmoneyPayment::HTTP_CODE_BAD_REQUEST,
RBKmoneyPayment::SHOP_ID . ' не совпадает с указанным в настройках магазина'
);
}
if (empty($data[RBKmoneyPayment::METADATA][RBKmoneyPayment::ORDER_ID])) {
_rbkmoney_payment_response_with_code_and_message(
RBKmoneyPayment::HTTP_CODE_BAD_REQUEST,
RBKmoneyPayment::ORDER_ID . ' не обнаружен в ' . RBKmoneyPayment::METADATA
);
}
if (empty($data[RBKmoneyPayment::METADATA][RBKmoneyPayment::SESSION_ID])) {
_rbkmoney_payment_response_with_code_and_message(
RBKmoneyPayment::HTTP_CODE_BAD_REQUEST,
RBKmoneyPayment::SESSION_ID . ' не обнаружен в ' . RBKmoneyPayment::METADATA
);
}
$signature = base64_decode($_SERVER[RBKmoneyPayment::SIGNATURE], TRUE);
$signature = RBKmoneyPaymentVerification::urlsafe_b64decode($params_signature[RBKmoneyPaymentVerification::SIGNATURE_DIGEST]);
if (!RBKmoneyPaymentVerification::verification_signature($content, $signature, trim(get_option('rbkmoney_payment_callback_public_key')))) {
_rbkmoney_payment_response_with_code_and_message(
RBKmoneyPayment::HTTP_CODE_BAD_REQUEST,
'Сигнатура не совпадает'
'Webhook notification signature mismatch'
);
}
$purchase_log_sql = $wpdb->prepare( "SELECT * FROM `".WPSC_TABLE_PURCHASE_LOGS."` WHERE `id`= %s AND `sessionid`= %s LIMIT 1",
$data[RBKmoneyPayment::METADATA][RBKmoneyPayment::ORDER_ID],
$data[RBKmoneyPayment::METADATA][RBKmoneyPayment::SESSION_ID]
);
$purchase_log = $wpdb->get_results($purchase_log_sql, ARRAY_A);
if(empty($purchase_log)) {
$invoice = 'invoice';
$eventType = 'eventType';
$required_fields = [$invoice, $eventType];
$data = json_decode($content, TRUE);
foreach ($required_fields as $field) {
if (empty($data[$field])) {
_rbkmoney_payment_response_with_code_and_message(
RBKmoneyPayment::HTTP_CODE_BAD_REQUEST,
'One or more required fields are missing'
);
}
}
$current_shop_id = (int)trim(get_option('rbkmoney_payment_shop_id'));
if ($current_shop_id != $data[$invoice][RBKmoneyPayment::SHOP_ID]) {
_rbkmoney_payment_response_with_code_and_message(
RBKmoneyPayment::HTTP_CODE_BAD_REQUEST,
RBKmoneyPayment::SESSION_ID . ' не найдена в базе данных'
RBKmoneyPayment::SHOP_ID . ' is missing'
);
}
$all_statuses = array(
'1' => 'pending',
'2' => 'completed',
'3' => 'ok',
'4' => 'processed',
'5' => 'closed',
'6' => 'rejected',
if (empty($data[$invoice][RBKmoneyPayment::METADATA][RBKmoneyPayment::ORDER_ID])) {
_rbkmoney_payment_response_with_code_and_message(
RBKmoneyPayment::HTTP_CODE_BAD_REQUEST,
RBKmoneyPayment::ORDER_ID . ' is missing'
);
}
if (empty($data[$invoice][RBKmoneyPayment::METADATA][RBKmoneyPayment::SESSION_ID])) {
_rbkmoney_payment_response_with_code_and_message(
RBKmoneyPayment::HTTP_CODE_BAD_REQUEST,
RBKmoneyPayment::SESSION_ID . ' is missing'
);
}
$purchase_log_sql = $wpdb->prepare("SELECT * FROM `" . WPSC_TABLE_PURCHASE_LOGS . "` WHERE `id`= %s AND `sessionid`= %s LIMIT 1",
$data[$invoice][RBKmoneyPayment::METADATA][RBKmoneyPayment::ORDER_ID],
$data[$invoice][RBKmoneyPayment::METADATA][RBKmoneyPayment::SESSION_ID]
);
if(in_array($purchase_log[0]['processed'], array_search('pending', $all_statuses))) {
$processed = array_search('pending', $all_statuses);
$details = array(
'processed' => $processed,
'transactid' => $data[RBKmoneyPayment::INVOICE_ID],
'date' => time(),
$purchase_log = $wpdb->get_results($purchase_log_sql, ARRAY_A);
if (empty($purchase_log)) {
_rbkmoney_payment_response_with_code_and_message(
RBKmoneyPayment::HTTP_CODE_BAD_REQUEST,
'Purchase not found ' . $purchase_log_sql
);
$session_id = $data[RBKmoneyPayment::METADATA][RBKmoneyPayment::SESSION_ID];
switch ($data[RBKmoneyPayment::STATUS]) {
case 'paid':
$details['processed'] = array_search('ok', $all_statuses);
wpsc_update_purchase_log_details( $session_id, $details, 'sessionid' );
transaction_results($session_id, false, $data[RBKmoneyPayment::INVOICE_ID]);
break;
case 'cancelled':
$details['processed'] = array_search('closed', $all_statuses);
wpsc_update_purchase_log_details( $session_id, $details, 'sessionid' );
transaction_results($session_id, false, $data[RBKmoneyPayment::INVOICE_ID]);
break;
default:
// The default action is not needed if a status has come that does not interest us
break;
}
}
$order_amount = number_format($purchase_log[0]['totalprice'], 2, '', '');
$invoice_amount = $data[$invoice]['amount'];
if ($order_amount != $invoice_amount) {
_rbkmoney_payment_response_with_code_and_message(
RBKmoneyPayment::HTTP_CODE_BAD_REQUEST,
'Received amount vs Order amount mismatch '. $order_amount .' - ' . $invoice_amount
);
}
$allowed_event_types = ['InvoicePaid', 'InvoiceCancelled'];
if (in_array($data[$eventType], $allowed_event_types)) {
$all_statuses = array(
'1' => 'pending',
'2' => 'completed',
'3' => 'ok',
'4' => 'processed',
'5' => 'closed',
'6' => 'rejected',
);
if ($purchase_log[0]['processed'] == array_search('pending', $all_statuses)) {
$processed = array_search('pending', $all_statuses);
$details = array(
'processed' => $processed,
'transactid' => $data[$invoice]['id'],
'date' => time(),
);
$session_id = $data[$invoice][RBKmoneyPayment::METADATA][RBKmoneyPayment::SESSION_ID];
switch ($data[$invoice]['status']) {
case 'paid':
$details['processed'] = array_search('ok', $all_statuses);
wpsc_update_purchase_log_details($session_id, $details, 'sessionid');
transaction_results($session_id, false, $data[$invoice]['id']);
break;
case 'cancelled':
$details['processed'] = array_search('closed', $all_statuses);
wpsc_update_purchase_log_details($session_id, $details, 'sessionid');
transaction_results($session_id, false, $data[$invoice]['id']);
break;
default:
// The default action is not needed if a status has come that does not interest us
break;
}
}
}
_rbkmoney_payment_response_with_code_and_message(
RBKmoneyPayment::HTTP_CODE_OK,
'OK'
@ -239,7 +279,8 @@ function submit_rbkmoney_payment()
$text_fields = array(
'rbkmoney_payment_shop_id',
'rbkmoney_payment_form_path_logo',
'rbkmoney_payment_form_company_name',
'rbkmoney_payment_form_button_label',
'rbkmoney_payment_form_description',
);
_rbkmoney_payment_update_options($text_fields, 'text');
@ -304,6 +345,22 @@ function form_rbkmoney_payment()
" . __('Your company name for payment form', 'wp-e-commerce') . "
</p>
</tr>
<tr>
<td>" . __('Button label in payment form', 'wp-e-commerce') . "</td>
<td>
<input type='text' size='60' value='" . trim(get_option('rbkmoney_payment_form_button_label')) . "' name='rbkmoney_payment_form_button_label' />
<p class='description'>
" . __('Your button label for payment form', 'wp-e-commerce') . "
</p>
</tr>
<tr>
<td>" . __('Description in payment form', 'wp-e-commerce') . "</td>
<td>
<input type='text' size='60' value='" . trim(get_option('rbkmoney_payment_form_description')) . "' name='rbkmoney_payment_form_description' />
<p class='description'>
" . __('Your description for payment form', 'wp-e-commerce') . "
</p>
</tr>
<tr>
<td>" . __('Private key', 'wp-e-commerce') . "</td>
<td>

View File

@ -27,15 +27,8 @@ class RBKmoneyPayment
/**
* Constants for Callback
*/
const SHOP_ID = 'shop_id';
const INVOICE_ID = 'invoice_id';
const PAYMENT_ID = 'payment_id';
const AMOUNT = 'amount';
const CURRENCY = 'currency';
const CREATED_AT = 'created_at';
const SHOP_ID = 'shopID';
const METADATA = 'metadata';
const STATUS = 'status';
const SIGNATURE = 'HTTP_X_SIGNATURE';
const ORDER_ID = 'order_id';
const SESSION_ID = 'session_id';
const EVENT_TYPE = 'event_type';
@ -260,29 +253,32 @@ class RBKmoneyPayment
'description',
]);
$data = [
'shopID' => (int)$this->getShopId(),
'amount' => (int)$this->getAmount(),
'metadata' => $this->prepare_metadata($this->getOrderId(), $this->getSessionId()),
'dueDate' => $this->prepare_due_date(),
'currency' => $this->getCurrency(),
'product' => $this->getProduct(),
'description' => $this->getDescription(),
];
$this->validate();
$url = $this->prepare_api_url('processing/invoices');
$headers = $this->headers();
$response = $this->send($url, static::HTTP_METHOD_POST, $headers, json_encode($data, true));
$response_decode = json_decode($response['body'], true);
$invoice_id = !empty($response_decode['id']) ? $response_decode['id'] : '';
return $invoice_id;
}
private function headers() {
$headers = [];
$headers[] = 'X-Request-ID: ' . uniqid();
$headers[] = 'Authorization: Bearer ' . $this->merchant_private_key;
$headers[] = 'Content-type: application/json; charset=utf-8';
$headers[] = 'Accept: application/json';
$data = [
'shopID' => (int)$this->shop_id,
'amount' => (int)$this->amount,
'metadata' => $this->prepare_metadata($this->order_id, $this->session_id),
'dueDate' => $this->prepare_due_date(),
'currency' => $this->currency,
'product' => $this->product,
'description' => $this->description,
];
$this->validate();
$url = $this->prepare_api_url('processing/invoices');
$response = $this->send($url, static::HTTP_METHOD_POST, $headers, json_encode($data, true));
$response_decode = json_decode($response['body'], true);
$invoice_id = !empty($response_decode['id']) ? $response_decode['id'] : '';
return $invoice_id;
return $headers;
}
public function create_access_token($invoice_id)
@ -290,13 +286,9 @@ class RBKmoneyPayment
if (empty($invoice_id)) {
throw new Exception('Не передан обязательный параметр invoice_id');
}
$headers = [];
$headers[] = 'X-Request-ID: ' . uniqid();
$headers[] = 'Authorization: Bearer ' . $this->merchant_private_key;
$headers[] = 'Content-type: application/json; charset=utf-8';
$headers[] = 'Accept: application/json';
$url = $this->prepare_api_url('processing/invoices/' . $invoice_id . '/access_tokens');
$headers = $this->headers();
$response = $this->send($url, static::HTTP_METHOD_POST, $headers);
if ($response['http_code'] != static::HTTP_CODE_CREATED) {
@ -313,16 +305,14 @@ class RBKmoneyPayment
throw new Exception('Не передан обязательный параметр url');
}
$allowed_methods = [static::HTTP_METHOD_POST, static::HTTP_METHOD_GET];
$allowed_methods = [static::HTTP_METHOD_POST];
if (!in_array($method, $allowed_methods)) {
throw new Exception('Неподдерживаемый метод ' . $method);
}
$curl = curl_init($url);
if ($method == static::HTTP_METHOD_POST) {
curl_setopt($curl, CURLOPT_POST, TRUE);
curl_setopt($curl, CURLOPT_POSTFIELDS, $data);
}
curl_setopt($curl, CURLOPT_POST, TRUE);
curl_setopt($curl, CURLOPT_POSTFIELDS, $data);
curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, FALSE);
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, FALSE);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, TRUE);

View File

@ -10,6 +10,11 @@ class RBKmoneyPaymentVerification
const OPENSSL_VERIFY_SIGNATURE_IS_INCORRECT = 0;
const OPENSSL_VERIFY_ERROR = -1;
const SIGNATURE = 'HTTP_CONTENT_SIGNATURE';
const SIGNATURE_ALG = 'alg';
const SIGNATURE_DIGEST = 'digest';
const SIGNATURE_PATTERN = "|alg=(\S+);\sdigest=(.*)|i";
public static function verification_signature($data = '', $signature = '', $public_key = '')
{
if (empty($data) || empty($signature) || empty($public_key)) {
@ -23,4 +28,29 @@ class RBKmoneyPaymentVerification
return ($verify == static::OPENSSL_VERIFY_SIGNATURE_IS_CORRECT);
}
public static function urlsafe_b64decode($string)
{
$data = str_replace(array('-', '_'), array('+', '/'), $string);
$mod4 = strlen($data) % 4;
if ($mod4) {
$data .= substr('====', $mod4);
}
return base64_decode($data);
}
public static function urlsafe_b64encode($string)
{
$data = base64_encode($string);
return str_replace(array('+', '/'), array('-', '_'), $data);
}
public static function get_parameters_content_signature($content_signature)
{
preg_match_all(static::SIGNATURE_PATTERN, $content_signature, $matches, PREG_PATTERN_ORDER);
$params = array();
$params[static::SIGNATURE_ALG] = !empty($matches[1][0]) ? $matches[1][0] : '';
$params[static::SIGNATURE_DIGEST] = !empty($matches[2][0]) ? $matches[2][0] : '';
return $params;
}
}