├── .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 | --------------------------------------------------------------------------------