├── .browserslistrc ├── .eslintrc ├── .github └── workflows │ └── npm-publish.yml ├── .gitignore ├── .gitlab-ci.yml ├── .npmrc ├── LICENSE ├── README.md ├── iife.js ├── package.json ├── public ├── test.html └── test.js ├── rollup.config.js └── src ├── components ├── Button │ ├── index.js │ └── style.css ├── Confirm │ ├── index.js │ └── style.css ├── Paranja │ ├── index.js │ └── style.css └── PaymentPage │ ├── index.js │ └── style.css ├── constants ├── icons.js ├── index.js ├── messages.js └── version.js ├── index.js └── utils ├── bindListener.js ├── changeLocation.js ├── classList.js ├── classProvider.js ├── component.js ├── prepareUrl.js └── scroll ├── index.js └── style.css /.browserslistrc: -------------------------------------------------------------------------------- 1 | ie >= 9 2 | last 2 versions 3 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["airbnb/base"], 3 | "parser": "babel-eslint", 4 | "env": { 5 | "browser": true, 6 | "commonjs": true 7 | }, 8 | "rules": { 9 | "indent": ["error", 4, { "SwitchCase": 1 }], 10 | "comma-dangle": ["error", "never"], 11 | "arrow-parens": ["error", "as-needed"], 12 | "import/prefer-default-export": 0, 13 | "import/no-extraneous-dependencies": ["error"], 14 | "class-methods-use-this": 0, 15 | "prefer-promise-reject-errors": 0 16 | }, 17 | "settings": { 18 | "import/resolver": { 19 | "node": { 20 | "paths": ["."] 21 | } 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /.github/workflows/npm-publish.yml: -------------------------------------------------------------------------------- 1 | # This workflow will run tests using node and then publish a package to GitHub Packages when a release is created 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/publishing-nodejs-packages 3 | 4 | name: Quality Check Master and Publish 5 | 6 | on: 7 | push: 8 | branches: [main] 9 | 10 | jobs: 11 | check-and-publish: 12 | name: Quality Check Master and Publish 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout source code 16 | uses: actions/checkout@v2 17 | - name: Setup Node.js 18 | uses: actions/setup-node@v1 19 | with: 20 | node-version: 12 21 | registry-url: https://registry.npmjs.org/ 22 | - name: Install node_modules 23 | run: npm install 24 | - run: npm publish 25 | env: 26 | NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | .DS_Store 3 | git 4 | deploy 5 | build* 6 | 7 | ### STS ### 8 | .apt_generated 9 | .classpath 10 | .factorypath 11 | .project 12 | .settings 13 | .springBeans 14 | .sts4-cache 15 | 16 | ### IntelliJ IDEA ### 17 | .idea 18 | *.iws 19 | *.iml 20 | *.ipr 21 | 22 | ### NetBeans ### 23 | /nbproject/private/ 24 | /nbbuild/ 25 | /nbdist/ 26 | /.nb-gradle/ 27 | /build/ 28 | 29 | ### VS Code ### 30 | .vscode/ 31 | *.tar.gz 32 | 33 | node_modules 34 | dist 35 | lib 36 | lib-style 37 | coverage 38 | es 39 | package-lock.json 40 | -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | stages: 2 | - verify 3 | - deploy 4 | 5 | include: 6 | - project: 'acquiring/devops/pipelines' 7 | ref: master 8 | file: '.appscreener.yml' 9 | 10 | variables: 11 | VERSION: 1.1.$CI_PIPELINE_IID 12 | APP_NAME: ecom-sdk-javascript 13 | 14 | push: 15 | stage: deploy 16 | image: artifactory.raiffeisen.ru/ecom-image-docker/cli-tools:$CLI_TOOLS_TAG 17 | script: 18 | - git config --global user.email $OPEN_SOURCE_GITHUB_EMAIL 19 | - git config --global user.name $OPEN_SOURCE_GITHUB_LOGIN 20 | - git config --global http.proxy http://sys-proxy.raiffeisen.ru:8080 21 | - npm version $VERSION 22 | - git push https://$OPEN_SOURCE_GITHUB_LOGIN:$OPEN_SOURCE_GITHUB_TOKEN@github.com/Raiffeisen-DGTL/$APP_NAME.git HEAD:master --force 23 | only: 24 | - master 25 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | registry=https://registry.npmjs.org/ 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Raiffeisen DGTL 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ```Raiffeisen Payment Page Sdk``` 2 | 3 | JS библиотека для работы с [формой оплаты Райффайзенбанка](https://pay.raif.ru/pay/demo.html). 4 | [Конфигуратор](https://pay.raif.ru/pay/configurator/) формы оплаты Райффайзенбанка. 5 | 6 | # `Документация` 7 | 8 | * [Подключение библиотеки](#подключение-библиотеки) 9 | * [Скриптом](#скриптом) 10 | * [Подключение модуля](#подключение-модуля) 11 | * [Использование библиотеки](#использование-библиотеки) 12 | * [Простые сценарии](#простые-сценарии) 13 | * [Форма оплаты во всплывающем окне](#форма-оплаты-во-всплывающем-окне) 14 | * [Форма оплаты в новой вкладке](#форма-оплаты-в-новой-вкладке) 15 | * [Форма оплаты в той же вкладке](#форма-оплаты-в-той-же-вкладке) 16 | * [Расширенные сценарии](#расширенные-сценарии) 17 | * [Пример открытия во всплывающем окне с необязательными параметрами](#пример-открытия-во-всплывающем-окне-с-необязательными-параметрами) 18 | * [Пример открытия в новой вкладке с необязательными параметрами](#пример-открытия-в-новой-вкладке-с-необязательными-параметрами) 19 | * [Пример открытия в той же вкладке с необязательными параметрами](#пример-открытия-в-той-же-вкладке-с-необязательными-параметрами) 20 | * [Дополнительно](#дополнительно) 21 | * [Подключение библиотеки скриптом](#подключение-библиотеки-скриптом) 22 | * [Раздельное подключение стилей отдельным файлом](#раздельное-подключение-стилей-отдельным-файлом) 23 | * [Локальный запуск проекта](#локальный-запуск-проекта) 24 | 25 | ## `Подключение библиотеки` 26 | 27 | #### Скриптом 28 | 29 | Для рабочего проекта подключите скрипт: 30 | 31 | ``` 32 | 33 | ``` 34 | 35 | или 36 | 37 | #### Подключение модуля 38 | 39 | ``` 40 | import PaymentPageSdk from '@raiffeisen-ecom/payment-sdk'; 41 | ``` 42 | 43 | Иные варианты подключения расположены в разделе [дополнительно](#дополнительно) 44 | 45 | ## `Использование библиотеки` 46 | 47 | Работа происходит через обращения к классу `PaymentPageSdk`. 48 | 49 | В параметрах конструктора нужно указать обязательный параметр `publicId` 50 | и необязательный, в случае, если нужно выбрать определенный сервер для работы 51 | (test/production) - `url` 52 | 53 | `test` 54 | ```js 55 | const paymentPage = new PaymentPageSdk('000001680200002-80200002', { 56 | url: 'https://pay-test.raif.ru/pay' 57 | }); 58 | ``` 59 | 60 | `prod` 61 | 62 | ```js 63 | const paymentPage = new PaymentPageSdk('000001780049001-80049001'); 64 | ``` 65 | 66 | 67 | ### `Простые сценарии` 68 | 69 | Обязательные парамеры: 70 | 71 | * publicId (String) - идентификатор продавца; 72 | * amount (Amount) - стоимость товара, для копеек доступно два знака после точки; 73 | 74 | #### Форма оплаты во всплывающем окне 75 | 76 | Для отслеживания успешности оплаты метод openPopup возвращает Promise, 77 | позволяющий подписаться на успешную оплату или закрытие окна. 78 | 79 | ```js 80 | paymentPage.openPopup({amount: 10.10}) 81 | .then(function() { 82 | // console.log("Спасибо"); 83 | }) 84 | .catch(function() { 85 | // console.log("Неудача"); 86 | }); 87 | ``` 88 | 89 | #### Форма оплаты в новой вкладке 90 | 91 | ```js 92 | paymentPage.openWindow({amount: 10.10}); 93 | ``` 94 | 95 | #### Форма оплаты в той же вкладке 96 | 97 | ```js 98 | paymentPage.replace({amount: 10.10}); 99 | ``` 100 | 101 | ### `Расширенные сценарии` 102 | 103 | * [Без чека](#без-чека) 104 | * [С чеком (ФФД 1.05)](#с-чеком-ффд-105) 105 | * [С чеком (ФФД 1.2)](#с-чеком-ффд-12) 106 | 107 | ### Без чека 108 | **Обязательные параметры:** 109 | 110 | * publicId (String) - идентификатор продавца; 111 | * amount (Amount) - стоимость товара, для копеек доступно два знака после точки; 112 | 113 | **Необязательные параметры:** 114 | 115 | * orderId (String) - номер заказа (в строке разрешены символы английского алфавита, цифры, а также два специальных символа: "-", "_" ([0-9a-zA-Z\-\_]+)); 116 | * successUrl (String) - ссылка, на которую перейдёт покупатель, в случае успешной оплаты. 117 | Поддерживается только для openWindow или replace; 118 | * failUrl (String) - ссылка, на которую перейдёт покупатель, в случае неудачной оплаты. 119 | Поддерживается только для openWindow или replace; 120 | * extra (Object) - любые данные, которые можно получить при вызове колбэка; 121 | * comment (String) - описание товара, который приобретает покупатель. 122 | * paymentMethod (['ONLY_SBP', 'ONLY_ACQUIRING 123 | ']) - способ оплаты, отображающий соответствующую форму. Если параметр не передан отображается и acquiring, и СБП. 124 | * locale (['ru', 'en']) - выбор языка формы, по умолчанию `ru`. 125 | * expirationDate (String) - срок жизни заказа. YYYY-MM-DD ТHH24:MM:SS±HH:MM 126 | * successSbpUrl (String) - ссылка для автоматического возврата плательщика из приложения банка в приложение или на сайт магазина. Ссылка должна содержать `https://` для web страниц или уникальную схему для мобильного приложения. 127 | * paymentDetails (String) - назначение платежа. 128 | 129 | Дополнительно можно стилизовать страницу, это достигается путём добавления параметра `style`: 130 | 131 | * style (Object) 132 | * button - кнопка 133 | * backgroundColor - цвет фона 134 | * textColor - цвет текста 135 | * hoverTextColor - цвет текста при наведении 136 | * hoverBackgroundColor - цвет фона при наведении 137 | * borderRadius - радиус 138 | * header - шапка формы 139 | * logo - ссылка на логотип 140 | * titlePlace - расположение 141 | 142 | В зависимости от titlePlace зависит размер логотипа: 143 | 144 | * titlePlace: 'RIGHT' => узкий логотип: 60x40; 145 | * titlePlace: 'BOTTOM' => широкий логотип: 340x40. 146 | 147 | ### С чеком (ФФД 1.05) 148 | 149 | > Если вы хотите фискализировать чеки через форму оплаты по ФФД 1.05, необходимо дополнительно передать объект чека `receipt`.
150 | > В случае некорректного формата объекта `receipt` чек не будет создан, при этом открытие платежной формы не блокируется, и заказ может быть оплачен. 151 | 152 | * receipt (Object) 153 | * receiptNumber (String) `maxLength: 99 154 | ` – уникальный номер чека. Формат `A-Za-z0-9_-`; 155 | * customer (Object) – данные о покупателе; 156 | * email (String) `required` `maxLength: 64` - электронный адрес покупателя для отправки чека; 157 | * name (String) `maxLength: 256` - ФИО покупателя; 158 | * items (Object[]) `required` – позиции чека (не более 100 объектов); 159 | * name (String) `required` `maxLength: 128` - наименование товара, работы, услуги, иного предмета расчета; 160 | * price (Number) `required` – цена за единицу товара, работы, услуги, иного предмета расчета в рублях (8 символов на целую часть, 2 - на дробную); 161 | * quantity (Number) `required` – количество/вес (5 символов на целую часть, 3 - на дробную); 162 | * amount (Number) `required` – итоговая сумма в рублях (8 символов на целую часть, 2 - на дробную); 163 | * paymentObject (String) – признак предмета расчёта ['COMMODITY', 'EXCISE', 'JOB', 'SERVICE', 'PAYMENT', 'ANOTHER']. Для авансовых чеков и чеков частичной предоплаты должен заполняться значением PAYMENT. Если параметр не передан, то заполняется значением COMMODITY по умолчанию; 164 | * paymentMode (String) – способ расчета ['FULL_PREPAYMENT', 'FULL_PAYMENT', 'ADVANCE', 'PREPAYMENT']. Если параметр не передан, по умолчанию устанавливается значение FULL_PREPAYMENT. 165 | * FULL_PREPAYMENT – 100% предоплата до момента передачи предмета расчета 166 | * FULL_PAYMENT – полная оплата в момент передачи предмета расчета 167 | * ADVANCE – аванс 168 | * PREPAYMENT – частичная предоплата до момента передачи предмета расчета; 169 | * measurementUnit (String) `maxLength: 16` – единица измерения товара, работы, услуги, иного предмета расчета; 170 | * nomenclatureCode (String) `maxLength: 150` – номенклатурный код товара в 16-ричном представлении с пробелами или в формате GS1 DataMatrix. Например, "00 00 00 00 12 00 AB 00" или "010463003407001221CMK45BrhN0WLf"; 171 | * vatType (String) `required` – ставка НДС ['NONE', 'VAT0', 'VAT10', 'VAT110', 'VAT20', 'VAT120', 'VAT5', 'VAT105', 'VAT7', 'VAT107']; 172 | * agentType (String) – признак агента по предмету расчета. Заполняется только для операций через агента ['BANK_PAYING_AGENT', 'BANK_PAYING_SUBAGENT', 'PAYING_AGENT', 'PAYING_SUBAGENT', 'ATTORNEY' , 'COMMISSION_AGENT', 'ANOTHER']; 173 | * supplierInfo (Object) – данные о поставщике. Обязательно к заполнению, если заполнен параметр agentType; 174 | * phone (String) – телефон поставщика. Заполняется по формату "+79991234567", после кода +7 должно быть указано 10 цифр; 175 | * name (String) – наименование поставщика; 176 | * inn (String) `required` `maxLength: 12` – ИНН поставщика. Может содержать только цифры в количестве 10 или 12 символов; 177 | * payments (Object[]) – данные об оплате, только для чеков с зачетом аванса или частичной предоплаты. Если payments не передан, то по умолчанию заполняется безналичным видом оплаты и ее суммой, которая равна сумме чека; 178 | * type (String) `required` – вид оплаты ['E_PAYMENT', 'PREPAID']. 179 | * E_PAYMENT – безналичная оплата 180 | * PREPAID – предварительная оплата (зачет аванса и/или предыдущих платежей); 181 | * amount (Number) `required` – сумма оплаты 182 | 183 | ### С чеком (ФФД 1.2) 184 | 185 | > Если вы хотите фискализировать чеки через форму оплаты по ФФД 1.2, необходимо дополнительно передать объект чека `receipt`.
186 | > В случае некорректного формата объекта `receipt` чек не будет создан, при этом открытие платежной формы не блокируется, и заказ может быть оплачен. 187 | 188 | * receipt (Object) 189 | * receiptNumber (String) `maxLength: 99` – уникальный номер чека. Формат `A-Za-z0-9_-`; 190 | * customer (Object) – данные о покупателе; 191 | * email (String) `required` `maxLength: 64` – электронный адрес покупателя для отправки чека; 192 | * extra (Object) – дополнительная информация о покупателе. Заполняется как объект свободного наполнения; 193 | * items (Object[]) `required` – позиции чека (не более 100 объектов); 194 | * name (String) `required` `maxLength: 128` - наименование товара, работы, услуги, иного предмета расчета; 195 | * price (Number) `required` – цена за единицу товара, работы, услуги, иного предмета расчета в рублях (8 символов на целую часть, 2 - на дробную); 196 | * quantity (Number) `required` – количество/вес (5 символов на целую часть, 3 - на дробную); 197 | * amount (Number) `required` – итоговая сумма в рублях (8 символов на целую часть, 2 - на дробную); 198 | * paymentObject (String) – признак предмета расчёта ['COMMODITY', 'COMMODITY_MARKING_NO_CODE', 'COMMODITY_MARKING_WITH_CODE', 'EXCISE', 'EXCISE_MARKING_NO_CODE', 'EXCISE_MARKING_WITH_CODE', 'JOB', 'SERVICE', 'PAYMENT', 'ANOTHER']. Для авансовых чеков и чеков частичной предоплаты должен заполняться значением PAYMENT. Если параметр не передан, то заполняется значением COMMODITY по умолчанию; 199 | * paymentMode (String) – способ расчета ['FULL_PREPAYMENT', 'FULL_PAYMENT', 'ADVANCE', 'PREPAYMENT']. Если параметр не передан, по умолчанию устанавливается значение FULL_PREPAYMENT. 200 | * FULL_PREPAYMENT – 100% предоплата до момента передачи предмета расчета 201 | * FULL_PAYMENT – полная оплата в момент передачи предмета расчета 202 | * ADVANCE – аванс 203 | * PREPAYMENT – частичная предоплата до момента передачи предмета расчета; 204 | * measurementUnit (String) – единица измерения товара, работы, услуги, иного предмета расчета ['PIECE', 'GRAM', 'KILOGRAM', 'TON', 'CENTIMETER', 'DECIMETER', 'METER', 'SQUARE_CENTIMETER', 'SQUARE_DECIMETER', 'SQUARE_METER', 'MILLILITER', 'LITER', 'CUBIC_METER', 'KILOWATT_HOUR', 'GIGACALORIE', 'DAY', 'HOUR', 'MINUTE', 'SECOND', 'KILOBYTE', 'MEGABYTE', 'GIGABYTE', 'TERABYTE', 'OTHER']. Если передано значение вне списка выше, то в ОФД автоматически будет передано OTHER; 205 | * vatType (String) `required` – ставка НДС ['NONE', 'VAT0', 'VAT10', 'VAT110', 'VAT20', 'VAT120', 'VAT5', 'VAT105', 'VAT7', 'VAT107']; 206 | * agentType (String) – признак агента по предмету расчета. Заполняется только для операций через агента ['BANK_PAYING_AGENT', 'BANK_PAYING_SUBAGENT', 'PAYING_AGENT', 'PAYING_SUBAGENT', 'ATTORNEY' , 'COMMISSION_AGENT', 'ANOTHER']; 207 | * supplierInfo (Object) – данные о поставщике. Обязательно к заполнению, если заполнен параметр agentType; 208 | * phone (String) – телефон поставщика. Заполняется по формату "+79991234567", после кода +7 должно быть указано 10 цифр; 209 | * name (String) – наименование поставщика; 210 | * inn (String) `required` `maxLength: 12` – ИНН поставщика. Может содержать только цифры в количестве 10 или 12 символов; 211 | * marking (Object) – данные маркировки. Обязательно для маркированного товара, который имеет код маркировки; 212 | * quantity (Object) – дробное количество маркированного товара. Обязательно для дробного маркированного товара, который имеет код маркировки. Тогда параметр measurementUnit должен иметь значение PIECE; 213 | * numerator (Number) – числитель дробной части 214 | * denominator (Number) – знаменатель дробной части 215 | * code (Object) `required` – код маркировки 216 | * format (String) `required` – формат кода маркировки ['UNKNOWN', 'EAN8', 'EAN13', 'ITF14', 'GS1M', 'SHORT', 'FUR', 'EGAIS20', 'EGAIS30'] 217 | * value (String) `required` – код маркировки в соответствии с форматом; 218 | * payments (Object[]) – данные об оплате, только для чеков с зачетом аванса или частичной предоплаты. Если payments не передан, то по умолчанию заполняется безналичным видом оплаты и ее суммой, которая равна сумме чека; 219 | * type (String) `required` – вид оплаты ['E_PAYMENT', 'PREPAID']. 220 | * E_PAYMENT – безналичная оплата 221 | * PREPAID – предварительная оплата (зачет аванса и/или предыдущих платежей); 222 | * amount (Number) `required` – сумма оплаты 223 | 224 | 225 | #### Пример открытия платежной формы с передачей данных чека (для ФФД 1.05 и ФФД 1.2) 226 | 227 | ```js 228 | paymentPage.openPopup({ 229 | "publicId": "000001680200002-80200002", 230 | "orderId": "orderTest", 231 | "amount": 1200, 232 | "receipt": { 233 | "receiptNumber": "3000827351831", 234 | "customer": { 235 | "email": "customer@domain.ru" 236 | }, 237 | "items": [ 238 | { 239 | "name": "Шоколадный торт", 240 | "price": 1200, 241 | "quantity": 1, 242 | "paymentObject": "COMMODITY", 243 | "paymentMode": "FULL_PAYMENT", 244 | "amount": 1200, 245 | "vatType": "VAT20" 246 | } 247 | ] 248 | } 249 | }) 250 | ``` 251 | 252 | #### Пример открытия во всплывающем окне с необязательными параметрами 253 | 254 | ``` 255 | paymentPage.openPopup({ 256 | amount: 10.10, 257 | orderId: '91700', 258 | extra: { 259 | email: 'test@test.ru', 260 | login: 'testLogin', 261 | phone: '79191234567' 262 | }, 263 | style: { 264 | button: { 265 | backgroundColor: '#ffc800', 266 | textColor: '#542595', 267 | hoverTextColor: '#ffc800', 268 | hoverBackgroundColor: '#542595', 269 | borderRadius: '3px' 270 | }, 271 | header: { 272 | logo: 'https://www.raiffeisen.ru/common/new/images/logo-raif.svg', 273 | titlePlace: 'RIGHT' 274 | } 275 | }, 276 | comment: 'Тирольский пирог с яблоками, грушами, ветчиной, сыром, ананасами, 50см' 277 | }) 278 | .then(function() { 279 | //console.log("Спасибо"); 280 | }) 281 | .catch(function() { 282 | //console.log("Неудача"); 283 | }); 284 | ); 285 | ``` 286 | 287 | #### Пример открытия в новой вкладке с необязательными параметрами 288 | 289 | В openWindow передаются необязательные параметры для возврата пользователя на страницу, 290 | в зависимости от результата оплаты: successUrl и failUrl. 291 | 292 | ``` 293 | paymentPage.openWindow({ 294 | amount: 10.10, 295 | orderId: '91700', 296 | successUrl: 'https://www.raiffeisen.ru', 297 | failUrl: 'https://pay.raif.ru/pay/demo.html', 298 | extra: { 299 | email: 'test@test.ru', 300 | login: 'testLogin', 301 | phone: '79191234567' 302 | }, 303 | style: { 304 | button: { 305 | backgroundColor: '#ffc800', 306 | textColor: '#542595', 307 | hoverTextColor: '#ffc800', 308 | hoverBackgroundColor: '#542595', 309 | borderRadius: '3px' 310 | }, 311 | header: { 312 | logo: 'https://www.raiffeisen.ru/common/new/images/logo-raif.svg', 313 | titlePlace: 'RIGHT' 314 | } 315 | }, 316 | comment: 'Тирольский пирог с яблоками, грушами, ветчиной, сыром, ананасами, 50см' 317 | }); 318 | ``` 319 | 320 | #### Пример открытия в той же вкладке с необязательными параметрами 321 | 322 | То же самое, что и [открытие в новой вкладке](#пример-открытия-в-новой-вкладке-с-необязательными-параметрами), 323 | только необходимо использовать метод `paymentPage.replace()` 324 | 325 | ## `Дополнительно` 326 | 327 | ### Подключение библиотеки скриптом 328 | 329 | Не минифицированный скрипт со стилями внутри: 330 | 331 | ``` 332 | 333 | ``` 334 | 335 | ### Раздельное подключение стилей отдельным файлом 336 | 337 | #### Скриптом 338 | 339 | Подключение стилей: 340 | 341 | ``` 342 | 343 | ``` 344 | 345 | Подключение библиотеки: 346 | 347 | ``` 348 | 349 | ``` 350 | 351 | #### Подключение модуля 352 | 353 | Подключение стилей: 354 | 355 | ``` 356 | import '@raiffeisen-ecom/payment-sdk/lib-style/index.css'; 357 | ``` 358 | 359 | Подключение библиотеки: 360 | 361 | ``` 362 | import PaymentPageSdk from '@raiffeisen-ecom/payment-sdk/lib-style'; 363 | ``` 364 | 365 | #### Локальный запуск проекта 366 | 367 | Для успешного запуска проекта локально, необходимо использовать https. 368 | 369 | -------------------------------------------------------------------------------- /iife.js: -------------------------------------------------------------------------------- 1 | import PaymentPageSdk from './src'; 2 | 3 | window.PaymentPageSdk = PaymentPageSdk; 4 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@raiffeisen-ecom/payment-sdk", 3 | "version": "1.1.212", 4 | "description": "Ecommerce payment API SDK", 5 | "main": "lib/index.js", 6 | "author": "user-ecom-git", 7 | "scripts": { 8 | "clean": "cross-env rimraf lib lib-style dist/payment.* es", 9 | "lint": "cross-env NODE_PATH=. node ./node_modules/eslint/bin/eslint.js ./src", 10 | "prepare": "npm run clean && npm run build", 11 | "build": "npm run build:commonjs && npm run build:styled:commonjs && npm run build:iife && npm run build:iife:min && npm run build:iife:styled:min", 12 | "build:commonjs": "cross-env STYLE_EXTRACT=true FORMAT=cjs NODE_ENV=production BABEL_ENV=commonjs rollup -c -o lib-style/index.js", 13 | "build:styled:commonjs": "cross-env BABEL_ENV=commonjs FORMAT=cjs NODE_ENV=production rollup -c -o lib/index.js", 14 | "build:iife": "cross-env NODE_ENV=development rollup -c -o dist/payment.styled.js", 15 | "build:iife:styled:min": "cross-env NODE_ENV=production rollup -c -o dist/payment.styled.min.js", 16 | "build:iife:min": "cross-env STYLE_EXTRACT=true NODE_ENV=production rollup -c -o dist/payment.min.js" 17 | }, 18 | "files": [ 19 | "lib", 20 | "lib-style", 21 | "dist" 22 | ], 23 | "devDependencies": { 24 | "@babel/cli": "^7.4.4", 25 | "@babel/core": "^7.4.5", 26 | "@babel/plugin-proposal-class-properties": "^7.5.5", 27 | "@babel/plugin-proposal-decorators": "^7.4.4", 28 | "@babel/plugin-proposal-function-bind": "^7.2.0", 29 | "@babel/plugin-proposal-object-rest-spread": "^7.4.4", 30 | "@babel/plugin-proposal-private-methods": "^7.4.4", 31 | "@babel/plugin-syntax-class-properties": "^7.2.0", 32 | "@babel/plugin-transform-object-assign": "^7.2.0", 33 | "@babel/plugin-transform-runtime": "^7.4.4", 34 | "@babel/preset-env": "^7.4.5", 35 | "@babel/preset-stage-0": "^7.0.0", 36 | "autoprefixer": "^9.6.1", 37 | "babel-eslint": "^10.0.1", 38 | "babel-jest": "^24.8.0", 39 | "babel-plugin-globals": "^3.0.0", 40 | "codecov": "^3.5.0", 41 | "create-react-class": "^15.6.3", 42 | "cross-env": "^5.2.0", 43 | "es3ify": "^0.2.0", 44 | "eslint": "^6.6.0", 45 | "eslint-config-airbnb": "^18.0.1", 46 | "eslint-plugin-import": "^2.18.2", 47 | "glob": "^7.1.4", 48 | "jest": "^24.8.0", 49 | "jest-dom": "^3.5.0", 50 | "prettier": "^1.18.2", 51 | "rimraf": "^2.6.3", 52 | "rollup": "^1.14.6", 53 | "rollup-plugin-alias": "^1.5.2", 54 | "rollup-plugin-babel": "^4.3.2", 55 | "rollup-plugin-commonjs": "^10.0.2", 56 | "rollup-plugin-copy": "3.4.0", 57 | "rollup-plugin-css-only": "^1.0.0", 58 | "rollup-plugin-node-resolve": "^5.0.1", 59 | "rollup-plugin-postcss": "^2.0.3", 60 | "rollup-plugin-replace": "^2.2.0", 61 | "rollup-plugin-terser": "^5.1.3" 62 | }, 63 | "license": "MIT", 64 | "dependencies": { 65 | "promise-polyfill": "^8.1.3" 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /public/test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 9 | 10 | 11 | 12 | 47 |
48 |
49 | 50 | 51 |
52 |
53 | 54 | 55 |
56 |
57 | 58 | 59 |
60 |
61 | 62 | 63 |
64 |
65 | 66 | 67 |
68 |
69 | 70 | 71 |
72 |
73 | 74 | 75 |
76 |
77 | 86 | 87 |
88 |
89 | 97 | 98 |
99 |
100 | 101 | 102 |
103 |
104 |
105 | 106 |
107 |
108 |
109 | 110 |
111 |
112 |
113 | 114 |
115 |

Фискализация

116 |
117 |
118 | 119 |
120 |
121 | 122 | 123 | 124 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

125 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

126 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

127 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

128 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

129 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

130 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

131 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

132 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

133 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

134 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

135 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

136 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

137 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

138 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

139 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

140 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

141 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

142 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

143 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

144 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

145 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

146 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

147 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

148 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

149 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

150 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

151 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

152 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

153 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

154 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

155 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

156 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

157 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

158 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

159 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

160 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

161 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

162 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

163 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

164 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

165 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

166 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

167 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

168 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

169 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

170 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

171 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

172 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

173 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

174 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

175 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

176 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

177 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

178 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

179 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

180 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

181 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

182 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

183 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

184 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

185 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

186 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

187 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

188 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

189 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

190 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

191 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

192 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

193 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

194 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

195 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

196 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

197 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

198 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

199 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

200 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

201 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

202 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

203 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

204 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

205 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

206 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

207 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

208 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

209 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

210 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

211 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

212 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

213 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

214 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

215 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

216 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

217 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

218 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

219 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

220 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

221 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

222 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

223 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

224 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

225 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

226 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

227 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

228 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

229 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

230 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

231 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

232 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

233 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

234 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

235 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

236 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

237 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

238 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

239 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

240 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

241 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

242 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

243 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

244 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

245 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

246 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

247 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

248 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

249 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

250 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

251 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

252 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

253 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

254 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

255 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

256 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

257 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

258 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

259 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

260 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

261 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

262 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

263 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

264 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

265 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

266 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

267 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

268 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

269 |
Это нужно чтобы проверить, что скрол дизэйблится при открытии попапа)

270 |
271 | 272 | 273 | 274 | -------------------------------------------------------------------------------- /public/test.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | document.getElementById('extra').value = JSON.stringify( 3 | { 4 | lol: 'qweqwe', 5 | kek: 'asdfasdf' 6 | }, 7 | 2, 8 | 2 9 | ); 10 | 11 | document.getElementById('style').value = JSON.stringify( 12 | { 13 | button: { 14 | backgroundColor: '#ffc800', 15 | textColor: '#542595', 16 | hoverTextColor: '#ffc800', 17 | hoverBackgroundColor: '#542595', 18 | borderRadius: '3px' 19 | }, 20 | header: { 21 | logo: 'http://t1.gstatic.com/images?q=tbn:ANd9GcRHyucFf66c6YZGyErymx67X_VSzI_WX5xsLwJbJ9wyJxY3Cz1P', 22 | titlePlace: 'RIGHT' 23 | } 24 | }, 25 | 2, 26 | 2 27 | ); 28 | 29 | document.getElementById('receipt').value = JSON.stringify( 30 | { 31 | customer: { 32 | email: 'test@test.ru', 33 | name: 'Петров Иван Олегович' 34 | }, 35 | receiptNumber: '1234', 36 | items: [{ 37 | name: 'Наименование товара', 38 | price: 10.11, 39 | quantity: 2, 40 | amount: 20.22, 41 | paymentObject: 'commodity', 42 | vatType: 'vat20' 43 | }] 44 | }, 45 | 2, 46 | 2 47 | ); 48 | 49 | const today = new Date(); 50 | today.setDate(today.getDate() + 3); 51 | const todayISOString = today.toISOString(); 52 | document.getElementById('expirationDate').value = `${todayISOString.slice(0, todayISOString.length - 1)}+03:00`; 53 | 54 | document.getElementById('orderId').value = Math.floor(Math.random() * 99999).toString().substr(0, 5); 55 | 56 | const getPaymentData = function () { 57 | const extraString = document.getElementById('extra').value; // параметры, которые придут пользователю (любые данные) 58 | const receiptString = document.getElementById('receipt').value; // нужен для того, чтобы зарегистрировать чек 59 | const styleString = document.getElementById('style').value; // мерч может сам настроить стилизацию 60 | 61 | const result = { 62 | amount: document.getElementById('amount').value, // цена 63 | orderId: document.getElementById('orderId').value, // номер заказа 64 | successUrl: document.getElementById('successUrl').value, 65 | failUrl: document.getElementById('failUrl').value, 66 | successSbpUrl: document.getElementById('successSbpUrl').value, 67 | comment: document.getElementById('comment').value, // описание товара 68 | publicId: document.getElementById('publicId').value, 69 | paymentMethod: document.getElementById('paymentMethod').value, 70 | locale: document.getElementById('locale').value, 71 | expirationDate: document.getElementById('expirationDate').value, 72 | paymentDetails: document.getElementById('paymentDetails').value 73 | }; 74 | 75 | result.extra = extraString ? JSON.parse(extraString) : ''; 76 | 77 | result.receipt = receiptString ? JSON.parse(receiptString) : ''; 78 | 79 | result.style = styleString ? JSON.parse(styleString) : ''; 80 | 81 | return result; 82 | }; 83 | 84 | const getTarget = function () { 85 | return document.getElementById('target').value; 86 | }; 87 | 88 | document.getElementById('openPopup').addEventListener('click', function () { 89 | const paymentData = getPaymentData(); 90 | 91 | const paymentPage = new PaymentPageSdk(getPaymentData().publicId, { 92 | targetElem: null, url: getTarget() 93 | }); 94 | 95 | paymentPage.openPopup({ 96 | amount: paymentData.amount, 97 | orderId: paymentData.orderId, 98 | comment: paymentData.comment, 99 | extra: paymentData.extra, 100 | style: paymentData.style, 101 | paymentMethod: paymentData.paymentMethod, 102 | locale: paymentData.locale, 103 | receipt: paymentData.receipt, 104 | expirationDate: paymentData.expirationDate, 105 | successSbpUrl: paymentData.successSbpUrl, 106 | paymentDetails: paymentData.paymentDetails 107 | }) 108 | .then(function (result) { 109 | console.log('resolve', result); 110 | }) 111 | .catch(function (result) { 112 | console.log('reject', result); 113 | }); 114 | }); 115 | 116 | document.getElementById('openSelf').addEventListener('click', function () { 117 | const paymentData = getPaymentData(); 118 | 119 | const paymentPage = new PaymentPageSdk(getPaymentData().publicId, { 120 | targetElem: null, url: getTarget() 121 | }); 122 | 123 | paymentPage.replace({ 124 | amount: paymentData.amount, 125 | orderId: paymentData.orderId, 126 | successUrl: paymentData.successUrl, 127 | failUrl: paymentData.failUrl, 128 | successSbpUrl: paymentData.successSbpUrl, 129 | comment: paymentData.comment, 130 | extra: paymentData.extra, 131 | style: paymentData.style, 132 | paymentMethod: paymentData.paymentMethod, 133 | locale: paymentData.locale, 134 | receipt: paymentData.receipt, 135 | expirationDate: paymentData.expirationDate, 136 | paymentDetails: paymentData.paymentDetails 137 | }); 138 | }); 139 | 140 | document.getElementById('openBlank').addEventListener('click', function () { 141 | const paymentData = getPaymentData(); 142 | 143 | const paymentPage = new PaymentPageSdk(getPaymentData().publicId, { 144 | url: getTarget() 145 | }); 146 | 147 | paymentPage.openWindow({ 148 | amount: paymentData.amount, 149 | orderId: paymentData.orderId, 150 | successUrl: paymentData.successUrl, 151 | failUrl: paymentData.failUrl, 152 | successSbpUrl: paymentData.successSbpUrl, 153 | comment: paymentData.comment, 154 | extra: paymentData.extra, 155 | style: paymentData.style, 156 | paymentMethod: paymentData.paymentMethod, 157 | locale: paymentData.locale, 158 | receipt: paymentData.receipt, 159 | expirationDate: paymentData.expirationDate, 160 | paymentDetails: paymentData.paymentDetails 161 | }); 162 | }); 163 | })(); 164 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import nodeResolve from 'rollup-plugin-node-resolve'; 2 | import path from 'path'; 3 | import commonjs from 'rollup-plugin-commonjs'; 4 | import alias from 'rollup-plugin-alias'; 5 | import babel from 'rollup-plugin-babel'; 6 | import replace from 'rollup-plugin-replace'; 7 | import { terser } from 'rollup-plugin-terser'; 8 | import postcss from 'rollup-plugin-postcss'; 9 | import autoprefixer from 'autoprefixer'; 10 | import pkg from './package.json'; 11 | import copy from 'rollup-plugin-copy'; 12 | 13 | const env = process.env.NODE_ENV; 14 | const extract = Boolean(process.env.STYLE_EXTRACT); 15 | const format = process.env.FORMAT === 'cjs' ? 'cjs' : 'iife'; 16 | 17 | const config = { 18 | input: format === 'iife' ? './iife.js' : './src/index.js', 19 | external: Object.keys(pkg.peerDependencies || {}), 20 | output: { 21 | format, 22 | name: 'PaymentPageSdk' 23 | }, 24 | plugins: [ 25 | copy({ 26 | targets: [ 27 | { src: ['public/test.html', 'public/test.js'], dest: 'dist' }, 28 | ] 29 | }), 30 | nodeResolve({ 31 | modulesOnly: format === 'cjs' 32 | }), 33 | alias({ 34 | src: path.resolve(process.cwd(), './src'), 35 | resolve: ['.js', '/index.js'] 36 | }), 37 | commonjs({ 38 | include: 'node_modules/**' 39 | }), 40 | babel({ 41 | exclude: /node_modules/, 42 | presets: [['@babel/env', { 43 | targets: { 44 | browsers: ["last 2 versions", "ie >= 9"] 45 | }, 46 | loose: true, 47 | modules: false 48 | }]], 49 | plugins: [ 50 | ['@babel/proposal-decorators', { legacy: true }], 51 | ['@babel/proposal-object-rest-spread', { loose: true }], 52 | '@babel/plugin-proposal-class-properties', 53 | '@babel/plugin-transform-object-assign', 54 | '@babel/plugin-proposal-private-methods', 55 | ].filter(Boolean), 56 | runtimeHelpers: true 57 | }), 58 | replace({ 59 | 'process.env.NODE_ENV': JSON.stringify(env), 60 | 'process.env.VERSION': `\'${pkg.version}\'` 61 | }), 62 | postcss({ 63 | modules: true, 64 | extract, 65 | plugins: [autoprefixer()] 66 | }) 67 | ] 68 | } 69 | 70 | if (format !== 'cjs' && env === 'production') { 71 | config.plugins.push( 72 | terser({ 73 | compress: { 74 | pure_getters: true, 75 | unsafe: true, 76 | unsafe_comps: true, 77 | warnings: false 78 | } 79 | }) 80 | ) 81 | } 82 | 83 | export default config 84 | -------------------------------------------------------------------------------- /src/components/Button/index.js: -------------------------------------------------------------------------------- 1 | import Component from 'src/utils/component'; 2 | 3 | import { addClass } from 'src/utils/classList'; 4 | import style from './style.css'; 5 | 6 | export class Button extends Component { 7 | render() { 8 | const { isActive, onClick, children } = this.props; 9 | 10 | const elem = document.createElement('button'); 11 | elem.addEventListener('click', onClick); 12 | elem.innerText = children; 13 | addClass(elem, style.button); 14 | 15 | if (isActive) { 16 | addClass(elem, style.active); 17 | } 18 | 19 | return elem; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/components/Button/style.css: -------------------------------------------------------------------------------- 1 | .button { 2 | background-color: white; 3 | border-color: rgba(0, 0, 0, 0.2); 4 | padding: 13px 23px; 5 | text-align: center; 6 | border-radius: 1px; 7 | outline: 0px; 8 | border-width: 1px; 9 | cursor: pointer; 10 | font-size: 14px; 11 | height: 48px; 12 | display: inline-block; 13 | box-sizing: border-box; 14 | white-space: nowrap; 15 | width: 200px; 16 | font-family: Helvetica, Arial, sans-serif; 17 | 18 | transition: background-color 0.3s ease 0s, color 0.3s ease 0s, border-color 0.3s ease 0s; 19 | } 20 | 21 | .button.active { 22 | background-color: rgb(255, 237, 0); 23 | border-color: rgb(255, 237, 0); 24 | } 25 | 26 | .button.active:hover { 27 | border-color: rgb(0, 0, 0); 28 | background-color: rgb(0, 0, 0); 29 | color: white; 30 | } 31 | 32 | .button:hover { 33 | border-color: rgb(0, 0, 0); 34 | } 35 | -------------------------------------------------------------------------------- /src/components/Confirm/index.js: -------------------------------------------------------------------------------- 1 | import Component from 'src/utils/component'; 2 | 3 | import { Paranja } from 'src/components/Paranja'; 4 | import { Button } from 'src/components/Button'; 5 | 6 | import { addClass } from 'src/utils/classList'; 7 | import style from './style.css'; 8 | 9 | export class Confirm extends Component { 10 | render() { 11 | const { onClose, onCancel } = this.props; 12 | const paranja = new Paranja(); 13 | 14 | const confirmPanel = document.createElement('div'); 15 | addClass(confirmPanel, style['confirm-panel']); 16 | 17 | const label = document.createElement('div'); 18 | label.innerText = 'Вы уверены, что хотите закрыть окно?'; 19 | addClass(label, style.label); 20 | 21 | const buttonWrap = document.createElement('div'); 22 | addClass(buttonWrap, style['button-wrap']); 23 | 24 | const closeButton = new Button(); 25 | const cancelButton = new Button(); 26 | 27 | confirmPanel.appendChild(label); 28 | confirmPanel.appendChild(buttonWrap); 29 | buttonWrap.appendChild(closeButton.execute({ 30 | onClick: onClose, 31 | isActive: true, 32 | children: 'Закрыть' 33 | })); 34 | buttonWrap.appendChild(cancelButton.execute({ 35 | onClick: onCancel, 36 | children: 'Отмена' 37 | })); 38 | 39 | return paranja.execute({ 40 | children: confirmPanel 41 | }); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/components/Confirm/style.css: -------------------------------------------------------------------------------- 1 | .confirm-panel { 2 | left: 50%; 3 | top: 50%; 4 | transform: translate(-50%, -50%); 5 | position: absolute; 6 | width: 100%; 7 | } 8 | 9 | .button-wrap { 10 | text-align: center; 11 | } 12 | 13 | .button-wrap > button + button { 14 | margin-left: 10px; 15 | } 16 | 17 | .label { 18 | color: white; 19 | font-size: 18px; 20 | margin-bottom: 40px; 21 | text-align: center; 22 | font-family: Helvetica, Arial, sans-serif; 23 | } 24 | 25 | @media (max-width: 450px) { 26 | .button-wrap > button + button { 27 | margin-left: 0; 28 | margin-top: 10px; 29 | } 30 | 31 | .button-wrap { 32 | margin: 0 40px; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/components/Paranja/index.js: -------------------------------------------------------------------------------- 1 | import Component from 'src/utils/component'; 2 | 3 | import { addClass } from 'src/utils/classList'; 4 | import style from './style.css'; 5 | 6 | export class Paranja extends Component { 7 | render() { 8 | const { children, onClick } = this.props; 9 | const elem = document.createElement('div'); 10 | 11 | addClass(elem, style.root); 12 | elem.addEventListener('click', onClick); 13 | 14 | elem.appendChild(children); 15 | 16 | return elem; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/components/Paranja/style.css: -------------------------------------------------------------------------------- 1 | .root { 2 | position: fixed; 3 | left: 0; 4 | right: 0; 5 | top: 0; 6 | bottom: 0; 7 | background-color: rgba(0, 0, 0, .7); 8 | z-index: 100; 9 | } 10 | -------------------------------------------------------------------------------- /src/components/PaymentPage/index.js: -------------------------------------------------------------------------------- 1 | import Component from 'src/utils/component'; 2 | 3 | import { Paranja } from 'src/components/Paranja'; 4 | import { addClass } from 'src/utils/classList'; 5 | import { CROSS } from 'src/constants/icons'; 6 | import style from './style.css'; 7 | 8 | export class PaymentPage extends Component { 9 | name = 'payment-page'; 10 | 11 | handleClickCross = e => { 12 | e.stopPropagation(); 13 | 14 | const { onForceClose } = this.props; 15 | 16 | onForceClose(); 17 | } 18 | 19 | render() { 20 | const { onClose } = this.props; 21 | const paranja = new Paranja(); 22 | 23 | const cover = document.createElement('div'); 24 | addClass(cover, style.cover); 25 | 26 | const cross = document.createElement('div'); 27 | cross.innerHTML = CROSS; 28 | addClass(cross, style.cross); 29 | cross.addEventListener('click', this.handleClickCross); 30 | 31 | const wrap = document.createElement('div'); 32 | addClass(wrap, style.wrap); 33 | 34 | const inner = document.createElement('div'); 35 | addClass(inner, style.inner); 36 | 37 | const iframe = document.createElement('iframe'); 38 | iframe.setAttribute('name', this.name); 39 | addClass(iframe, style.iframe); 40 | 41 | const iframeWrap = document.createElement('div'); 42 | addClass(iframeWrap, style['iframe-wrap']); 43 | 44 | cover.appendChild(wrap); 45 | cover.appendChild(cross); 46 | wrap.appendChild(iframeWrap); 47 | iframeWrap.appendChild(iframe); 48 | 49 | return paranja.execute({ 50 | children: cover, 51 | onClick: onClose 52 | }); 53 | } 54 | 55 | get url() { 56 | const { url } = this.props; 57 | const pos = url.indexOf('?'); 58 | 59 | if (pos === -1) { 60 | return url; 61 | } 62 | 63 | return url.slice(0, pos); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/components/PaymentPage/style.css: -------------------------------------------------------------------------------- 1 | .cover { 2 | position: absolute; 3 | left: 0; 4 | top: 0; 5 | right: 0; 6 | bottom: 0; 7 | } 8 | 9 | .wrap { 10 | position: absolute; 11 | overflow-y: auto; 12 | left: 50%; 13 | top: 50%; 14 | transform: translate(-50%, -50%); 15 | width: 654px; 16 | max-width: 100%; 17 | height: 637px; 18 | max-height: 100%; 19 | display: flex; 20 | flex-direction: column; 21 | } 22 | 23 | .cross { 24 | right: 27px; 25 | top: 27px; 26 | text-align: center; 27 | color: #fff; 28 | position: fixed; 29 | font-size: 2rem; 30 | cursor: pointer; 31 | font-family: "Arial", sans-serif; 32 | z-index: 101; 33 | } 34 | 35 | .cross:hover svg { 36 | fill: #AAABAD; 37 | } 38 | 39 | .iframe { 40 | width: 100%; 41 | height: 100%; 42 | border: none; 43 | } 44 | 45 | .iframe-wrap { 46 | flex: 1; 47 | overflow-y: hidden; 48 | } 49 | 50 | @media (max-width: 450px) { 51 | .wrap { 52 | height: 100%; 53 | } 54 | 55 | .inner { 56 | -webkit-overflow-scrolling: touch; 57 | overflow: auto; 58 | } 59 | 60 | .cross { 61 | right: 16px; 62 | top: 22px; 63 | color: rgb(102, 102, 102); 64 | position: absolute; 65 | font-size: 1.5rem; 66 | transform: translate(0, -50%); 67 | } 68 | 69 | .cross svg { 70 | fill: #2B2D33; 71 | width: 16px; 72 | height: 16px; 73 | } 74 | 75 | .cross:hover svg { 76 | fill: #AAABAD; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/constants/icons.js: -------------------------------------------------------------------------------- 1 | export const CROSS = '\n' 2 | + '\n' 3 | + '\n'; 4 | -------------------------------------------------------------------------------- /src/constants/index.js: -------------------------------------------------------------------------------- 1 | export const POPUP_POSTFIX = '/popup'; 2 | -------------------------------------------------------------------------------- /src/constants/messages.js: -------------------------------------------------------------------------------- 1 | export const SUCCESS_RESULT = 'success'; 2 | export const FAILED_RESULT = 'failed'; 3 | -------------------------------------------------------------------------------- /src/constants/version.js: -------------------------------------------------------------------------------- 1 | export const { VERSION } = process.env; 2 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import { PaymentPage } from 'src/components/PaymentPage'; 2 | import { Confirm } from 'src/components/Confirm'; 3 | import classProvider from 'src/utils/classProvider'; 4 | import { VERSION } from 'src/constants/version'; 5 | import { POPUP_POSTFIX } from 'src/constants'; 6 | import { SUCCESS_RESULT, FAILED_RESULT } from 'src/constants/messages'; 7 | import { addMessageListener, removeMessageListener } from 'src/utils/bindListener'; 8 | import prepareUrl from 'src/utils/prepareUrl'; 9 | import changeLocation from 'src/utils/changeLocation'; 10 | import 'promise-polyfill/src/polyfill'; 11 | import { disableScroll, enableScroll } from './utils/scroll'; 12 | 13 | const prepareValue = value => { 14 | if (value instanceof Object) { 15 | try { 16 | return JSON.stringify(value); 17 | } catch (e) { 18 | return ''; 19 | } 20 | } 21 | 22 | return value; 23 | }; 24 | 25 | class PaymentPageSdk { 26 | constructor(publicId, options = {}) { 27 | if (options.targetElem instanceof HTMLElement) { 28 | this.mount = options.targetElem; 29 | } else { 30 | this.mount = document.body; 31 | } 32 | 33 | this.publicId = publicId; 34 | this.version = VERSION; 35 | this.url = prepareUrl(options.url || 'https://pay.raif.ru/pay'); 36 | } 37 | 38 | closePopup = resolve => () => { 39 | if ( 40 | (this.confirm && this.confirm.isMount()) 41 | || !this.paymentPage 42 | || !this.paymentPage.isMount() 43 | ) { 44 | return; 45 | } 46 | this.confirm = new Confirm(); 47 | 48 | this.mount.appendChild(this.confirm.execute({ 49 | onClose: this.forceClosePopup(resolve), 50 | onCancel: () => this.confirm.unmount() 51 | })); 52 | }; 53 | 54 | forceClosePopup = reject => () => { 55 | if (this.paymentPage) { 56 | this.paymentPage.unmount(); 57 | } 58 | 59 | if (this.confirm) { 60 | this.confirm.unmount(); 61 | } 62 | 63 | removeMessageListener(window, this.messageBinding); 64 | this.messageBinding = null; 65 | 66 | enableScroll(); 67 | 68 | if (typeof reject === 'function') { 69 | reject(); 70 | } 71 | }; 72 | 73 | submitForm = (target = '_self', paymentData, url = this.url) => { 74 | const form = document.createElement('form'); 75 | form.setAttribute('action', url); 76 | form.setAttribute('method', 'POST'); 77 | form.setAttribute('target', target); 78 | 79 | Object.keys(paymentData).forEach(paymentDataKey => { 80 | const input = document.createElement('input'); 81 | const value = prepareValue(paymentData[paymentDataKey]); 82 | 83 | input.setAttribute('value', value); 84 | input.setAttribute('name', paymentDataKey); 85 | 86 | form.appendChild(input); 87 | }); 88 | 89 | this.mount.appendChild(form); 90 | form.submit(); 91 | 92 | this.mount.removeChild(form); 93 | } 94 | 95 | openPopup = (props = {}) => new Promise((resolve, reject) => { 96 | if (this.paymentPage && this.paymentPage.isMount() && this.messageBinding) { 97 | return; 98 | } 99 | 100 | const { publicId, version } = this; 101 | const { style, extra } = props; 102 | 103 | const paymentData = { 104 | ...props, publicId, style, extra, version, successUrl: '#', failUrl: '#' 105 | }; 106 | 107 | this.paymentPage = new PaymentPage(); 108 | 109 | this.mount.appendChild(this.paymentPage.execute({ 110 | onClose: this.closePopup(() => reject({ isCrossClose: true })), 111 | onForceClose: this.forceClosePopup(() => reject({ isCrossClose: true })), 112 | url: this.url 113 | })); 114 | 115 | const { successUrl, failUrl } = props; 116 | 117 | this.messageBinding = addMessageListener( 118 | window, 119 | { finish: this.handleFinishPayment(resolve, reject, successUrl, failUrl) } 120 | ); 121 | 122 | this.submitForm(this.paymentPage.name, paymentData, this.url + POPUP_POSTFIX); 123 | 124 | disableScroll(); 125 | }) 126 | 127 | openWindow = (props = {}) => { 128 | const { publicId, version } = this; 129 | const { style, extra } = props; 130 | 131 | const paymentData = { 132 | ...props, publicId, style, version, extra 133 | }; 134 | 135 | this.submitForm('_blank', paymentData, this.url); 136 | } 137 | 138 | replace = (props = {}) => { 139 | const { publicId, version } = this; 140 | const { style, extra } = props; 141 | 142 | const paymentData = { 143 | ...props, publicId, style, version, extra 144 | }; 145 | 146 | this.submitForm('_self', paymentData, this.url); 147 | } 148 | 149 | handleFinishPayment = (res, rej, successUrl, failUrl) => content => { 150 | if (content.result === SUCCESS_RESULT) { 151 | res(); 152 | 153 | if (successUrl) { 154 | changeLocation(successUrl); 155 | 156 | return; 157 | } 158 | } 159 | 160 | if (content.result === FAILED_RESULT) { 161 | rej({ isCrossClose: false }); 162 | 163 | if (failUrl) { 164 | changeLocation(failUrl); 165 | 166 | return; 167 | } 168 | } 169 | 170 | this.forceClosePopup()(); 171 | } 172 | } 173 | 174 | export default classProvider(PaymentPageSdk); 175 | -------------------------------------------------------------------------------- /src/utils/bindListener.js: -------------------------------------------------------------------------------- 1 | const POST_MESSAGE_EVENT_TYPE = 'message'; 2 | 3 | export const addMessageListener = (elem, mapping) => { 4 | const listener = e => { 5 | if (!e || !e.data) { 6 | return; 7 | } 8 | 9 | const data = JSON.parse(e.data); 10 | 11 | if (!data.event || typeof mapping[data.event] !== 'function') { 12 | return; 13 | } 14 | 15 | mapping[data.event](data.content); 16 | }; 17 | 18 | elem.addEventListener(POST_MESSAGE_EVENT_TYPE, listener, false); 19 | 20 | return listener; 21 | }; 22 | 23 | export const removeMessageListener = (elem, listener) => { 24 | elem.removeEventListener(POST_MESSAGE_EVENT_TYPE, listener); 25 | }; 26 | -------------------------------------------------------------------------------- /src/utils/changeLocation.js: -------------------------------------------------------------------------------- 1 | export default location => { 2 | window.location.replace(location); 3 | }; 4 | -------------------------------------------------------------------------------- /src/utils/classList.js: -------------------------------------------------------------------------------- 1 | const getClassNames = element => { 2 | const classList = element.getAttribute('class') || ''; 3 | 4 | return classList.split(' ').filter(Boolean); 5 | }; 6 | 7 | export const addClass = (element, targetClass) => { 8 | const classNames = getClassNames(element); 9 | 10 | if (classNames.indexOf(targetClass) !== -1) { 11 | return; 12 | } 13 | 14 | classNames.push(targetClass); 15 | element.setAttribute('class', classNames.join(' ')); 16 | }; 17 | 18 | export const removeClass = (element, targetClass) => { 19 | const classNames = getClassNames(element); 20 | 21 | if (classNames.indexOf(targetClass) === -1) { 22 | return; 23 | } 24 | 25 | classNames.splice(classNames.indexOf(targetClass), 1); 26 | element.setAttribute('class', classNames.join(' ')); 27 | }; 28 | -------------------------------------------------------------------------------- /src/utils/classProvider.js: -------------------------------------------------------------------------------- 1 | export default TargetClass => { 2 | let instance = null; 3 | 4 | return class { 5 | constructor(...args) { 6 | instance = new TargetClass(...args); 7 | } 8 | 9 | openPopup(...args) { 10 | return instance.openPopup(...args); 11 | } 12 | 13 | openWindow(...args) { 14 | return instance.openWindow(...args); 15 | } 16 | 17 | replace(...args) { 18 | return instance.replace(...args); 19 | } 20 | }; 21 | }; 22 | -------------------------------------------------------------------------------- /src/utils/component.js: -------------------------------------------------------------------------------- 1 | export default class BaseComponent { 2 | execute(props = {}) { 3 | this.props = props; 4 | 5 | const elem = this.render(); 6 | 7 | this.mount = elem; 8 | 9 | return this.mount; 10 | } 11 | 12 | unmount() { 13 | if (!this.isMount()) { 14 | return; 15 | } 16 | 17 | this.mount.parentNode.removeChild(this.mount); 18 | 19 | this.mount = null; 20 | } 21 | 22 | isMount() { 23 | return this.mount instanceof HTMLElement; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/utils/prepareUrl.js: -------------------------------------------------------------------------------- 1 | export default url => { 2 | if (url[url.length - 1] !== '/') { 3 | return url; 4 | } 5 | 6 | return url.slice(0, url.length - 1); 7 | }; 8 | -------------------------------------------------------------------------------- /src/utils/scroll/index.js: -------------------------------------------------------------------------------- 1 | import { addClass, removeClass } from 'src/utils/classList'; 2 | import style from './style.css'; 3 | 4 | export const disableScroll = () => { 5 | addClass(document.body, style['body-scroll-disable']); 6 | const html = document.getElementsByTagName('html')[0]; 7 | 8 | addClass(html, style['body-scroll-disable']); 9 | }; 10 | 11 | export const enableScroll = () => { 12 | removeClass(document.body, style['body-scroll-disable']); 13 | const html = document.getElementsByTagName('html')[0]; 14 | 15 | removeClass(html, style['body-scroll-disable']); 16 | }; 17 | -------------------------------------------------------------------------------- /src/utils/scroll/style.css: -------------------------------------------------------------------------------- 1 | .body-scroll-disable { 2 | overflow: hidden; 3 | position: relative; 4 | } --------------------------------------------------------------------------------