├── .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 |
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';
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 | }
--------------------------------------------------------------------------------