Прием платежей через Яндекс.Кассу

После регистрации в сервисе становится доступен тестовый магазин, в котором можно проверить проведение платежей.

Для подключения нам понадобятся ID тестового магазина и ключ api.

ID магазина есть в заголовке страницы магазина

Ключ - в разделе Интеграция => Ключи API

Яндекс.Касса_1

Для проведения платежей можно использовать виджет. 

<script src="https://kassa.yandex.ru/checkout-ui/v2.js"></script>

Алгоритм платежа следующий:

  • Делаем ajax запрос, в хранимой процедуре которого отправляем запрос к внешнему api на создание платежа к Яндекс.Кассе

Запрос содержит:

  • Тело запроса. JSON с суммой и описанием платежа.
  • Заголовок авторизации. Формируется на основе id и ключа магазина.
  • Заголовок с ключом идемпотентности. Ключ должен быть уникальным, запрос с тем же ключом считается повторным.

            Ответ на запрос сохраняем в базе. Он будет содержать токен, который нам понадобится для инициализации виджета для платежа.

  • Делаем ajax запрос, который получает ответ api яндекс кассы. Используем токен в составе ответа для инициализации виджета. После проведения платежа нас перенаправит на страницу подтверждения платежа, адрес которой также нужно указать в параметрах виджета (обязательно, и нужно указывать абсолютный url).
  • Яндекс.Касса отправит уведомление с подтверждением платежа на наш внутренний api. Подлинность уведомления можно проверить по ip адресу (https://kassa.yandex.ru/developers/using-api/webhooks).

 

Адрес для отправки уведомлений указывается в настройках магазина:

Яндекс.Касса_2.png

Внутренний api для подтверждения платежей не должен использовать токены. 

JavaScript компонент для проведения платежей

as.yaKassa = {
    response: {},
    checkout: {},
    initPayment: function () {
        as.sys.request("yaKassa", "initPayment", { //создаем платеж через запрос
            data: {
                value: $(".payment-price").val(), //размер платежа
                description: $(".payment-description").val() //описание платежа
            },
            onSuccess: function (data) {
                as.yaKassa.getPaymentStatus(); //получаем статус платежа
            }
        });
    },
    getPaymentStatus: function () {
        as.sys.request("yaKassa", "getPaymentResponse", {
            data: {},
            onSuccess: function (data) {
                as.yaKassa.response = JSON.parse(data.data[0].response); //парсим ответ яндекс кассы на запрос создания платежа
		    //запускаем виджет
                as.yaKassa.checkout = new window.YandexCheckout({
                    confirmation_token: as.yaKassa.response.confirmation.confirmation_token, //Токен на проведение платежа от Яндекс.Кассы
                    return_url: 'https://polygon2.web-automation.ru/yaKassaConfirm', //Ссылка на страницу завершения оплаты
                    error_callback(error) {
                        console.log("yaKassa error:")
                        console.log(error);
                        //Обработка ошибок инициализации
                    }
                });
                as.yaKassa.checkout.render('paymentForm');
            }
        });
    }
}

Хранимая процедура внешнего api для создания платежа - запрос

CREATE PROCEDURE [dbo].[api_yaKassaInitPayment_request]
    @parameters ExtendedDictionaryParameter READONLY,  -- входящие параметры для внутренней обработки (используйте Key, Value2)
    @username nvarchar(32)  -- текущий пользователь.
AS
BEGIN

    exec as_print @str='starting yaKassaInitPayment request...'

    -- SELECT 1  Msg, Result, Url (адрес, куда будет идти запрос)
    select '' Msg, 1 Result, 'https://payment.yandex.net/api/v3/payments' Url

    declare @json nvarchar(max) = isnull((select Value2 from @parameters where [Key]='json'),''), --тело запроса. В нашем примере оно приходит сюда в качестве параметра
            @shopID nvarchar(16) = (select value from as_settings where code='yaKassaShopID'), --id магазина из настроек
            @shopKey nvarchar(256) = (select value from as_settings where code='yaKassaShopKey'), --ключ магазина из настроек
            @idempotenceKey nvarchar(256) = isnull((select Value2 from @parameters where [Key]='idempotenceKey'),'') --ключ идемпотентности

    declare @authorizationHeader nvarchar(512) = 'Basic ' + dbo.str_toBase64(@shopID + ':' + @shopKey) --формируем заголовок авторизации

    declare @paramsTemp table ([Key] nvarchar(128), [Value2] nvarchar(max), [type] nvarchar(64)) --параметры запроса
    insert into @paramsTemp ([Key], [Value2], [type]) values
        ('Authorization', @authorizationHeader, 'header'),
        ('Idempotence-Key', @idempotenceKey, 'header'),
        ('body', @json, 'json')

    -- SELECT 2 PARAMETERS - параметры, которые будут передаваться во внешний источник
    select [Key] name, [Value2] value, [type] [type] -- form (в форме передается), header (в http headers), get запросы передавайте прямо в URL
    from @paramsTemp
END

Хранимая процедура внешнего api для создания платежа - ответ

CREATE PROCEDURE [dbo].[api_yaKassaInitPayment_response]
    @response nvarchar(max),
    @parameters ExtendedDictionaryParameter READONLY,
    @username nvarchar(32)
AS
BEGIN

    exec as_print @str='YaKassaInitPaymentResponse'

    --для примера сохраняем платеж в as_trace со специальным кодом
    --в реальности будет таблица с платежами
    insert into as_trace (text, username, code)
    values(@response, @username, 'yaKassaPayments')

    -- SELECT 1
    select '' Msg, 1 Result, @response Response

END

Хранимая процедура ajax-запроса для создания платежа

CREATE PROCEDURE [dbo].[request_yaKassa_initPayment]
    @parameters DictionaryParameter READONLY,
    @username nvarchar(32)
AS
BEGIN
    select '' Msg, 1 Result

    declare @description nvarchar(256) = (select Value from @parameters where [Key]='description'), --описание платежа
            @value nvarchar(16) = (select Value from @parameters where [Key]='value') --сумма платежа

    --тело запроса
    declare @json nvarchar(max) =
    '{
        "amount": {
          "value": "' + @value + '",
          "currency": "RUB"
        },
        "confirmation": {
          "type": "embedded"
        },
        "capture": true,
        "description": "' + @description + '"
    }'


    select @description description, @value value

    select 'apirequest' type, 'yaKassaInitPayment' code, --запрос к внешнему api
    'idempotenceKey' p1_name, @description p1_value, --в качестве ключа идемпотентности для примера берем описание
    'json' p2_name, @json p2_value
END

Хранимая процедура ajax-запроса для получения статуса платежа

CREATE PROCEDURE [dbo].[request_yaKassa_getPaymentResponse]
    @parameters DictionaryParameter READONLY,
    @username nvarchar(32)
AS
BEGIN
    select '' Msg, 1 Result

    --т.к. мы используем as_trace, просто достаем последний платеж
    --на практике можно передать id платежа и доставать по нему
    select top 1 [text] as response from as_trace where username=@username and code='yaKassaPayments' order by id desc
END

Хранимая процедура внутреннего api для подтверждения платежа

CREATE PROCEDURE [dbo].[api_payment_confirmPayment]
@parameters ExtendedDictionaryParameter READONLY,
@username nvarchar(256)
as
begin
       
    --проверяем, что уведомление пришло с верного ip адреса
    declare @ip nvarchar(50) = (select Value2 from @parameters where [Key]='remoteIP') --ip адрес
    
    --разрешенные адреса
    --185.71.76.0/27
    --185.71.77.0/27
    --77.75.153.0/25
    --77.75.154.128/25
    declare @allowedNets table (subnet nvarchar(50), maskNumber int)
    insert into @allowedNets (subnet, maskNumber)
    values ('185.71.76.0', 27), ('185.71.77.0', 27), ('77.75.153.0', 25), ('77.75.154.128', 25)
    
  
    --проверяем, что ip уведомления входит в разрешенные подсети
    declare @ipIsCorrect bit = case when 
           exists(select * from @allowedNets where dbo.as_GetSubnetForIP(@ip, maskNumber)=subnet) 
           then 1 else 0 end
    
    if (@ipIsCorrect = 1) begin
        --json уведомления находится в параметре InputStream
      declare @notification nvarchar(max) = (select Value2 from @parameters where [Key]='InputStream')

      --получаем статус платежа
      declare @paymentStatus nvarchar(128) = JSON_VALUE(@notification, '$.object.status')

      --обновляем статус платежа в системе
      if (@paymentStatus = 'succeeded') begin
          exec as_print @str='payment success!'
          --....
      end
    end
    
    -- SELECT 1 - вывод метаданных о результате операции метода API 
    select '' Msg, 1 Result, 0 errorCode
    
    -- SELECT 2 -  вывод самих данных в API (в случае проблем проверьте что этот запрос приходит непустой)
    select 0 where 1=0

Тестирование

Тестовые номера карт доступны по адресу https://kassa.yandex.ru/developers/using-api/testing

Пример уведомления о подтверждении платежа:

{
    "type": "notification",
    "event": "payment.succeeded",
    "object": {
        "id": "2669b9a3-000f-5000-9000-1bc95c3ceaac",
        "status": "succeeded",
        "paid": true,
        "amount": {
            "value": "103.00",
            "currency": "RUB"
        },
        "authorization_details": {
            "rrn": "018162691437",
            "auth_code": "690243"
        },
        "captured_at": "2020-06-03T13:30:46.160Z",
        "created_at": "2020-06-03T13:30:11.563Z",
        "description": "test2030",
        "metadata": {},
        "payment_method": {
            "type": "bank_card",
            "id": "2669b9a3-000f-5000-9000-1bc95c3ceaac",
            "saved": false,
            "card": {
                "first6": "555555",
                "last4": "4477",
                "expiry_month": "08",
                "expiry_year": "2020",
                "card_type": "MasterCard",
                "issuer_country": "US"
            },
            "title": "Bank card *4477"
        },
        "recipient": {
            "account_id": "700318",
            "gateway_id": "1712302"
        },
        "refundable": true,
        "refunded_amount": {
            "value": "0.00",
            "currency": "RUB"
        },
        "test": true
    }
}

Выплаты через яндекс.кассу

Официальная документация: https://kassa.yandex.ru/tech/payout/intro.html

 Есть два механизма выплат - через api и по списку.

Способ по списку подразумевает загрузку excel или csv файла в личном кабинете и подтверждение по смс.

Шаблон excel файла можно скачать в личном кабинете после регистрации. Формат csv файла описан на https://kassa.yandex.ru/tech/payout/payouts-via-mp-csv.html

Для подключения выплат необходимо связаться с менеджером и заполнить анкету.

 

Дополнительеные функции SQL

CREATE FUNCTION [dbo].[as_BinaryToDecimal]
(
	@Input varchar(255)
)
RETURNS bigint
AS
BEGIN

	DECLARE @Cnt tinyint = 1
	DECLARE @Len tinyint = LEN(@Input)
	DECLARE @Output bigint = CAST(SUBSTRING(@Input, @Len, 1) AS bigint)

	WHILE(@Cnt < @Len) BEGIN
		SET @Output = @Output + POWER(CAST(SUBSTRING(@Input, @Len - @Cnt, 1) * 2 AS bigint), @Cnt)

		SET @Cnt = @Cnt + 1
	END

	RETURN @Output	

END

USE [polygon2]
GO
/****** Object:  UserDefinedFunction [dbo].[as_DecimalToBinary]    Script Date: 08.06.2020 23:10:08 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER FUNCTION [dbo].[as_DecimalToBinary]
(
	@Input bigint
)
RETURNS varchar(255)
AS
BEGIN

	DECLARE @Output varchar(255) = ''

	WHILE @Input > 0 BEGIN

		SET @Output = @Output + CAST((@Input % 2) AS varchar)
		SET @Input = @Input / 2

	END

	RETURN REVERSE(@Output)

END

SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
alter FUNCTION as_GetSubnetForIP
(
	@ip varchar(16),
	@maskNumber int
	-- Add the parameters for the function here
	
)
RETURNS varchar(16)
AS
BEGIN

	declare @temp0 table (value varchar(8))
	declare @temp1 table (bin varchar(1), mask varchar(1))

	insert into @temp0
	select dbo.as_DecimalToBinary(Value) value from dbo.split(@ip, '.')

	update @temp0
	set value = isnull(replicate('0', 8-len(value)), '') + value

	declare @binaryString varchar(32) =  (SELECT
	   stuff( (SELECT value 
				   FROM @temp0
				   FOR XML PATH(''), TYPE).value('.', 'nvarchar(max)')
				,1,0,''))

	--select * from #subnetTemp0

	--select @binaryString


	declare @mask varchar(32) = replicate('1', @maskNumber) + replicate('0', 32-@maskNumber)

	--select @mask

	insert into @temp1
	select substring(e.bin, v.number+1, 1) bin, substring(e.mask, v.number+1, 1) mask
	from (select @binaryString bin, @mask mask) e
	join master..spt_values v on v.number < len(e.bin)
	where v.type = 'P'

	declare @binaryResult varchar(32) =  (SELECT
	   stuff( (SELECT cast(bin as int) & cast(mask as int)
				   FROM @temp1
				   FOR XML PATH(''), TYPE).value('.', 'nvarchar(max)')
				,1,0,''))

	--select @binaryResult

	declare @result varchar(16) = cast(dbo.as_BinaryToDecimal(substring(@binaryResult, 0, 9)) as nvarchar) + '.' + 
									cast(dbo.as_BinaryToDecimal(substring(@binaryResult, 9, 8)) as nvarchar) + '.' +
									cast(dbo.as_BinaryToDecimal(substring(@binaryResult, 17, 8)) as nvarchar) + '.' +
									cast(dbo.as_BinaryToDecimal(substring(@binaryResult, 25, 8)) as nvarchar)

	return @result 

END
GO

Note