├── .npmrc
├── .browserslistrc
├── src
├── constants
│ ├── index.js
│ ├── version.js
│ └── messages.js
├── utils
│ ├── scroll
│ │ ├── style.css
│ │ └── index.js
│ ├── changeLocation.js
│ ├── prepareUrl.js
│ ├── prepareArgs.js
│ ├── parseUrl.js
│ ├── classProvider.js
│ ├── component.js
│ ├── bindListener.js
│ └── classList.js
├── components
│ ├── Paranja
│ │ ├── style.css
│ │ └── index.js
│ ├── Button
│ │ ├── index.js
│ │ └── style.css
│ ├── Confirm
│ │ ├── style.css
│ │ └── index.js
│ └── PaymentPage
│ │ ├── style.css
│ │ └── index.js
└── index.js
├── iife.js
├── .gitignore
├── .eslintrc
├── .gitlab-ci.yml
├── .github
└── workflows
│ └── npm-publish.yml
├── LICENSE
├── rollup.config.js
├── package.json
└── README.md
/.npmrc:
--------------------------------------------------------------------------------
1 | registry=https://registry.npmjs.org/
--------------------------------------------------------------------------------
/.browserslistrc:
--------------------------------------------------------------------------------
1 | ie >= 9
2 | last 2 versions
3 |
--------------------------------------------------------------------------------
/src/constants/index.js:
--------------------------------------------------------------------------------
1 | export const POPUP_POSTFIX = '/popup';
2 |
--------------------------------------------------------------------------------
/src/constants/version.js:
--------------------------------------------------------------------------------
1 | export const { VERSION } = process.env;
2 |
--------------------------------------------------------------------------------
/iife.js:
--------------------------------------------------------------------------------
1 | import PaymentPageSdk from './src';
2 |
3 | window.PaymentPageSdk = PaymentPageSdk;
4 |
--------------------------------------------------------------------------------
/src/utils/scroll/style.css:
--------------------------------------------------------------------------------
1 | .body-scroll-disable {
2 | overflow: hidden;
3 | position: relative;
4 | }
--------------------------------------------------------------------------------
/src/constants/messages.js:
--------------------------------------------------------------------------------
1 | export const SUCCESS_RESULT = 'success';
2 | export const FAILED_RESULT = 'failed';
3 |
--------------------------------------------------------------------------------
/src/utils/changeLocation.js:
--------------------------------------------------------------------------------
1 | export default location => {
2 | window.location.replace(location);
3 | };
4 |
--------------------------------------------------------------------------------
/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/prepareArgs.js:
--------------------------------------------------------------------------------
1 | export default args => {
2 | const [arg1, arg2] = args;
3 |
4 | if (typeof arg1 === 'string') {
5 | return [arg1, arg2];
6 | }
7 | return [undefined, arg1];
8 | };
9 |
--------------------------------------------------------------------------------
/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/utils/parseUrl.js:
--------------------------------------------------------------------------------
1 | import prepareUrl from './prepareUrl';
2 |
3 | export default rawUrl => {
4 | const url = new URL(rawUrl);
5 | const origin = url.origin + url.pathname;
6 | const payformId = url.searchParams.get('payformId');
7 |
8 | return { origin: prepareUrl(origin), payformId };
9 | };
10 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/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/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/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/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 |
--------------------------------------------------------------------------------
/.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 | }
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/.gitlab-ci.yml:
--------------------------------------------------------------------------------
1 | stages:
2 | - verify
3 | - deploy
4 |
5 | include:
6 | - project: 'acquiring/devops/pipelines'
7 | ref: master
8 | file: '.appsec.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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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.
--------------------------------------------------------------------------------
/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/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: 710px;
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 |
--------------------------------------------------------------------------------
/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/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 style from './style.css';
6 |
7 | export class PaymentPage extends Component {
8 | name = 'payment-page';
9 |
10 | handleClickCross = e => {
11 | e.stopPropagation();
12 |
13 | const { onForceClose } = this.props;
14 |
15 | onForceClose();
16 | }
17 |
18 | render() {
19 | const { onClose } = this.props;
20 | const paranja = new Paranja();
21 |
22 | const cover = document.createElement('div');
23 | addClass(cover, style.cover);
24 |
25 | const cross = document.createElement('div');
26 | addClass(cross, style.cross);
27 | cross.addEventListener('click', this.handleClickCross);
28 | const svgNS = 'http://www.w3.org/2000/svg';
29 | const svg = document.createElementNS(svgNS, 'svg');
30 | svg.setAttribute('width', '28');
31 | svg.setAttribute('height', '28');
32 | svg.setAttribute('viewBox', '0 0 28 28');
33 | svg.setAttribute('fill', 'white');
34 |
35 | const path = document.createElementNS(svgNS, 'path');
36 | path.setAttribute('d', 'M11.5255 14L0 25.5255V28H2.4745L14 16.4745L25.5255 28H28V25.5255L16.4745 14L28 2.4745V0H25.5255L14 11.5255L2.4745 0H0V2.4745L11.5255 14Z');
37 |
38 | svg.appendChild(path);
39 | cross.appendChild(svg);
40 |
41 | const wrap = document.createElement('div');
42 | addClass(wrap, style.wrap);
43 |
44 | const inner = document.createElement('div');
45 | addClass(inner, style.inner);
46 |
47 | const iframe = document.createElement('iframe');
48 | iframe.setAttribute('name', this.name);
49 | addClass(iframe, style.iframe);
50 |
51 | const iframeWrap = document.createElement('div');
52 | addClass(iframeWrap, style['iframe-wrap']);
53 |
54 | cover.appendChild(wrap);
55 | cover.appendChild(cross);
56 | wrap.appendChild(iframeWrap);
57 | iframeWrap.appendChild(iframe);
58 |
59 | return paranja.execute({
60 | children: cover,
61 | onClick: onClose
62 | });
63 | }
64 |
65 | get url() {
66 | const { url } = this.props;
67 | const pos = url.indexOf('?');
68 |
69 | if (pos === -1) {
70 | return url;
71 | }
72 |
73 | return url.slice(0, pos);
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@raiffeisen-ecom/payment-sdk",
3 | "version": "1.1.235",
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 |
--------------------------------------------------------------------------------
/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 changeLocation from 'src/utils/changeLocation';
9 | import 'promise-polyfill/src/polyfill';
10 | import { disableScroll, enableScroll } from './utils/scroll';
11 | import parseUrl from './utils/parseUrl';
12 | import prepareArgs from './utils/prepareArgs';
13 |
14 | const prepareValue = value => {
15 | if (value instanceof Object) {
16 | try {
17 | return JSON.stringify(value);
18 | } catch (e) {
19 | return '';
20 | }
21 | }
22 |
23 | return value;
24 | };
25 |
26 | class PaymentPageSdk {
27 | constructor(...args) {
28 | const [publicId, options = {}] = prepareArgs(args);
29 |
30 | if (options.targetElem instanceof HTMLElement) {
31 | this.mount = options.targetElem;
32 | } else {
33 | this.mount = document.body;
34 | }
35 |
36 | this.publicId = publicId;
37 | this.version = VERSION;
38 |
39 | const { origin, payformId } = parseUrl(options.url || 'https://pay.raif.ru/pay');
40 | this.url = origin;
41 | this.payformId = payformId;
42 | }
43 |
44 | closePopup = resolve => () => {
45 | if (
46 | (this.confirm && this.confirm.isMount())
47 | || !this.paymentPage
48 | || !this.paymentPage.isMount()
49 | ) {
50 | return;
51 | }
52 | this.confirm = new Confirm();
53 |
54 | this.mount.appendChild(this.confirm.execute({
55 | onClose: this.forceClosePopup(resolve),
56 | onCancel: () => this.confirm.unmount()
57 | }));
58 | };
59 |
60 | forceClosePopup = reject => () => {
61 | if (this.paymentPage) {
62 | this.paymentPage.unmount();
63 | }
64 |
65 | if (this.confirm) {
66 | this.confirm.unmount();
67 | }
68 |
69 | removeMessageListener(window, this.messageBinding);
70 | this.messageBinding = null;
71 |
72 | enableScroll();
73 |
74 | if (typeof reject === 'function') {
75 | reject();
76 | }
77 | };
78 |
79 | submitForm = (target = '_self', paymentData, url = this.url) => {
80 | const form = document.createElement('form');
81 | form.setAttribute('action', url);
82 | form.setAttribute('method', 'POST');
83 | form.setAttribute('target', target);
84 |
85 | Object.keys(paymentData).forEach(paymentDataKey => {
86 | const input = document.createElement('input');
87 | const value = prepareValue(paymentData[paymentDataKey]);
88 |
89 | input.setAttribute('value', value);
90 | input.setAttribute('name', paymentDataKey);
91 |
92 | form.appendChild(input);
93 | });
94 |
95 | this.mount.appendChild(form);
96 | form.submit();
97 |
98 | this.mount.removeChild(form);
99 | }
100 |
101 | openPopup = (props = {}) => new Promise((resolve, reject) => {
102 | if (this.paymentPage && this.paymentPage.isMount() && this.messageBinding) {
103 | return;
104 | }
105 |
106 | const { publicId, version } = this;
107 | const { style, extra } = props;
108 |
109 | const paymentData = this.payformId ? {
110 | payformId: this.payformId,
111 | style,
112 | version
113 | } : {
114 | ...props, publicId, style, extra, version, successUrl: '#', failUrl: '#'
115 | };
116 |
117 | this.paymentPage = new PaymentPage();
118 |
119 | this.mount.appendChild(this.paymentPage.execute({
120 | onClose: this.closePopup(() => reject({ isCrossClose: true })),
121 | onForceClose: this.forceClosePopup(() => reject({ isCrossClose: true })),
122 | url: this.url
123 | }));
124 |
125 | const { successUrl, failUrl } = props;
126 |
127 | this.messageBinding = addMessageListener(
128 | window,
129 | { finish: this.handleFinishPayment(resolve, reject, successUrl, failUrl) }
130 | );
131 |
132 | this.submitForm(this.paymentPage.name, paymentData, this.url + POPUP_POSTFIX);
133 |
134 | disableScroll();
135 | })
136 |
137 | openWindow = (props = {}) => {
138 | const { publicId, version } = this;
139 | const { style, extra } = props;
140 |
141 | const paymentData = this.payformId ? {
142 | payformId: this.payformId,
143 | style,
144 | version
145 | } : {
146 | ...props, publicId, style, version, extra
147 | };
148 |
149 | this.submitForm('_blank', paymentData, this.url);
150 | }
151 |
152 | replace = (props = {}) => {
153 | const { publicId, version } = this;
154 | const { style, extra } = props;
155 |
156 | const paymentData = this.payformId ? {
157 | payformId: this.payformId,
158 | style,
159 | version
160 | } : {
161 | ...props, publicId, style, version, extra
162 | };
163 |
164 | this.submitForm('_self', paymentData, this.url);
165 | }
166 |
167 | handleFinishPayment = (res, rej, successUrl, failUrl) => content => {
168 | if (content.result === SUCCESS_RESULT) {
169 | res();
170 |
171 | if (successUrl) {
172 | changeLocation(successUrl);
173 |
174 | return;
175 | }
176 | }
177 |
178 | if (content.result === FAILED_RESULT) {
179 | rej({ isCrossClose: false });
180 |
181 | if (failUrl) {
182 | changeLocation(failUrl);
183 |
184 | return;
185 | }
186 | }
187 |
188 | this.forceClosePopup()();
189 | }
190 | }
191 |
192 | export default classProvider(PaymentPageSdk);
193 |
--------------------------------------------------------------------------------
/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`. SDK поддерживает два основных сценария работы:
48 | - Открытие платежной формы с созданием заказа на стороне плательщика
49 | - Открытие платежной формы с заранее созданным заказом
50 |
51 | От сценария зависит передача параметров в конструктор класса и вызываемый метод. Далее разделы будут делиться на эти сценарии.
52 |
53 | ### `Создание заказа на стороне плательщика`
54 |
55 | В параметрах конструктора нужно указать обязательный параметр `publicId`
56 | и необязательный, в случае, если нужно выбрать определенный сервер для работы
57 | (test/production) - `url`
58 |
59 | `test`
60 | ```js
61 | const paymentPage = new PaymentPageSdk('000001680200002-80200002', {
62 | url: 'https://pay-test.raif.ru/pay'
63 | });
64 | ```
65 |
66 | `prod`
67 |
68 | ```js
69 | const paymentPage = new PaymentPageSdk('000001780049001-80049001');
70 | ```
71 |
72 | ### `Заранее созданный заказ`
73 | Теперь можно открывать форму по уже готовой ссылке, без необходимости передавать
74 | `publicId`. В этом случае нужно передать в конструктор только обязательный `url` с параметром `payformId`.
75 |
76 | Если нужно выбрать определенный сервер для работы (test/production), нужно вставить в `url` ссылку c соответствующим доменом (`pay-test.raif.ru` / `pay.raif.ru`).
77 |
78 | Подробнее про метод генерации ссылки можно почитать в [документации API](https://pay.raif.ru/doc/ecom.html#tag/payform/operation/post-payments-v1-merchants-publicId-orders-payment-link2)
79 |
80 | ```js
81 | const paymentPage = new PaymentPageSdk({
82 | url: 'https://pay.raif.ru/pay/?payformId=f96f19db-b53d-4087-b3fa-034aecd682ce'
83 | });
84 | ```
85 |
86 |
87 | ## `Простые сценарии`
88 |
89 | ### `Создание заказа на стороне плательщика`
90 |
91 | Обязательные парамеры:
92 |
93 | * publicId (String) - идентификатор продавца (он уже передан в момент инициализации);
94 | * amount (Amount) - стоимость товара, для копеек доступно два знака после точки;
95 |
96 | #### Форма оплаты во всплывающем окне
97 |
98 | Для отслеживания успешности оплаты метод openPopup возвращает Promise,
99 | позволяющий подписаться на успешную оплату или закрытие окна.
100 |
101 | ```js
102 | paymentPage.openPopup({amount: 10.10})
103 | .then(function() {
104 | // console.log("Спасибо");
105 | })
106 | .catch(function() {
107 | // console.log("Неудача");
108 | });
109 | ```
110 |
111 | #### Форма оплаты в новой вкладке
112 |
113 | ```js
114 | paymentPage.openWindow({amount: 10.10});
115 | ```
116 |
117 | #### Форма оплаты в той же вкладке
118 |
119 | ```js
120 | paymentPage.replace({amount: 10.10});
121 | ```
122 |
123 | ### `Заранее созданный заказ`
124 | В этом случае обязательных параметров нет. Из необязательных параметров поддерживается только `style`, который стилизует форму. Если вместе с ним будут переданы другие необязательные параметры, они будут проигнорированы.
125 |
126 | Использование самих методов в этом сценарии аналогично предыдущему. Главное отличие – в инициализации класса.
127 |
128 | #### Форма оплаты во всплывающем окне
129 |
130 | Для отслеживания успешности оплаты метод openPopup возвращает Promise,
131 | позволяющий подписаться на успешную оплату или закрытие окна.
132 |
133 | ```js
134 | paymentPage.openPopup()
135 | .then(function() {
136 | // console.log("Спасибо");
137 | })
138 | .catch(function() {
139 | // console.log("Неудача");
140 | });
141 | ```
142 |
143 | #### Форма оплаты в новой вкладке
144 |
145 | ```js
146 | paymentPage.openWindow();
147 | ```
148 |
149 | #### Форма оплаты в той же вкладке
150 | Для наглядности ниже приведен пример со стилизацией
151 |
152 | ```js
153 | paymentPage.replace({
154 | style: {
155 | button: {
156 | backgroundColor: '#ffc800',
157 | textColor: '#542595',
158 | hoverTextColor: '#ffc800',
159 | hoverBackgroundColor: '#542595',
160 | borderRadius: '3px'
161 | },
162 | header: {
163 | logo: 'https://www.raiffeisen.ru/common/new/images/logo-raif.svg',
164 | titlePlace: 'RIGHT'
165 | }
166 | },
167 | });
168 | ```
169 |
170 | ### `Расширенные сценарии`
171 | Ниже приведены расширенные сценарии для случая с `созданием заказа на стороне плательщика`.
172 | Подобные сценарии для случая с открытием формы с `заранее созданным заказом` регулируются на стадии её генерации. Отдельного рассмотрения заслуживает сценарий со стилизацией формы, который описан отдельно в предыдущем разделе.
173 |
174 | * [Без чека](#без-чека)
175 | * [С чеком (ФФД 1.05)](#с-чеком-ффд-105)
176 | * [С чеком (ФФД 1.2)](#с-чеком-ффд-12)
177 |
178 | ### Без чека
179 | **Обязательные параметры:**
180 |
181 | * publicId (String) - идентификатор продавца;
182 | * amount (Amount) - стоимость товара, для копеек доступно два знака после точки;
183 |
184 | **Необязательные параметры:**
185 |
186 | * orderId (String) - номер заказа (в строке разрешены символы английского алфавита, цифры, а также два специальных символа: "-", "_" ([0-9a-zA-Z\-\_]+));
187 | * successUrl (String) - ссылка, на которую перейдёт покупатель, в случае успешной оплаты.
188 | Поддерживается только для openWindow или replace;
189 | * failUrl (String) - ссылка, на которую перейдёт покупатель, в случае неудачной оплаты.
190 | Поддерживается только для openWindow или replace;
191 | * extra (Object) - любые данные, которые можно получить при вызове колбэка;
192 | * comment (String) - описание товара, который приобретает покупатель.
193 | * paymentMethod (['ONLY_SBP', 'ONLY_ACQUIRING
194 | ']) - способ оплаты, отображающий соответствующую форму. Если параметр не передан отображается и acquiring, и СБП.
195 | * locale (['ru', 'en']) - выбор языка формы, по умолчанию `ru`.
196 | * expirationDate (String) - срок жизни заказа. YYYY-MM-DD ТHH24:MM:SS±HH:MM
197 | * successSbpUrl (String) - ссылка для автоматического возврата плательщика из приложения банка в приложение или на сайт магазина. Ссылка должна содержать `https://` для web страниц или уникальную схему для мобильного приложения.
198 | * paymentDetails (String) - назначение платежа.
199 |
200 | Дополнительно можно стилизовать страницу, это достигается путём добавления параметра `style`:
201 |
202 | * style (Object)
203 | * button - кнопка
204 | * backgroundColor - цвет фона
205 | * textColor - цвет текста
206 | * hoverTextColor - цвет текста при наведении
207 | * hoverBackgroundColor - цвет фона при наведении
208 | * borderRadius - радиус
209 | * header - шапка формы
210 | * logo - ссылка на логотип
211 | * titlePlace - расположение
212 |
213 | В зависимости от titlePlace зависит размер логотипа:
214 |
215 | * titlePlace: 'RIGHT' => узкий логотип: 60x40;
216 | * titlePlace: 'BOTTOM' => широкий логотип: 340x40.
217 |
218 | ### С чеком (ФФД 1.05)
219 |
220 | > Если вы хотите фискализировать чеки через форму оплаты по ФФД 1.05, необходимо дополнительно передать объект чека `receipt`.
221 | > В случае некорректного формата объекта `receipt` чек не будет создан, при этом открытие платежной формы не блокируется, и заказ может быть оплачен.
222 |
223 | * receipt (Object)
224 | * receiptNumber (String) `maxLength: 99
225 | ` – уникальный номер чека. Формат `A-Za-z0-9_-`;
226 | * onlinePayment (Boolean) `required` - признак расчета в Интернете;
227 | * customer (Object) – данные о покупателе;
228 | * email (String) `required` `maxLength: 64` - электронный адрес покупателя для отправки чека;
229 | * name (String) `maxLength: 256` - ФИО покупателя;
230 | * items (Object[]) `required` – позиции чека (не более 100 объектов);
231 | * name (String) `required` `maxLength: 128` - наименование товара, работы, услуги, иного предмета расчета;
232 | * price (Number) `required` – цена за единицу товара, работы, услуги, иного предмета расчета в рублях (8 символов на целую часть, 2 - на дробную);
233 | * quantity (Number) `required` – количество/вес (5 символов на целую часть, 3 - на дробную);
234 | * amount (Number) `required` – итоговая сумма в рублях (8 символов на целую часть, 2 - на дробную);
235 | * paymentObject (String) – признак предмета расчёта ['COMMODITY', 'EXCISE', 'JOB', 'SERVICE', 'PAYMENT', 'ANOTHER']. Для авансовых чеков и чеков частичной предоплаты должен заполняться значением PAYMENT. Если параметр не передан, то заполняется значением COMMODITY по умолчанию;
236 | * paymentMode (String) – способ расчета ['FULL_PREPAYMENT', 'FULL_PAYMENT', 'ADVANCE', 'PREPAYMENT']. Если параметр не передан, по умолчанию устанавливается значение FULL_PREPAYMENT.
237 | * FULL_PREPAYMENT – 100% предоплата до момента передачи предмета расчета
238 | * FULL_PAYMENT – полная оплата в момент передачи предмета расчета
239 | * ADVANCE – аванс
240 | * PREPAYMENT – частичная предоплата до момента передачи предмета расчета;
241 | * measurementUnit (String) `maxLength: 16` – единица измерения товара, работы, услуги, иного предмета расчета;
242 | * nomenclatureCode (String) `maxLength: 150` – номенклатурный код товара в 16-ричном представлении с пробелами или в формате GS1 DataMatrix. Например, "00 00 00 00 12 00 AB 00" или "010463003407001221CMK45BrhN0WLf";
243 | * vatType (String) `required` – ставка НДС ['NONE', 'VAT0', 'VAT10', 'VAT110', 'VAT20', 'VAT120', 'VAT5', 'VAT105', 'VAT7', 'VAT107', 'VAT22', 'VAT122'];
244 | * agentType (String) – признак агента по предмету расчета. Заполняется только для операций через агента ['BANK_PAYING_AGENT', 'BANK_PAYING_SUBAGENT', 'PAYING_AGENT', 'PAYING_SUBAGENT', 'ATTORNEY' , 'COMMISSION_AGENT', 'ANOTHER'];
245 | * supplierInfo (Object) – данные о поставщике. Обязательно к заполнению, если заполнен параметр agentType;
246 | * phone (String) – телефон поставщика. Заполняется по формату "+79991234567", после кода +7 должно быть указано 10 цифр;
247 | * name (String) – наименование поставщика;
248 | * inn (String) `required` `maxLength: 12` – ИНН поставщика. Может содержать только цифры в количестве 10 или 12 символов;
249 | * payments (Object[]) – данные об оплате, только для чеков с зачетом аванса или частичной предоплаты. Если payments не передан, то по умолчанию заполняется безналичным видом оплаты и ее суммой, которая равна сумме чека;
250 | * type (String) `required` – вид оплаты ['E_PAYMENT', 'PREPAID'].
251 | * E_PAYMENT – безналичная оплата
252 | * PREPAID – предварительная оплата (зачет аванса и/или предыдущих платежей);
253 | * amount (Number) `required` – сумма оплаты
254 |
255 | ### С чеком (ФФД 1.2)
256 |
257 | > Если вы хотите фискализировать чеки через форму оплаты по ФФД 1.2, необходимо дополнительно передать объект чека `receipt`.
258 | > В случае некорректного формата объекта `receipt` чек не будет создан, при этом открытие платежной формы не блокируется, и заказ может быть оплачен.
259 |
260 | * receipt (Object)
261 | * receiptNumber (String) `maxLength: 99` – уникальный номер чека. Формат `A-Za-z0-9_-`;
262 | * onlinePayment (Boolean) `required` - признак расчета в Интернете;
263 | * timezone (string) `required` - часовая зона `+02:00, +03:00... +12:00`;
264 | * customer (Object) – данные о покупателе;
265 | * email (String) `required` `maxLength: 64` – электронный адрес покупателя для отправки чека;
266 | * extra (Object) – дополнительная информация о покупателе. Заполняется как объект свободного наполнения;
267 | * items (Object[]) `required` – позиции чека (не более 100 объектов);
268 | * name (String) `required` `maxLength: 128` - наименование товара, работы, услуги, иного предмета расчета;
269 | * price (Number) `required` – цена за единицу товара, работы, услуги, иного предмета расчета в рублях (8 символов на целую часть, 2 - на дробную);
270 | * quantity (Number) `required` – количество/вес (5 символов на целую часть, 3 - на дробную);
271 | * amount (Number) `required` – итоговая сумма в рублях (8 символов на целую часть, 2 - на дробную);
272 | * 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 по умолчанию;
273 | * paymentMode (String) – способ расчета ['FULL_PREPAYMENT', 'FULL_PAYMENT', 'ADVANCE', 'PREPAYMENT']. Если параметр не передан, по умолчанию устанавливается значение FULL_PREPAYMENT.
274 | * FULL_PREPAYMENT – 100% предоплата до момента передачи предмета расчета
275 | * FULL_PAYMENT – полная оплата в момент передачи предмета расчета
276 | * ADVANCE – аванс
277 | * PREPAYMENT – частичная предоплата до момента передачи предмета расчета;
278 | * 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;
279 | * vatType (String) `required` – ставка НДС ['NONE', 'VAT0', 'VAT10', 'VAT110', 'VAT20', 'VAT120', 'VAT5', 'VAT105', 'VAT7', 'VAT107', 'VAT22', 'VAT122'];
280 | * agentType (String) – признак агента по предмету расчета. Заполняется только для операций через агента ['BANK_PAYING_AGENT', 'BANK_PAYING_SUBAGENT', 'PAYING_AGENT', 'PAYING_SUBAGENT', 'ATTORNEY' , 'COMMISSION_AGENT', 'ANOTHER'];
281 | * supplierInfo (Object) – данные о поставщике. Обязательно к заполнению, если заполнен параметр agentType;
282 | * phone (String) – телефон поставщика. Заполняется по формату "+79991234567", после кода +7 должно быть указано 10 цифр;
283 | * name (String) – наименование поставщика;
284 | * inn (String) `required` `maxLength: 12` – ИНН поставщика. Может содержать только цифры в количестве 10 или 12 символов;
285 | * marking (Object) – данные маркировки. Обязательно для маркированного товара, который имеет код маркировки;
286 | * quantity (Object) – дробное количество маркированного товара. Обязательно для дробного маркированного товара, который имеет код маркировки. Тогда параметр measurementUnit должен иметь значение PIECE;
287 | * numerator (Number) – числитель дробной части
288 | * denominator (Number) – знаменатель дробной части
289 | * code (Object) `required` – код маркировки
290 | * format (String) `required` – формат кода маркировки ['UNKNOWN', 'EAN8', 'EAN13', 'ITF14', 'GS1M', 'SHORT', 'FUR', 'EGAIS20', 'EGAIS30']
291 | * value (String) `required` – код маркировки в соответствии с форматом;
292 | * plannedStatus (Number) - планируемый статус маркированного торвара. Только маркировок вида 'GS1M' и 'SHORT' `1, 2... 6`;
293 | * payments (Object[]) – данные об оплате, только для чеков с зачетом аванса или частичной предоплаты. Если payments не передан, то по умолчанию заполняется безналичным видом оплаты и ее суммой, которая равна сумме чека;
294 | * type (String) `required` – вид оплаты ['E_PAYMENT', 'PREPAID'].
295 | * E_PAYMENT – безналичная оплата
296 | * PREPAID – предварительная оплата (зачет аванса и/или предыдущих платежей);
297 | * amount (Number) `required` – сумма оплаты
298 |
299 |
300 | #### Пример открытия платежной формы с передачей данных чека (для ФФД 1.05 и ФФД 1.2)
301 |
302 | ```js
303 | paymentPage.openPopup({
304 | "publicId": "000001680200002-80200002",
305 | "orderId": "orderTest",
306 | "amount": 1200,
307 | "receipt": {
308 | "receiptNumber": "3000827351831",
309 | "onlinePayment": true,
310 | "timezone": "+02:00",
311 | "customer": {
312 | "email": "customer@domain.ru"
313 | },
314 | "items": [
315 | {
316 | "name": "Шоколадный торт",
317 | "price": 1200,
318 | "quantity": 1,
319 | "paymentObject": "COMMODITY",
320 | "paymentMode": "FULL_PAYMENT",
321 | "amount": 1200,
322 | "vatType": "VAT20"
323 | }
324 | ]
325 | }
326 | })
327 | ```
328 |
329 | #### Пример открытия во всплывающем окне с необязательными параметрами
330 |
331 | ```
332 | paymentPage.openPopup({
333 | amount: 10.10,
334 | orderId: '91700',
335 | extra: {
336 | email: 'test@test.ru',
337 | login: 'testLogin',
338 | phone: '79191234567'
339 | },
340 | style: {
341 | button: {
342 | backgroundColor: '#ffc800',
343 | textColor: '#542595',
344 | hoverTextColor: '#ffc800',
345 | hoverBackgroundColor: '#542595',
346 | borderRadius: '3px'
347 | },
348 | header: {
349 | logo: 'https://www.raiffeisen.ru/common/new/images/logo-raif.svg',
350 | titlePlace: 'RIGHT'
351 | }
352 | },
353 | comment: 'Тирольский пирог с яблоками, грушами, ветчиной, сыром, ананасами, 50см'
354 | })
355 | .then(function() {
356 | //console.log("Спасибо");
357 | })
358 | .catch(function() {
359 | //console.log("Неудача");
360 | });
361 | );
362 | ```
363 |
364 | #### Пример открытия в новой вкладке с необязательными параметрами
365 |
366 | В openWindow передаются необязательные параметры для возврата пользователя на страницу,
367 | в зависимости от результата оплаты: successUrl и failUrl.
368 |
369 | ```
370 | paymentPage.openWindow({
371 | amount: 10.10,
372 | orderId: '91700',
373 | successUrl: 'https://www.raiffeisen.ru',
374 | failUrl: 'https://pay.raif.ru/pay/demo.html',
375 | extra: {
376 | email: 'test@test.ru',
377 | login: 'testLogin',
378 | phone: '79191234567'
379 | },
380 | style: {
381 | button: {
382 | backgroundColor: '#ffc800',
383 | textColor: '#542595',
384 | hoverTextColor: '#ffc800',
385 | hoverBackgroundColor: '#542595',
386 | borderRadius: '3px'
387 | },
388 | header: {
389 | logo: 'https://www.raiffeisen.ru/common/new/images/logo-raif.svg',
390 | titlePlace: 'RIGHT'
391 | }
392 | },
393 | comment: 'Тирольский пирог с яблоками, грушами, ветчиной, сыром, ананасами, 50см'
394 | });
395 | ```
396 |
397 | #### Пример открытия в той же вкладке с необязательными параметрами
398 |
399 | То же самое, что и [открытие в новой вкладке](#пример-открытия-в-новой-вкладке-с-необязательными-параметрами),
400 | только необходимо использовать метод `paymentPage.replace()`
401 |
402 | ## `Дополнительно`
403 |
404 | ### Подключение библиотеки скриптом
405 |
406 | Не минифицированный скрипт со стилями внутри:
407 |
408 | ```
409 |
410 | ```
411 |
412 | ### Раздельное подключение стилей отдельным файлом
413 |
414 | #### Скриптом
415 |
416 | Подключение стилей:
417 |
418 | ```
419 |
420 | ```
421 |
422 | Подключение библиотеки:
423 |
424 | ```
425 |
426 | ```
427 |
428 | #### Подключение модуля
429 |
430 | Подключение стилей:
431 |
432 | ```
433 | import '@raiffeisen-ecom/payment-sdk/lib-style/index.css';
434 | ```
435 |
436 | Подключение библиотеки:
437 |
438 | ```
439 | import PaymentPageSdk from '@raiffeisen-ecom/payment-sdk/lib-style';
440 | ```
441 |
442 | #### Локальный запуск проекта
443 |
444 | Для успешного запуска проекта локально, необходимо использовать https.
445 |
446 |
--------------------------------------------------------------------------------