├── vpos ├── checkout │ └── javascript │ │ ├── dist │ │ └── .keep │ │ ├── src │ │ ├── constants.js │ │ ├── index.js │ │ ├── bancard-checkout-exceptions.js │ │ ├── bancard-checkout.js │ │ └── specs │ │ │ └── bancard-checkout.test.js │ │ ├── .babelrc │ │ ├── .eslintrc │ │ ├── setupJest.js │ │ ├── scripts │ │ └── lint.js │ │ ├── webpack.config.js │ │ ├── webpack.sandbox.config.js │ │ ├── webpack.dev.config.js │ │ ├── package.json │ │ └── README.md └── sdk │ └── python │ ├── requirements.txt │ ├── setup.cfg │ ├── MANIFEST.in │ ├── MANIFEST │ ├── LICENSE.txt │ ├── bancardconnectorpython │ ├── __init__.py │ ├── constants.py │ ├── util.py │ ├── exceptions.py │ └── api.py │ ├── setup.py │ ├── tests │ ├── test_bancard_rollback.py │ ├── test_bancard_confirmations.py │ ├── test_bancard_integration_testing.py │ └── test_bancard_single_buy.py │ ├── README.rst │ ├── README │ └── README.md ├── .gitignore ├── .github └── workflows │ └── config.yml ├── LICENSE.md ├── README.md └── CONTRIBUTING.md /vpos/checkout/javascript/dist/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vpos/sdk/python/requirements.txt: -------------------------------------------------------------------------------- 1 | requests[security]>=2.18.4 2 | -------------------------------------------------------------------------------- /vpos/sdk/python/setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | description-file = README 3 | -------------------------------------------------------------------------------- /vpos/sdk/python/MANIFEST.in: -------------------------------------------------------------------------------- 1 | include *.txt *.md *.rst 2 | recursive-include tests *.py 3 | -------------------------------------------------------------------------------- /vpos/checkout/javascript/src/constants.js: -------------------------------------------------------------------------------- 1 | export default { 2 | BANCARD_URL: process.env.VPOS_PORTAL, 3 | }; 4 | -------------------------------------------------------------------------------- /vpos/checkout/javascript/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["env"], 3 | "plugins": ["transform-async-to-generator"] 4 | } 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | vpos/checkout/javascript/dist/* 3 | !vpos/checkout/javascript/dist/.keep 4 | vpos/sdk/python/.idea 5 | vpos/sdk/python/dist 6 | -------------------------------------------------------------------------------- /vpos/checkout/javascript/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "eslint-config-airbnb", 3 | "env": { 4 | "jest": true, 5 | "browser": true, 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /vpos/checkout/javascript/setupJest.js: -------------------------------------------------------------------------------- 1 | global.fetch = require('jest-fetch-mock'); 2 | 3 | process.env.VPOS_PORTAL = 'https://desa.infonet.com.py:8085'; 4 | -------------------------------------------------------------------------------- /vpos/checkout/javascript/src/index.js: -------------------------------------------------------------------------------- 1 | import Bancard from './bancard-checkout'; 2 | 3 | ((function bancard(window) { 4 | if (typeof (window.Bancard) === 'undefined') { 5 | window.Bancard = new Bancard(); // eslint-disable-line no-param-reassign 6 | } 7 | })(window)); 8 | -------------------------------------------------------------------------------- /vpos/checkout/javascript/scripts/lint.js: -------------------------------------------------------------------------------- 1 | function lint(files) { 2 | const CLIEngine = require("eslint").CLIEngine; 3 | const cli = new CLIEngine(); 4 | const report = cli.executeOnFiles(files); 5 | const formatter = cli.getFormatter(); 6 | 7 | console.log(formatter(report.results)); 8 | } 9 | 10 | lint(['bancard-checkout.js', 'bancard-checkout.test.js']); 11 | -------------------------------------------------------------------------------- /.github/workflows/config.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: 4 | push: 5 | branches: 6 | - '**' 7 | 8 | jobs: 9 | build: 10 | name: 'Run tests' 11 | runs-on: ubuntu-latest 12 | container: node:23.7.0 13 | defaults: 14 | run: 15 | working-directory: ./vpos/checkout/javascript 16 | 17 | timeout-minutes: 10 18 | 19 | steps: 20 | - uses: actions/checkout@v4 21 | - run: yarn install --frozen-lockfile 22 | - run: yarn test 23 | 24 | -------------------------------------------------------------------------------- /vpos/sdk/python/MANIFEST: -------------------------------------------------------------------------------- 1 | # file GENERATED by distutils, do NOT edit 2 | LICENSE.txt 3 | README 4 | README.md 5 | README.rst 6 | requirements.txt 7 | setup.cfg 8 | setup.py 9 | bancardconnectorpython/__init__.py 10 | bancardconnectorpython/api.py 11 | bancardconnectorpython/constants.py 12 | bancardconnectorpython/exceptions.py 13 | bancardconnectorpython/util.py 14 | tests/test_bancard_confirmations.py 15 | tests/test_bancard_integration_testing.py 16 | tests/test_bancard_rollback.py 17 | tests/test_bancard_single_buy.py 18 | -------------------------------------------------------------------------------- /vpos/checkout/javascript/src/bancard-checkout-exceptions.js: -------------------------------------------------------------------------------- 1 | const DivDoesNotExist = function DivDoesNotExist(divId) { 2 | this.name = 'DivDoesNotExist'; 3 | this.message = `Div with id: ${divId} could not be found.`; 4 | this.stack = (new Error()).stack; 5 | }; 6 | DivDoesNotExist.prototype = new Error(); 7 | 8 | const InvalidParameter = function InvalidParameter(parameter) { 9 | this.name = 'InvalidParameter'; 10 | this.message = `${parameter} must be a non empty string.`; 11 | this.stack = (new Error()).stack; 12 | }; 13 | InvalidParameter.prototype = new Error(); 14 | 15 | const exceptions = { 16 | DivDoesNotExist, 17 | InvalidParameter, 18 | }; 19 | 20 | export default exceptions; 21 | -------------------------------------------------------------------------------- /vpos/checkout/javascript/webpack.config.js: -------------------------------------------------------------------------------- 1 | const Path = require('path'); 2 | const CleanWebpackPlugin = require('clean-webpack-plugin'); 3 | const Webpack = require('webpack'); 4 | 5 | module.exports = { 6 | entry: ['babel-polyfill', './src/index.js'], 7 | plugins: [ 8 | new CleanWebpackPlugin([`dist/bancard-checkout-${process.env.npm_package_version}.js`]), 9 | new Webpack.DefinePlugin({ 10 | 'process.env.NODE_ENV': JSON.stringify('production'), 11 | 'process.env.VPOS_PORTAL': JSON.stringify('https://vpos.infonet.com.py'), 12 | }), 13 | new Webpack.optimize.UglifyJsPlugin(), 14 | ], 15 | output: { 16 | filename: `bancard-checkout-${process.env.npm_package_version}.js`, 17 | path: Path.resolve(__dirname, 'dist'), 18 | }, 19 | module: { 20 | rules: [ 21 | { 22 | test: /\.js$/, 23 | exclude: /node_modules|specs/, 24 | loader: 'babel-loader', 25 | }, 26 | ], 27 | }, 28 | }; 29 | -------------------------------------------------------------------------------- /vpos/checkout/javascript/webpack.sandbox.config.js: -------------------------------------------------------------------------------- 1 | const Path = require('path'); 2 | const CleanWebpackPlugin = require('clean-webpack-plugin'); 3 | const Webpack = require('webpack'); 4 | 5 | module.exports = { 6 | entry: ['babel-polyfill', './src/index.js'], 7 | plugins: [ 8 | new CleanWebpackPlugin([`dist/bancard-checkout-${process.env.npm_package_version}-sandbox.js`]), 9 | new Webpack.DefinePlugin({ 10 | 'process.env.NODE_ENV': JSON.stringify('production'), 11 | 'process.env.VPOS_PORTAL': JSON.stringify('https://vpos.infonet.com.py:8888'), 12 | }), 13 | new Webpack.optimize.UglifyJsPlugin(), 14 | ], 15 | output: { 16 | filename: `bancard-checkout-${process.env.npm_package_version}-sandbox.js`, 17 | path: Path.resolve(__dirname, 'dist') 18 | }, 19 | module: { 20 | rules: [ 21 | { 22 | test: /\.js$/, 23 | exclude: /node_modules|specs/, 24 | loader: 'babel-loader', 25 | } 26 | ], 27 | }, 28 | }; 29 | -------------------------------------------------------------------------------- /vpos/checkout/javascript/webpack.dev.config.js: -------------------------------------------------------------------------------- 1 | const Path = require('path'); 2 | const CleanWebpackPlugin = require('clean-webpack-plugin'); 3 | const Webpack = require('webpack'); 4 | 5 | module.exports = { 6 | entry: ['babel-polyfill', './src/index.js'], 7 | devtool: 'inline-source-map', 8 | devServer: { 9 | contentBase: './dist', 10 | hot: true, 11 | }, 12 | plugins: [ 13 | new CleanWebpackPlugin(['dist/*-dev.js'], { exclude: ['.keep'] }), 14 | new Webpack.DefinePlugin({ 15 | 'process.env.NODE_ENV': JSON.stringify('development'), 16 | 'process.env.VPOS_PORTAL': JSON.stringify('https://desa.infonet.com.py:8085'), 17 | }), 18 | new Webpack.NamedModulesPlugin(), 19 | new Webpack.HotModuleReplacementPlugin(), 20 | ], 21 | output: { 22 | filename: `bancard-checkout-${process.env.npm_package_version}-dev.js`, 23 | path: Path.resolve(__dirname, 'dist') 24 | }, 25 | module: { 26 | rules: [ 27 | { 28 | test: /\.js$/, 29 | exclude: /node_modules|specs/, 30 | loader: 'babel-loader', 31 | } 32 | ], 33 | }, 34 | }; 35 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Bancard 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. -------------------------------------------------------------------------------- /vpos/sdk/python/LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) [2018] [Victor Manuel Cajes Gonzalez] 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. 22 | -------------------------------------------------------------------------------- /vpos/checkout/javascript/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bancard-connectors", 3 | "version": "5.0.1", 4 | "author": "Bruno Bradach ", 5 | "license": "MIT", 6 | "devDependencies": { 7 | "babel-core": "^6.26.0", 8 | "babel-eslint": "^8.0.3", 9 | "babel-loader": "^7.1.2", 10 | "babel-plugin-transform-async-to-generator": "^6.5.0", 11 | "babel-polyfill": "^6.26.0", 12 | "babel-preset-env": "^1.6.1", 13 | "clean-webpack-plugin": "^0.1.17", 14 | "eslint": "^4.13.1", 15 | "eslint-config-airbnb": "^16.1.0", 16 | "eslint-plugin-import": "^2.8.0", 17 | "eslint-plugin-jsx-a11y": "^6.0.3", 18 | "eslint-plugin-react": "^7.5.1", 19 | "jest": "^22.0.0", 20 | "jest-fetch-mock": "^1.4.1", 21 | "uglify-js": "^3.2.2", 22 | "webpack": "^3.10.0", 23 | "webpack-dev-server": "^2.11.1" 24 | }, 25 | "scripts": { 26 | "start": "webpack-dev-server --open", 27 | "build-prod": "webpack -p --config webpack.config.js", 28 | "build-sandbox": "webpack -p --config webpack.sandbox.config.js", 29 | "build-dev": "webpack -p --config webpack.dev.config.js", 30 | "lint": "node ./scripts/lint.js", 31 | "test": "jest -b" 32 | }, 33 | "jest": { 34 | "setupFiles": [ 35 | "./setupJest.js" 36 | ] 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /vpos/sdk/python/bancardconnectorpython/__init__.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | # 3 | # Copyright (c) [2018] [Victor Manuel Cajes Gonzalez - vcajes@gmail.com] 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. 22 | 23 | from bancardconnectorpython.constants import * 24 | from bancardconnectorpython.util import * 25 | from bancardconnectorpython.exceptions import * 26 | from bancardconnectorpython.api import * 27 | -------------------------------------------------------------------------------- /vpos/checkout/javascript/README.md: -------------------------------------------------------------------------------- 1 | 2 | # VPOS 2.0 JS library 3 | 4 | ## Getting Started 5 | 6 | This library allows commerces to accept payments from customers as well as manage customer cards while remaining fully PCI compliant. 7 | 8 | ### Prerequisites 9 | 10 | There are no prerequisites to use this library. 11 | 12 | ### Usage in Staging or Production 13 | 14 | Download the version you want from [bancard-checkout-js](https://github.com/Bancard/bancard-checkout-js/tree/master/build) and host it on your own web server. 15 | 16 | ### Usage in development 17 | 18 | * Clone the project. 19 | * Navigate to `vpos/checkout/javascript`. 20 | * Install node (we suggest you use NVM - https://github.com/creationix/nvm#installation). 21 | * Install yarn (https://yarnpkg.com/lang/en/docs/install/). 22 | * Run `yarn install`. 23 | * Run `yarn build-sandbox`. 24 | * Run `yarn start`. 25 | * Include the script from `http://localhost:8080/dist/bancard-checkout-${version}-sandbox.js`. 26 | 27 | ## Running the tests 28 | 29 | * Run `yarn test`. 30 | 31 | ### Running the linter 32 | 33 | * Run `yarn lint`. 34 | 35 | ## Contributing 36 | 37 | Please read [CONTRIBUTING.md](https://github.com/Bancard/bancard-connectors/blob/master/CONTRIBUTING.md) for details on our code of conduct, and the process for submitting pull requests to us. 38 | 39 | ## License 40 | 41 | This project is licensed under the MIT License - see the [LICENSE.md](https://github.com/Bancard/bancard-connectors/blob/master/LICENSE.md) file for details 42 | -------------------------------------------------------------------------------- /vpos/sdk/python/setup.py: -------------------------------------------------------------------------------- 1 | from distutils.core import setup 2 | 3 | 4 | def readme(): 5 | with open('README.rst') as f: 6 | return f.read() 7 | 8 | 9 | setup( 10 | name='bancardconnectorpython', 11 | version='0.5.4', 12 | author='Victor Cajes', 13 | author_email='vcajes@gmail.com', 14 | packages=['bancardconnectorpython'], 15 | scripts=[], 16 | url='https://github.com/vcajes/bancard-connector-python', 17 | license="Documentation: https://github.com/vcajes/bancard-connector-python/tree/python-connector/vpos/sdk/python", 18 | description='The Bancard Python connector provides Python APIs to create, process and manage payments.', 19 | long_description=readme(), 20 | package_data={'bancardconnectorpython': []}, 21 | install_requires=['requests[security]>=2.18.4'], 22 | classifiers=[ 23 | 'Intended Audience :: Developers', 24 | 'Natural Language :: English', 25 | 'Operating System :: OS Independent', 26 | 'Programming Language :: Python', 27 | 'Programming Language :: Python :: 2', 28 | 'Programming Language :: Python :: 2.6', 29 | 'Programming Language :: Python :: 2.7', 30 | 'Programming Language :: Python :: 3', 31 | 'Programming Language :: Python :: 3.3', 32 | 'Programming Language :: Python :: 3.4', 33 | 'Programming Language :: Python :: 3.5', 34 | 'Programming Language :: Python :: 3.6', 35 | 'Programming Language :: Python :: Implementation :: PyPy', 36 | 'Topic :: Software Development :: Libraries :: Python Modules' 37 | ], 38 | keywords=['bancard', 'paraguay', 'python', 'rest', 'sdk', 'charges', 'webhook'] 39 | ) 40 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Bancard Connectors 2 | 3 | Bancard Connectors aims to be a collective repository containing different connectors, in different programming languages, that work smoothly with Bancard's products. 4 | 5 | This project is aimed at anyone who wishes to consume any of Bancard's services. It is maintained by Bancard. 6 | 7 | ## Folder Structure 8 | 9 | In order to maintain the order of the repository, we've defined a minimal structure: 10 | 11 | - First Folder: Indicates the product (vpos, boca-web, minipos) 12 | - Second Folder: Indicates the sub product (checkout, vpos_1.0, reports, analytical) or sdk if the connector implements all of the product's operations. 13 | - Third Folder: Indicates the technology (javascript, ruby, python, java, etc.) 14 | 15 | Check folders for each connector. 16 | 17 | ## Contributing 18 | 19 | Please read [CONTRIBUTING.md](CONTRIBUTING.md) for details on our code of conduct, and the process for submitting pull requests to us. 20 | 21 | ## Approval process 22 | 23 | In order to ensure the quality of all connectors, each of them will pass through an approval process. 24 | 25 | In a first stage we will check that: 26 | - It has a readme file. 27 | - It has tests and they run appropriately. 28 | - It follows [good practices of software engineering](https://blog.codinghorror.com/a-pragmatic-quick-reference/). 29 | 30 | In a second stage we will: 31 | - Run the connector manually on a sandbox environment. 32 | - Verify that the readme explain clearly how to use it. 33 | - Run tests manually. 34 | 35 | ## License 36 | 37 | This project is licensed under the MIT License - see the [LICENSE.md](LICENSE.md) file for details -------------------------------------------------------------------------------- /vpos/sdk/python/tests/test_bancard_rollback.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | # 3 | # Copyright (c) [2018] [Victor Manuel Cajes Gonzalez - vcajes@gmail.com] 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. 22 | 23 | 24 | import sys 25 | import random 26 | import unittest 27 | from decimal import Decimal 28 | import bancardconnectorpython 29 | 30 | 31 | class TestBancardRollback(unittest.TestCase): 32 | 33 | def test_charge_not_payed_yet(self): 34 | marketplace_charge_id = random.randrange(500000, sys.maxsize >> 8) 35 | amount, currency = Decimal(1000), "PYG" 36 | description = "Sample charge" 37 | approved_url = "http://localhost/redirect/bancard/%s/approved" % marketplace_charge_id 38 | cancelled_url = "http://localhost/redirect/bancard/%s/cancelled" % marketplace_charge_id 39 | 40 | # configure the bancard API connector from the environment variables and get a reference to the connector 41 | bancard_api = bancardconnectorpython.connector() 42 | 43 | # create the charge request 44 | bancard_process_id, payment_url, bancard_response = bancard_api.generate_charge_token(marketplace_charge_id, amount, description, approved_url, cancelled_url, currency) 45 | self.assertIsNotNone(bancard_process_id) 46 | self.assertGreater(len(bancard_process_id), 0) # the process id should contain at least one character 47 | 48 | # rollback the payment 49 | successfull_rollback, bancard_response = bancard_api.rollback_charge(marketplace_charge_id) 50 | self.assertTrue(successfull_rollback) 51 | self.assertIsNotNone(bancard_response) 52 | 53 | 54 | if __name__ == '__main__': 55 | unittest.main() 56 | -------------------------------------------------------------------------------- /vpos/sdk/python/README.rst: -------------------------------------------------------------------------------- 1 | Bancard VPOS 1.0 - Bancard Python Connector 0.5.4 library 2 | ========================================================= 3 | 4 | Getting Started 5 | --------------- 6 | 7 | This library allows developers to integrate their Python backend 8 | applications to the Bancard VPOS API. 9 | 10 | This library works with the following Python versions: 2.6, 2.7, 3.3, 11 | 3.4, 3.5, 3.6 12 | 13 | Prerequisites 14 | ~~~~~~~~~~~~~ 15 | 16 | See the requirements.txt file to see which Python libraries will be 17 | required. 18 | 19 | Usage in Staging or Production 20 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 21 | 22 | You can either include this library from: 23 | 24 | :: 25 | 26 | https://github.com/vcajes/bancard-connector-python 27 | 28 | Or install the library from the `PYPI 29 | repository `__: 30 | 31 | :: 32 | 33 | pip3 install bancardconnectorpython 34 | 35 | Usage in development 36 | ~~~~~~~~~~~~~~~~~~~~ 37 | 38 | - Downlaod and install `Python (2.6 <= version <= 39 | 3.6) `__. 40 | - Run ``pip install bancardpythonconnector``. 41 | - Import and use library in your source code: ``import bancardconnectorpython``. 42 | 43 | This will autoconfigure the connector from the following OS environment variables: 44 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 45 | 46 | BANCARD\_ENVIRONMENT=sandbox\|production 47 | 48 | BANCARD\_PUBLIC\_KEY=your\_public\_key 49 | 50 | BANCARD\_PRIVATE\_KEY=your\_private\_key 51 | 52 | bancard\_api = bancardconnectorpython.connector() 53 | 54 | or you could just create your own BancardAPI 55 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 56 | 57 | bancard\_api = bancardconnectorpython.BancardAPI(environment=bancardconnectorpython.ENVIRONMENT\_SANDBOX, 58 | public\_key=your\_public\_key, private\_key=your\_private\_key) 59 | 60 | 61 | Running tests 62 | ------------- 63 | 64 | - Download and install `Python (2.6 <= version <= 3.6) `__ 65 | - Install the library from PYPI: ``pip install bancardconnectorpython`` 66 | - Set the following two OS environment variables ``BANCARD_PUBLIC_KEY`` and 67 | ``BANCARD_PRIVATE_KEY`` with the values provided by Bancard. 68 | - Run any of the tests, i.e.: 69 | ``python /path/to/tests/test_bancard_single_buy.py`` 70 | 71 | Versioning 72 | ---------- 73 | 74 | For the versions available, see the `tags on this 75 | repository `__ 76 | 77 | Authors 78 | ------- 79 | 80 | - **Victor Cajes** - [@vcajes](https://github.com/vcajes) 81 | 82 | License 83 | ------- 84 | 85 | This project is licensed under the MIT License - see the 86 | `LICENSE `__ file for details. -------------------------------------------------------------------------------- /vpos/sdk/python/tests/test_bancard_confirmations.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | # 3 | # Copyright (c) [2018] [Victor Manuel Cajes Gonzalez - vcajes@gmail.com] 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. 22 | 23 | 24 | import sys 25 | import random 26 | import unittest 27 | from decimal import Decimal 28 | import bancardconnectorpython 29 | 30 | 31 | class TestBancardConfirmations(unittest.TestCase): 32 | 33 | def test_charge_not_payed_yet(self): 34 | marketplace_charge_id = random.randrange(500000, sys.maxsize >> 8) 35 | amount, currency = Decimal(1000), "PYG" 36 | description = "Sample charge" 37 | approved_url = "http://localhost/redirect/bancard/%s/approved" % marketplace_charge_id 38 | cancelled_url = "http://localhost/redirect/bancard/%s/cancelled" % marketplace_charge_id 39 | 40 | # configure the bancard API connector from the environment variables and get a reference to the connector 41 | bancard_api = bancardconnectorpython.connector() 42 | 43 | # create the charge request 44 | bancard_process_id, payment_url, bancard_response = bancard_api.generate_charge_token(marketplace_charge_id, amount, description, approved_url, cancelled_url, currency) 45 | self.assertIsNotNone(bancard_process_id) 46 | self.assertGreater(len(bancard_process_id), 0) # the process id should contain at least one character 47 | 48 | # check that the charge request has not yet been payed 49 | already_payed, authorization_number, bancard_response = bancard_api.get_charge_status(marketplace_charge_id, amount, currency) 50 | self.assertFalse(already_payed) # this charge has not yet been payed by the user 51 | self.assertIsNone(authorization_number) # there is no bancard authorization number yet 52 | 53 | 54 | if __name__ == '__main__': 55 | unittest.main() 56 | -------------------------------------------------------------------------------- /vpos/sdk/python/bancardconnectorpython/constants.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | # 3 | # Copyright (c) [2018] [Victor Manuel Cajes Gonzalez - vcajes@gmail.com] 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. 22 | 23 | 24 | # possibles values for the BancardAPI environment class member 25 | ENVIRONMENT_SANDBOX = "sandbox" 26 | ENVIRONMENT_PRODUCTION = "production" 27 | 28 | # Currencies that Bancard allows for charging 29 | BANCARD_ALLOWED_CURRENCIES = ["PYG"] 30 | 31 | BANCARD_BASE_URL_SANDBOX = "https://vpos.infonet.com.py:8888" 32 | BANCARD_BASE_URL_PRODUCTION = "https://vpos.infonet.com.py" 33 | 34 | # Keys for the SANDBOX_URLS / PRODUCTION_URLS dictionaries 35 | ROLLBACK_KEY = "rollback" 36 | CHARGE_TOKEN_GENERATOR_KEY = "single_buy" 37 | PAYMENT_WEB_URL_KEY = "payment" 38 | CONFIRMATIONS_KEY = "confirmations" 39 | 40 | # Bancard WebService development (sandbox) environment endpoints 41 | BANCARD_SANDBOX_URLS = { 42 | ROLLBACK_KEY: "%s/vpos/api/0.3/single_buy/rollback" % BANCARD_BASE_URL_SANDBOX, 43 | CHARGE_TOKEN_GENERATOR_KEY: "%s/vpos/api/0.3/single_buy" % BANCARD_BASE_URL_SANDBOX, 44 | PAYMENT_WEB_URL_KEY: "%s/payment/single_buy?process_id=" % BANCARD_BASE_URL_SANDBOX, 45 | CONFIRMATIONS_KEY: "%s/vpos/api/0.3/single_buy/confirmations" % BANCARD_BASE_URL_SANDBOX, 46 | } 47 | 48 | # Bancard WebService production environment endpoints 49 | BANCARD_PRODUCTION_URLS = { 50 | ROLLBACK_KEY: "%s/vpos/api/0.3/single_buy/rollback" % BANCARD_BASE_URL_PRODUCTION, 51 | CHARGE_TOKEN_GENERATOR_KEY: "%s/vpos/api/0.3/single_buy" % BANCARD_BASE_URL_PRODUCTION, 52 | PAYMENT_WEB_URL_KEY: "%s/payment/single_buy?process_id=" % BANCARD_BASE_URL_PRODUCTION, 53 | CONFIRMATIONS_KEY: "%s/vpos/api/0.3/single_buy/confirmations" % BANCARD_BASE_URL_PRODUCTION, 54 | } 55 | 56 | # All the Bancard WebService endpoints for sandbox/production 57 | BANCARD_URLS = { 58 | ENVIRONMENT_SANDBOX: BANCARD_SANDBOX_URLS, 59 | ENVIRONMENT_PRODUCTION: BANCARD_PRODUCTION_URLS 60 | } 61 | -------------------------------------------------------------------------------- /vpos/sdk/python/bancardconnectorpython/util.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | # 3 | # Copyright (c) [2018] [Victor Manuel Cajes Gonzalez - vcajes@gmail.com] 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. 22 | 23 | 24 | import sys 25 | from decimal import Decimal 26 | from bancardconnectorpython.exceptions import BancardAPIInvalidParameterException 27 | 28 | # number of decimals for an amount of a given currency 29 | CURRENCIES_DECIMALS = {"PYG": 0} 30 | 31 | 32 | def is_python_version_greater_igual_than_3x(): 33 | """ 34 | Returns True if the Python version that runs this library is greater or equal than 3.x 35 | 36 | :return: a boolean that states if the python version if >= 3.x 37 | :rtype bool 38 | """ 39 | 40 | return sys.version_info >= (3,) 41 | 42 | 43 | def merge_dict(first_dict, *next_dicts): 44 | """ 45 | Returns the merge of all the dictionaries received as input parameters. 46 | 47 | :param first_dict: one dictionary 48 | :type first_dict: dict 49 | :param next_dicts: list of dicionaries to be merged with first_dict 50 | :type next_dicts: dict 51 | :return: the merged dictionary 52 | :rtype dict 53 | """ 54 | 55 | result_dict = dict() 56 | for curr_dict in (first_dict,) + next_dicts: 57 | result_dict.update(curr_dict) 58 | return result_dict 59 | 60 | 61 | def currency_decimal_to_string(currency, decimal_value): 62 | """ 63 | Returns the amount in a string format depending on the number of decimals of a given currency. 64 | 65 | :param currency: the currency of the decimal_value 66 | :type currency: str 67 | :param decimal_value: the Decimal value 68 | :type decimal_value: Decimal 69 | :return: the string that represents the decimal_value with the proper number of decimals depending on the currency 70 | :rtype str 71 | """ 72 | 73 | if currency not in CURRENCIES_DECIMALS: 74 | raise BancardAPIInvalidParameterException("The currency is not allowed.") 75 | 76 | if not isinstance(decimal_value, Decimal): 77 | raise BancardAPIInvalidParameterException("The amount is not a Decimal value.") 78 | 79 | decimals = CURRENCIES_DECIMALS[currency] if currency in CURRENCIES_DECIMALS else 2 80 | ret = ("%." + str(decimals) + "f") % decimal_value 81 | return ret 82 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, gender identity and expression, level of experience, 9 | education, socio-economic status, nationality, personal appearance, race, 10 | religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at bancard@moove-it.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | -------------------------------------------------------------------------------- /vpos/sdk/python/tests/test_bancard_integration_testing.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | # 3 | # Copyright (c) [2018] [Victor Manuel Cajes Gonzalez - vcajes@gmail.com] 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. 22 | 23 | 24 | import sys 25 | import random 26 | import unittest 27 | from decimal import Decimal 28 | import bancardconnectorpython 29 | from selenium import webdriver 30 | from selenium.webdriver.support.ui import Select 31 | 32 | 33 | # bancard testing credit card values 34 | TEST_BANCARD_CREDITCARD_NUMBER = "4907860500000016" 35 | TEST_BANCARD_CREDITCARD_CVV = "599" 36 | TEST_BANCARD_EXPIRATION_MONTH = "08" 37 | TEST_BANCARD_EXPIRATION_YEAR = "21" 38 | 39 | 40 | class TestBancardSingleBuy(unittest.TestCase): 41 | 42 | def test_successful_payment(self): 43 | marketplace_charge_id = random.randrange(500000, sys.maxsize >> 8) 44 | amount, currency = Decimal(1000), "PYG" 45 | description = "Sample charge" 46 | approved_url = "http://localhost/redirect/bancard/%s/approved" % marketplace_charge_id 47 | cancelled_url = "http://localhost/redirect/bancard/%s/cancelled" % marketplace_charge_id 48 | 49 | # configure the bancard API connector from the environment variables and get a reference to the connector 50 | bancard_api = bancardconnectorpython.connector() 51 | 52 | bancard_process_id, payment_url, bancard_response = bancard_api.generate_charge_token(marketplace_charge_id, amount, description, approved_url, cancelled_url, currency) 53 | self.assertIsNotNone(bancard_process_id) 54 | self.assertGreater(len(bancard_process_id), 0) 55 | 56 | print(bancard_process_id) 57 | print(payment_url) 58 | 59 | driver = webdriver.Chrome() 60 | driver.get(payment_url) 61 | 62 | elem = driver.find_element_by_id("payment_credit_card_number") 63 | elem.clear() 64 | elem.send_keys(TEST_BANCARD_CREDITCARD_NUMBER) 65 | 66 | elem = driver.find_element_by_id("payment_ccv") 67 | elem.clear() 68 | elem.send_keys(TEST_BANCARD_CREDITCARD_CVV) 69 | 70 | select = Select(driver.find_element_by_id('payment_exp_month')) 71 | select.select_by_value(TEST_BANCARD_EXPIRATION_MONTH) 72 | 73 | select = Select(driver.find_element_by_id('payment_exp_year')) 74 | select.select_by_value(TEST_BANCARD_EXPIRATION_YEAR) 75 | 76 | elem = driver.find_element_by_class_name("payment-form") 77 | elem.submit() 78 | 79 | # this div class is shown only when an error occurred 80 | self.assertNotIn("alert-danger", driver.page_source) 81 | 82 | driver.close() 83 | 84 | 85 | if __name__ == '__main__': 86 | unittest.main() 87 | -------------------------------------------------------------------------------- /vpos/sdk/python/bancardconnectorpython/exceptions.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | # 3 | # Copyright (c) [2018] [Victor Manuel Cajes Gonzalez - vcajes@gmail.com] 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. 22 | 23 | 24 | class BancardAPIException(Exception): 25 | def __init__(self, msg="", data=None): 26 | """ 27 | Constructor of the BancardAPIException. 28 | 29 | :param msg: one dictionary 30 | :type msg: str 31 | :param data: any type of data to be passed in the exception. Typically would be a dict. 32 | :type data: dict 33 | """ 34 | self.msg = msg 35 | self.data = data 36 | 37 | def get_message(self): 38 | """ 39 | Returns the message of the exception. 40 | 41 | :return the message of the exception. 42 | :rtype str 43 | """ 44 | return self.msg 45 | 46 | def to_dict(self): 47 | """ 48 | Returns the dict representation of the exception. 49 | 50 | :return the dict representation of the exception 51 | :rtype dict 52 | """ 53 | return {"msg": self.msg, "data": self.data} 54 | 55 | def __str__(self): 56 | """ 57 | Returns the message of the exception when calling to the function str(BancardAPIException). 58 | 59 | :return the message of the exception. 60 | :rtype str 61 | """ 62 | return str(self.msg) 63 | 64 | 65 | # exceptions for the configuration of the BancardAPI 66 | class BancardAPIConfigurationException(BancardAPIException): 67 | pass 68 | 69 | 70 | # exceptions for the charge request operation 71 | class BancardAPIInvalidParameterException(BancardAPIException): 72 | pass 73 | 74 | 75 | class BancardAPIMarketplaceChargeIDAlreadyExistsException(BancardAPIException): 76 | pass 77 | 78 | 79 | class BancardAPIChargeRejectedException(BancardAPIException): 80 | pass 81 | 82 | 83 | class BancardAPIChargeInconsistentValuesException(BancardAPIException): 84 | pass 85 | 86 | 87 | # exceptions for the payment rejections 88 | class BancardAPIPaymentRejectecException(BancardAPIException): 89 | pass 90 | 91 | 92 | class BancardAPIPaymentMethodNotEnabledException(BancardAPIPaymentRejectecException): 93 | pass 94 | 95 | 96 | class BancardAPIPaymentTransactionInvalidException(BancardAPIPaymentRejectecException): 97 | pass 98 | 99 | 100 | class BancardAPIPaymentMethodNotEnoughFundsException(BancardAPIPaymentRejectecException): 101 | pass 102 | 103 | 104 | class BancardAPIPaymentRejectecUnknownReasonException(BancardAPIPaymentRejectecException): 105 | pass 106 | 107 | 108 | # exceptions for the roll-back operations 109 | class BancardAPINotRolledBackException(BancardAPIException): 110 | pass 111 | 112 | 113 | # exceptions for the bancard webhook 114 | class BancardAPIInvalidWebhookException(BancardAPIException): 115 | pass 116 | 117 | 118 | class BancardAPIInvalidWebhookDataException(BancardAPIException): 119 | pass 120 | 121 | 122 | class BancardAPIInvalidWebhookTokenException(BancardAPIException): 123 | pass 124 | -------------------------------------------------------------------------------- /vpos/sdk/python/tests/test_bancard_single_buy.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | # 3 | # Copyright (c) [2018] [Victor Manuel Cajes Gonzalez - vcajes@gmail.com] 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. 22 | 23 | 24 | import sys 25 | import random 26 | import unittest 27 | from decimal import Decimal 28 | import bancardconnectorpython 29 | 30 | 31 | class TestBancardSingleBuy(unittest.TestCase): 32 | 33 | def test_generate_token_ok(self): 34 | marketplace_charge_id = random.randrange(500000, sys.maxsize >> 8) 35 | amount, currency = Decimal(1000), "PYG" 36 | description = "Sample charge" 37 | approved_url = "http://localhost/redirect/bancard/%s/approved" % marketplace_charge_id 38 | cancelled_url = "http://localhost/redirect/bancard/%s/cancelled" % marketplace_charge_id 39 | 40 | # configure the bancard API connector from the environment variables and get a reference to the connector 41 | bancard_api = bancardconnectorpython.connector() 42 | 43 | bancard_process_id, payment_url, bancard_response = bancard_api.generate_charge_token(marketplace_charge_id, amount, description, approved_url, cancelled_url, currency) 44 | self.assertIsNotNone(bancard_process_id) 45 | self.assertGreater(len(bancard_process_id), 0) 46 | 47 | def test_duplicate_token_error(self): 48 | marketplace_charge_id = random.randrange(500000, sys.maxsize >> 8) 49 | amount, currency = Decimal(1000), "PYG" 50 | description = "Sample charge" 51 | approved_url = "http://localhost/redirect/bancard/%s/approved" % marketplace_charge_id 52 | cancelled_url = "http://localhost/redirect/bancard/%s/cancelled" % marketplace_charge_id 53 | 54 | # configure the bancard API connector from the environment variables and get a reference to the connector 55 | bancard_api = bancardconnectorpython.connector() 56 | 57 | bancard_process_id, payment_url, bancard_response = bancard_api.generate_charge_token(marketplace_charge_id, amount, description, approved_url, cancelled_url, currency) 58 | self.assertIsNotNone(bancard_process_id) 59 | self.assertGreater(len(bancard_process_id), 0) # the process id should contain at least one character 60 | 61 | # call again to the generate token with the same charge ID in order to get the proper exception 62 | self.assertRaises( 63 | bancardconnectorpython.BancardAPIMarketplaceChargeIDAlreadyExistsException, 64 | bancard_api.generate_charge_token, marketplace_charge_id, amount, description, approved_url, cancelled_url, currency) 65 | 66 | def test_invalid_amount_error(self): 67 | marketplace_charge_id = random.randrange(500000, sys.maxsize >> 8) 68 | amount, currency = Decimal(-1), "PYG" # invalid amount 69 | description = "Sample charge" 70 | approved_url = "http://localhost/redirect/bancard/%s/approved" % marketplace_charge_id 71 | cancelled_url = "http://localhost/redirect/bancard/%s/cancelled" % marketplace_charge_id 72 | 73 | # configure the bancard API connector from the environment variables and get a reference to the connector 74 | bancard_api = bancardconnectorpython.connector() 75 | 76 | # try to generate the token with an invalid amount 77 | self.assertRaises( 78 | bancardconnectorpython.BancardAPIInvalidParameterException, 79 | bancard_api.generate_charge_token, marketplace_charge_id, amount, description, approved_url, cancelled_url, currency) 80 | 81 | 82 | if __name__ == '__main__': 83 | unittest.main() 84 | -------------------------------------------------------------------------------- /vpos/sdk/python/README: -------------------------------------------------------------------------------- 1 | 2 | # Bancard VPOS v1.0 - Bancard Python Connector v0.5.4 library 3 | 4 | ## Getting Started 5 | 6 | This library allows developers to integrate their Python backend applications to the Bancard VPOS API. 7 | 8 | This library works with the following Python versions: 2.6, 2.7, 3.3, 3.4, 3.5, 3.6 9 | 10 | ### Prerequisites 11 | 12 | See the requirements.txt file to see which Python libraries will be required. 13 | 14 | ### Usage in Staging or Production 15 | 16 | You can either include this library from: 17 | 18 | ``` 19 | https://github.com/vcajes/bancard-connector-python 20 | ``` 21 | 22 | Or install the library from the [PYPI repository](https://pypi.python.org/pypi/bancardconnectorpython/): 23 | 24 | ``` 25 | pip3 install bancardconnectorpython 26 | ``` 27 | 28 | ### Usage in development 29 | 30 | * Download and install [Python (2.6 <= version <= 3.6)](https://www.python.org/downloads/). 31 | * Run `pip install bancardconnectorpython`. 32 | * Import and use library in your source code: 33 | ``` 34 | from decimal import Decimal 35 | import bancardconnectorpython 36 | 37 | # this will autoconfigure the connector from the following OS environment variables: 38 | # BANCARD_ENVIRONMENT=sandbox|production 39 | # BANCARD_PUBLIC_KEY=your_public_key 40 | # BANCARD_PRIVATE_KEY=your_private_key 41 | bancard_api = bancardconnectorpython.connector() 42 | 43 | # or you could just create your own BancardAPI 44 | bancard_api = bancardconnectorpython.BancardAPI(environment=bancardconnectorpython.ENVIRONMENT_SANDBOX, public_key=your_public_key, private_key=your_private_key) 45 | ``` 46 | 47 | ## Sample code - Bancard Single Buy 48 | 49 | ``` 50 | # bancard sample charge information 51 | marketplace_charge_id = "123" # your own custom charge ID 52 | amount = Decimal("1000") # the amount you want to charge 53 | currency = "PYG" # currently the only allowed currency by Bancard 54 | description = "Sample charge" # a message you want to show to the payer 55 | approved_url = "htpps://your-domain.com/webhooks/bancard/approved" # bancard will redirect to the payer to this URL after finishing the paymentcancelled_url = "htpps://your-domain.com/webhooks/bancard/cancelled" # bancard will redirect to the payer to this URL is the payment do not succeed 56 | cancelled_url = "htpps://your-domain.com/webhooks/bancard/cancelled" # bancard will redirect to the payer to this URL is the payment do not succeed 57 | 58 | try: 59 | # bancard_process_id is the charge ID that bancard generated 60 | # payment_url is the URL that you should show to the payer in case you want him to pay within the Bancard website 61 | # bancard_response is the JSON object that contains the exact response from bancard 62 | bancard_process_id, payment_url, bancard_response = bancard_api.generate_charge_token(marketplace_charge_id, amount, description, approved_url, cancelled_url, currency) 63 | except BancardAPIInvalidParameterException as bancard_error1: 64 | print(bancard_error1.msg) # message returned by Bancard if any 65 | print(bancard_error1.data) # JSON object that contains the exact response from bancard 66 | except BancardAPIChargeRejectedException as bancard_error2: 67 | print(bancard_error2.msg) # message returned by Bancard if any 68 | print(bancard_error2.data) # JSON object that contains the exact response from bancard 69 | except BancardAPIMarketplaceChargeIDAlreadyExistsException as bancard_error3: 70 | print(bancard_error3.msg) # message returned by Bancard if any 71 | print(bancard_error3.data) # JSON object that contains the exact response from bancard 72 | 73 | # the marketplace ID "123" was already used previously in Bancard, so you would want to check the status of that previous charge 74 | # already_payed is a True/False boolean that defines if the charge has already been payed by someone 75 | # authorization_number will be the string with the Bancard authorization code. This will be None if already_payed is False 76 | # bancard_response is the JSON object that contains the exact response from bancard 77 | already_payed, authorization_number, bancard_response = bancard_api.get_charge_status(marketplace_charge_id, amount, currency) 78 | except BancardAPIException as bancard_error4: 79 | print(bancard_error4.msg) # message returned by Bancard if any 80 | print(bancard_error4.data) # JSON object that contains the exact response from bancard 81 | ``` 82 | 83 | ## Sample code - Bancard Confirmations 84 | 85 | ``` 86 | # bancard sample confirmations call to obtain the status of the charge 87 | marketplace_charge_id = "123" # your own custom charge ID 88 | amount = Decimal("1000") # the amount you want to charge 89 | currency = "PYG" # currently the only allowed currency by Bancard 90 | 91 | try: 92 | # already_payed is a True/False boolean that defines if the charge has already been payed by someone 93 | # authorization_number will be the string with the Bancard authorization code. This will be None if already_payed is False 94 | # bancard_response is the JSON object that contains the exact response from bancard 95 | already_payed, authorization_number, bancard_response = bancard_api.get_charge_status(marketplace_charge_id, amount, currency) 96 | except BancardAPIInvalidParameterException as bancard_error1: 97 | print(bancard_error1.msg) # message returned by Bancard if any 98 | print(bancard_error1.data) # JSON object that contains the exact response from bancard 99 | except BancardAPIChargeInconsistentValuesException as bancard_error2: 100 | print(bancard_error2.msg) # message returned by Bancard if any 101 | print(bancard_error2.data) # JSON object that contains the exact response from bancard 102 | except BancardAPIPaymentMethodNotEnabledException as bancard_error3: 103 | print(bancard_error3.msg) # message returned by Bancard if any 104 | print(bancard_error3.data) # JSON object that contains the exact response from bancard 105 | except BancardAPIPaymentTransactionInvalidException as bancard_error4: 106 | print(bancard_error4.msg) # message returned by Bancard if any 107 | print(bancard_error4.data) # JSON object that contains the exact response from bancard 108 | except BancardAPIPaymentMethodNotEnoughFundsException as bancard_error5: 109 | print(bancard_error5.msg) # message returned by Bancard if any 110 | print(bancard_error5.data) # JSON object that contains the exact response from bancard 111 | except BancardAPIPaymentRejectecUnknownReasonException as bancard_error6: 112 | print(bancard_error6.msg) # message returned by Bancard if any 113 | print(bancard_error6.data) # JSON object that contains the exact response from bancard 114 | except BancardAPIPaymentRejectecUnknownReasonException as bancard_error7: 115 | print(bancard_error7.msg) # message returned by Bancard if any 116 | print(bancard_error7.data) # JSON object that contains the exact response from bancard 117 | except BancardAPIException as bancard_error8: 118 | print(bancard_error8.msg) # message returned by Bancard if any 119 | print(bancard_error8.data) # JSON object that contains the exact response from bancard 120 | ``` 121 | 122 | ## Sample code - Bancard Rollback 123 | 124 | ``` 125 | # bancard sample confirmations call to obtain the status of the charge 126 | marketplace_charge_id = "123" # your own custom charge ID 127 | 128 | try: 129 | # successfull_rollback is a True/False boolean that defines if the charge has been successfully rolled-back by Bancard 130 | # bancard_response is the JSON object that contains the exact response from bancard 131 | successfull_rollback, bancard_response = bancard_api.rollback_charge(marketplace_charge_id) 132 | except BancardAPIInvalidParameterException as bancard_error1: 133 | print(bancard_error1.msg) # message returned by Bancard if any 134 | print(bancard_error1.data) # JSON object that contains the exact response from bancard 135 | except BancardAPINotRolledBackException as bancard_error2: 136 | print(bancard_error2.msg) # message returned by Bancard if any 137 | print(bancard_error2.data) # JSON object that contains the exact response from bancard 138 | except BancardAPIException as bancard_error3: 139 | print(bancard_error3.msg) # message returned by Bancard if any 140 | print(bancard_error3.data) # JSON object that contains the exact response from bancard 141 | ``` 142 | 143 | ## Running tests 144 | 145 | * Download and install [Python (2.6 <= version <= 3.6)](https://www.python.org/downloads/) 146 | * Install the library from PYPI: `pip install bancardconnectorpython` 147 | * Set the following two OS environment variables `BANCARD_PUBLIC_KEY` and `BANCARD_PRIVATE_KEY` with the values provided by Bancard. 148 | * Run any of the tests, i.e.: `python /path/to/tests/test_bancard_single_buy.py ` 149 | 150 | ## Versioning 151 | 152 | For the versions available, see the [tags on this repository](https://github.com/vcajes/bancard-connector-python/tags) 153 | 154 | ## Authors 155 | 156 | * **Victor Cajes** - [@vcajes](https://github.com/vcajes) 157 | 158 | ## License 159 | 160 | This project is licensed under the MIT License - see the [LICENSE](LICENSE.txt) file for details. 161 | 162 | -------------------------------------------------------------------------------- /vpos/sdk/python/README.md: -------------------------------------------------------------------------------- 1 | 2 | # Bancard VPOS v1.0 - Bancard Python Connector v0.5.4 library 3 | 4 | ## Getting Started 5 | 6 | This library allows developers to integrate their Python backend applications to the Bancard VPOS API. 7 | 8 | This library works with the following Python versions: 2.6, 2.7, 3.3, 3.4, 3.5, 3.6 9 | 10 | ### Prerequisites 11 | 12 | See the requirements.txt file to see which Python libraries will be required. 13 | 14 | ### Usage in Staging or Production 15 | 16 | You can either include this library from: 17 | 18 | ``` 19 | https://github.com/vcajes/bancard-connector-python 20 | ``` 21 | 22 | Or install the library from the [PYPI repository](https://pypi.python.org/pypi/bancardconnectorpython/): 23 | 24 | ``` 25 | pip3 install bancardconnectorpython 26 | ``` 27 | 28 | ### Usage in development 29 | 30 | * Download and install [Python (2.6 <= version <= 3.6)](https://www.python.org/downloads/). 31 | * Run `pip install bancardconnectorpython`. 32 | * Import and use library in your source code: 33 | ``` 34 | from decimal import Decimal 35 | import bancardconnectorpython 36 | 37 | # this will autoconfigure the connector from the following OS environment variables: 38 | # BANCARD_ENVIRONMENT=sandbox|production 39 | # BANCARD_PUBLIC_KEY=your_public_key 40 | # BANCARD_PRIVATE_KEY=your_private_key 41 | bancard_api = bancardconnectorpython.connector() 42 | 43 | # or you could just create your own BancardAPI 44 | bancard_api = bancardconnectorpython.BancardAPI(environment=bancardconnectorpython.ENVIRONMENT_SANDBOX, public_key=your_public_key, private_key=your_private_key) 45 | ``` 46 | 47 | ## Sample code - Bancard Single Buy 48 | 49 | ``` 50 | # bancard sample charge information 51 | marketplace_charge_id = "123" # your own custom charge ID 52 | amount = Decimal("1000") # the amount you want to charge 53 | currency = "PYG" # currently the only allowed currency by Bancard 54 | description = "Sample charge" # a message you want to show to the payer 55 | approved_url = "htpps://your-domain.com/webhooks/bancard/approved" # bancard will redirect to the payer to this URL after finishing the paymentcancelled_url = "htpps://your-domain.com/webhooks/bancard/cancelled" # bancard will redirect to the payer to this URL is the payment do not succeed 56 | cancelled_url = "htpps://your-domain.com/webhooks/bancard/cancelled" # bancard will redirect to the payer to this URL is the payment do not succeed 57 | 58 | try: 59 | # bancard_process_id is the charge ID that bancard generated 60 | # payment_url is the URL that you should show to the payer in case you want him to pay within the Bancard website 61 | # bancard_response is the JSON object that contains the exact response from bancard 62 | bancard_process_id, payment_url, bancard_response = bancard_api.generate_charge_token(marketplace_charge_id, amount, description, approved_url, cancelled_url, currency) 63 | except BancardAPIInvalidParameterException as bancard_error1: 64 | print(bancard_error1.msg) # message returned by Bancard if any 65 | print(bancard_error1.data) # JSON object that contains the exact response from bancard 66 | except BancardAPIChargeRejectedException as bancard_error2: 67 | print(bancard_error2.msg) # message returned by Bancard if any 68 | print(bancard_error2.data) # JSON object that contains the exact response from bancard 69 | except BancardAPIMarketplaceChargeIDAlreadyExistsException as bancard_error3: 70 | print(bancard_error3.msg) # message returned by Bancard if any 71 | print(bancard_error3.data) # JSON object that contains the exact response from bancard 72 | 73 | # the marketplace ID "123" was already used previously in Bancard, so you would want to check the status of that previous charge 74 | # already_payed is a True/False boolean that defines if the charge has already been payed by someone 75 | # authorization_number will be the string with the Bancard authorization code. This will be None if already_payed is False 76 | # bancard_response is the JSON object that contains the exact response from bancard 77 | already_payed, authorization_number, bancard_response = bancard_api.get_charge_status(marketplace_charge_id, amount, currency) 78 | except BancardAPIException as bancard_error4: 79 | print(bancard_error4.msg) # message returned by Bancard if any 80 | print(bancard_error4.data) # JSON object that contains the exact response from bancard 81 | ``` 82 | 83 | ## Sample code - Bancard Confirmations 84 | 85 | ``` 86 | # bancard sample confirmations call to obtain the status of the charge 87 | marketplace_charge_id = "123" # your own custom charge ID 88 | amount = Decimal("1000") # the amount you want to charge 89 | currency = "PYG" # currently the only allowed currency by Bancard 90 | 91 | try: 92 | # already_payed is a True/False boolean that defines if the charge has already been payed by someone 93 | # authorization_number will be the string with the Bancard authorization code. This will be None if already_payed is False 94 | # bancard_response is the JSON object that contains the exact response from bancard 95 | already_payed, authorization_number, bancard_response = bancard_api.get_charge_status(marketplace_charge_id, amount, currency) 96 | except BancardAPIInvalidParameterException as bancard_error1: 97 | print(bancard_error1.msg) # message returned by Bancard if any 98 | print(bancard_error1.data) # JSON object that contains the exact response from bancard 99 | except BancardAPIChargeInconsistentValuesException as bancard_error2: 100 | print(bancard_error2.msg) # message returned by Bancard if any 101 | print(bancard_error2.data) # JSON object that contains the exact response from bancard 102 | except BancardAPIPaymentMethodNotEnabledException as bancard_error3: 103 | print(bancard_error3.msg) # message returned by Bancard if any 104 | print(bancard_error3.data) # JSON object that contains the exact response from bancard 105 | except BancardAPIPaymentTransactionInvalidException as bancard_error4: 106 | print(bancard_error4.msg) # message returned by Bancard if any 107 | print(bancard_error4.data) # JSON object that contains the exact response from bancard 108 | except BancardAPIPaymentMethodNotEnoughFundsException as bancard_error5: 109 | print(bancard_error5.msg) # message returned by Bancard if any 110 | print(bancard_error5.data) # JSON object that contains the exact response from bancard 111 | except BancardAPIPaymentRejectecUnknownReasonException as bancard_error6: 112 | print(bancard_error6.msg) # message returned by Bancard if any 113 | print(bancard_error6.data) # JSON object that contains the exact response from bancard 114 | except BancardAPIPaymentRejectecUnknownReasonException as bancard_error7: 115 | print(bancard_error7.msg) # message returned by Bancard if any 116 | print(bancard_error7.data) # JSON object that contains the exact response from bancard 117 | except BancardAPIException as bancard_error8: 118 | print(bancard_error8.msg) # message returned by Bancard if any 119 | print(bancard_error8.data) # JSON object that contains the exact response from bancard 120 | ``` 121 | 122 | ## Sample code - Bancard Rollback 123 | 124 | ``` 125 | # bancard sample confirmations call to obtain the status of the charge 126 | marketplace_charge_id = "123" # your own custom charge ID 127 | 128 | try: 129 | # successfull_rollback is a True/False boolean that defines if the charge has been successfully rolled-back by Bancard 130 | # bancard_response is the JSON object that contains the exact response from bancard 131 | successfull_rollback, bancard_response = bancard_api.rollback_charge(marketplace_charge_id) 132 | except BancardAPIInvalidParameterException as bancard_error1: 133 | print(bancard_error1.msg) # message returned by Bancard if any 134 | print(bancard_error1.data) # JSON object that contains the exact response from bancard 135 | except BancardAPINotRolledBackException as bancard_error2: 136 | print(bancard_error2.msg) # message returned by Bancard if any 137 | print(bancard_error2.data) # JSON object that contains the exact response from bancard 138 | except BancardAPIException as bancard_error3: 139 | print(bancard_error3.msg) # message returned by Bancard if any 140 | print(bancard_error3.data) # JSON object that contains the exact response from bancard 141 | ``` 142 | 143 | ## Running tests 144 | 145 | * Download and install [Python (2.6 <= version <= 3.6)](https://www.python.org/downloads/) 146 | * Install the library from PYPI: `pip install bancardconnectorpython` 147 | * Set the following two OS environment variables `BANCARD_PUBLIC_KEY` and `BANCARD_PRIVATE_KEY` with the values provided by Bancard. 148 | * Run any of the tests, i.e.: `python /path/to/tests/test_bancard_single_buy.py ` 149 | 150 | ## Versioning 151 | 152 | For the versions available, see the [tags on this repository](https://github.com/vcajes/bancard-connector-python/tags) 153 | 154 | ## Authors 155 | 156 | * **Victor Cajes** - [@vcajes](https://github.com/vcajes) 157 | 158 | ## License 159 | 160 | This project is licensed under the MIT License - see the [LICENSE](LICENSE.txt) file for details. 161 | 162 | -------------------------------------------------------------------------------- /vpos/checkout/javascript/src/bancard-checkout.js: -------------------------------------------------------------------------------- 1 | import exceptions from './bancard-checkout-exceptions'; 2 | import constants from './constants'; 3 | 4 | const CHECKOUT_IFRAME_URL = `${constants.BANCARD_URL}/checkout/new`; 5 | const CHECKOUT_EXAMPLE_IFRAME_URL = `${constants.BANCARD_URL}/checkout/static_example`; 6 | const NEW_CARD_IFRAME_URL = `${constants.BANCARD_URL}/checkout/register_card/new`; 7 | const CARD_EXAMPLE_IFRAME_URL = `${constants.BANCARD_URL}/checkout/static_example/register_card`; 8 | const ZIMPLE_IFRAME_URL = `${constants.BANCARD_URL}/checkout/zimple/new`; 9 | const ALLOWED_STYLES_URL = `${constants.BANCARD_URL}/checkout/allowed_styles`; 10 | const CONFIRMATION_IFRAME_URL = `${constants.BANCARD_URL}/alias_token/confirmation/new`; 11 | const PREAUTHORIZATION_IFRAME_URL = `${constants.BANCARD_URL}/checkout/preauthorization/new`; 12 | const CHARGE3DS_IFRAME_URL = `${constants.BANCARD_URL}/checkout/charge_3ds/new`; 13 | 14 | const Settings = { 15 | handler: 'default', 16 | }; 17 | 18 | const internalMethods = { 19 | redirect: (data) => { 20 | const { message, details, return_url: returnUrl } = data; 21 | 22 | let url = internalMethods.addParamToUrl(returnUrl, 'status', message); 23 | 24 | if (typeof details !== 'undefined') { 25 | url = internalMethods.addParamToUrl(url, 'description', details); 26 | } 27 | 28 | window.location.replace(url); 29 | }, 30 | 31 | updateMinHeight: (iframeHeight, divId) => { 32 | const iframe = document.querySelectorAll(`#${divId} iframe`)[0]; 33 | iframe.style.minHeight = `${iframeHeight}px`; 34 | }, 35 | 36 | setListener: (divId) => { 37 | window.addEventListener('message', e => internalMethods.responseHandler(e, divId)); 38 | }, 39 | 40 | responseHandler: (event, divId) => { 41 | if (event.origin !== constants.BANCARD_URL) { 42 | return; 43 | } 44 | 45 | if (typeof event.data.iframeHeight !== 'undefined') { 46 | internalMethods.updateMinHeight(event.data.iframeHeight, divId); 47 | return; 48 | } 49 | 50 | if (Settings.handler === 'default') { 51 | internalMethods.redirect(event.data); 52 | } else { 53 | Settings.handler(event.data); 54 | } 55 | }, 56 | 57 | addParamToUrl: (url, param, value) => { 58 | const lastUrlChar = url.slice(-1); 59 | const paramValue = `${param}=${value}`; 60 | let newUrl = url; 61 | 62 | if (['&', '?'].indexOf(lastUrlChar) > -1) { 63 | newUrl += paramValue; 64 | } else if (url.indexOf('?') > -1) { 65 | newUrl = `${newUrl}&${paramValue}`; 66 | } else { 67 | newUrl = `${newUrl}?${paramValue}`; 68 | } 69 | 70 | return newUrl; 71 | }, 72 | 73 | request: async (method, url) => { 74 | const response = await fetch(url, { method }); 75 | const data = await response.json(); 76 | 77 | return data; 78 | }, 79 | 80 | validateStyles: (styles) => { 81 | internalMethods 82 | .request('GET', ALLOWED_STYLES_URL) 83 | .then((data) => { 84 | const allowedStyles = data.allowed_styles; 85 | 86 | internalMethods.checkInvalidStyles(allowedStyles, styles); 87 | }); 88 | }, 89 | 90 | checkInvalidStyles: (allowedStyles, styles) => { 91 | const stylesNames = Object.keys(styles); 92 | 93 | stylesNames.forEach((styleName) => { 94 | if (typeof allowedStyles[styleName] === 'undefined') { 95 | console.warn(`Invalid Style Object: the style ${styleName} is not allowed`); 96 | } else { 97 | let showWarning = false; 98 | 99 | if (allowedStyles[styleName] === 'color') { 100 | if (styles[styleName].match(/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/) == null) { 101 | showWarning = true; 102 | } 103 | } else if (!['true', 'false', true, false].includes(styles[styleName])) { 104 | showWarning = true; 105 | } 106 | 107 | if (showWarning) { 108 | console.warn(`Invalid Value: the value ${styles[styleName]} for the style ${styleName} is not valid.`); 109 | } 110 | } 111 | }); 112 | }, 113 | 114 | initializeIframe: (divId, iFrameUrl, options) => { 115 | if (typeof divId !== 'string' || divId === '') { 116 | throw new exceptions.InvalidParameter('Div id'); 117 | } 118 | 119 | const iframeContainer = window.document.getElementById(divId); 120 | 121 | if (!iframeContainer) { 122 | throw new exceptions.DivDoesNotExist(divId); 123 | } 124 | 125 | let newIframeUrl = iFrameUrl; 126 | 127 | if (typeof options !== 'undefined') { 128 | if (typeof options.styles !== 'undefined') { 129 | internalMethods.validateStyles(options.styles); 130 | 131 | const styles = encodeURIComponent(JSON.stringify(options.styles)); 132 | newIframeUrl = internalMethods.addParamToUrl(newIframeUrl, 'styles', styles); 133 | } 134 | 135 | if (typeof options.responseHandler !== 'undefined') { 136 | Settings.handler = options.responseHandler; 137 | } 138 | } 139 | 140 | const iframe = window.document.createElement('iframe'); 141 | 142 | iframe.src = newIframeUrl; 143 | iframe.style.width = '100%'; 144 | iframe.style.borderWidth = '0px'; 145 | iframe.allow = 'web-share'; 146 | 147 | iframeContainer.innerHTML = ''; 148 | iframeContainer.appendChild(iframe); 149 | 150 | internalMethods.setListener(divId); 151 | }, 152 | 153 | createForm: ({ 154 | divId, processId, options, url, 155 | }) => { 156 | if (typeof processId !== 'string' || processId === '') { 157 | throw new exceptions.InvalidParameter('Process id'); 158 | } 159 | 160 | const iFrameUrl = internalMethods.addParamToUrl(url, 'process_id', processId); 161 | 162 | internalMethods.initializeIframe(divId, iFrameUrl, options); 163 | }, 164 | 165 | createStaticForm: ({ 166 | divId, applicationId, options, url, 167 | }) => { 168 | if (typeof applicationId !== 'string' || applicationId === '') { 169 | throw new exceptions.InvalidParameter('Application id'); 170 | } 171 | 172 | const iFrameUrl = internalMethods.addParamToUrl(url, 'application_id', applicationId); 173 | 174 | internalMethods.initializeIframe(divId, iFrameUrl, options); 175 | }, 176 | 177 | loadPinPad: ({ 178 | divId, aliasToken, options, url, 179 | }) => { 180 | if (typeof aliasToken !== 'string' || aliasToken === '') { 181 | throw new exceptions.InvalidParameter('Alias token'); 182 | } 183 | 184 | const iFrameUrl = internalMethods.addParamToUrl(url, 'alias_token', aliasToken); 185 | 186 | internalMethods.initializeIframe(divId, iFrameUrl, options); 187 | }, 188 | 189 | clearElement: (element) => { 190 | while (element.firstChild) { 191 | element.removeChild(element.firstChild); 192 | } 193 | }, 194 | }; 195 | 196 | class Bancard { 197 | get Checkout() { 198 | return { 199 | createForm: (divId, processId, options) => { 200 | this.divId = divId; 201 | internalMethods.createForm({ 202 | divId, processId, options, url: CHECKOUT_IFRAME_URL, 203 | }); 204 | }, 205 | }; 206 | } 207 | 208 | get CheckoutExample() { 209 | return { 210 | createStaticForm: (divId, applicationId, options) => { 211 | this.divId = divId; 212 | internalMethods.createStaticForm({ 213 | divId, applicationId, options, url: CHECKOUT_EXAMPLE_IFRAME_URL, 214 | }); 215 | }, 216 | }; 217 | } 218 | 219 | get Cards() { 220 | return { 221 | createForm: (divId, processId, options) => { 222 | this.divId = divId; 223 | internalMethods.createForm({ 224 | divId, processId, options, url: NEW_CARD_IFRAME_URL, 225 | }); 226 | }, 227 | }; 228 | } 229 | 230 | get CardsExample() { 231 | return { 232 | createStaticForm: (divId, applicationId, options) => { 233 | this.divId = divId; 234 | internalMethods.createStaticForm({ 235 | divId, applicationId, options, url: CARD_EXAMPLE_IFRAME_URL, 236 | }); 237 | }, 238 | }; 239 | } 240 | 241 | get Zimple() { 242 | return { 243 | createForm: (divId, processId, options) => { 244 | this.divId = divId; 245 | internalMethods.createForm({ 246 | divId, processId, options, url: ZIMPLE_IFRAME_URL, 247 | }); 248 | }, 249 | }; 250 | } 251 | 252 | get Confirmation() { 253 | return { 254 | loadPinPad: (divId, aliasToken, options) => { 255 | this.divId = divId; 256 | internalMethods.loadPinPad({ 257 | divId, aliasToken, options, url: CONFIRMATION_IFRAME_URL, 258 | }); 259 | }, 260 | }; 261 | } 262 | 263 | get Preauthorization() { 264 | return { 265 | createForm: (divId, processId, options) => { 266 | this.divId = divId; 267 | internalMethods.createForm({ 268 | divId, processId, options, url: PREAUTHORIZATION_IFRAME_URL, 269 | }); 270 | }, 271 | }; 272 | } 273 | 274 | get Charge3DS() { 275 | return { 276 | createForm: (divId, processId, options) => { 277 | this.divId = divId; 278 | internalMethods.createForm({ 279 | divId, processId, options, url: CHARGE3DS_IFRAME_URL, 280 | }); 281 | }, 282 | }; 283 | } 284 | 285 | destroy() { 286 | const iframeContainer = window.document.getElementById(this.divId); 287 | 288 | window.removeEventListener('message', internalMethods.responseHandler); 289 | 290 | if (iframeContainer) { 291 | internalMethods.clearElement(iframeContainer); 292 | } 293 | 294 | this.divId = null; 295 | } 296 | } 297 | 298 | export default Bancard; 299 | -------------------------------------------------------------------------------- /vpos/checkout/javascript/src/specs/bancard-checkout.test.js: -------------------------------------------------------------------------------- 1 | import Bancard from '../bancard-checkout'; 2 | import exceptions from '../bancard-checkout-exceptions'; 3 | import constants from '../constants'; 4 | 5 | describe('Bancard', () => { 6 | let instance; 7 | beforeEach(() => { instance = new Bancard(); }); 8 | 9 | describe('Initialize iFrame', () => { 10 | beforeEach(() => { 11 | document.body.innerHTML = '
'; 12 | }); 13 | 14 | describe('Checkout', () => { 15 | beforeEach(() => { 16 | instance.Checkout.createForm('targetDiv', '1234'); 17 | window.location.replace = jest.fn(); 18 | }); 19 | 20 | afterEach(() => { instance.destroy(); }); 21 | 22 | test('It creates the iframe', () => { 23 | expect(document.querySelectorAll('iframe').length).toBe(1); 24 | }); 25 | 26 | test('Iframe points to correct URL', () => { 27 | expect(document.querySelectorAll('iframe')[0].getAttribute('src')) 28 | .toBe('https://desa.infonet.com.py:8085/checkout/new?process_id=1234'); 29 | }); 30 | }); 31 | 32 | describe('CheckoutExample', () => { 33 | beforeEach(() => { 34 | instance.CheckoutExample.createStaticForm('targetDiv', '1234'); 35 | window.location.replace = jest.fn(); 36 | }); 37 | 38 | afterEach(() => { instance.destroy(); }); 39 | 40 | test('It creates the iframe', () => { 41 | expect(document.querySelectorAll('iframe').length).toBe(1); 42 | }); 43 | 44 | test('Iframe points to correct URL', () => { 45 | expect(document.querySelectorAll('iframe')[0].getAttribute('src')) 46 | .toBe('https://desa.infonet.com.py:8085/checkout/static_example?application_id=1234'); 47 | }); 48 | }); 49 | 50 | describe('Cards', () => { 51 | beforeEach(() => { 52 | instance.Cards.createForm('targetDiv', '1234'); 53 | window.location.replace = jest.fn(); 54 | }); 55 | 56 | afterEach(() => { instance.destroy(); }); 57 | 58 | test('It creates the iframe', () => { 59 | expect(document.querySelectorAll('iframe').length).toBe(1); 60 | }); 61 | 62 | test('Iframe points to correct URL', () => { 63 | expect(document.querySelectorAll('iframe')[0].getAttribute('src')) 64 | .toBe('https://desa.infonet.com.py:8085/checkout/register_card/new?process_id=1234'); 65 | }); 66 | }); 67 | 68 | describe('CardsExample', () => { 69 | beforeEach(() => { 70 | instance.CardsExample.createStaticForm('targetDiv', '1234'); 71 | window.location.replace = jest.fn(); 72 | }); 73 | 74 | afterEach(() => { instance.destroy(); }); 75 | 76 | test('It creates the iframe', () => { 77 | expect(document.querySelectorAll('iframe').length).toBe(1); 78 | }); 79 | 80 | test('Iframe points to correct URL', () => { 81 | expect(document.querySelectorAll('iframe')[0].getAttribute('src')) 82 | .toBe('https://desa.infonet.com.py:8085/checkout/static_example/register_card?application_id=1234'); 83 | }); 84 | }); 85 | 86 | describe('Zimple', () => { 87 | beforeEach(() => { 88 | instance.Zimple.createForm('targetDiv', '1234'); 89 | window.location.replace = jest.fn(); 90 | }); 91 | 92 | afterEach(() => { instance.destroy(); }); 93 | 94 | test('It creates the iframe', () => { 95 | expect(document.querySelectorAll('iframe').length).toBe(1); 96 | }); 97 | 98 | test('Iframe points to correct URL', () => { 99 | expect(document.querySelectorAll('iframe')[0].getAttribute('src')) 100 | .toBe('https://desa.infonet.com.py:8085/checkout/zimple/new?process_id=1234'); 101 | }); 102 | }); 103 | 104 | describe('Confirmation', () => { 105 | beforeEach(() => { 106 | instance.Confirmation.loadPinPad('targetDiv', '1234'); 107 | window.location.replace = jest.fn(); 108 | }); 109 | 110 | afterEach(() => { instance.destroy(); }); 111 | 112 | test('It creates the iframe', () => { 113 | expect(document.querySelectorAll('iframe').length).toBe(1); 114 | }); 115 | 116 | test('Iframe points to correct URL', () => { 117 | expect(document.querySelectorAll('iframe')[0].getAttribute('src')) 118 | .toBe('https://desa.infonet.com.py:8085/alias_token/confirmation/new?alias_token=1234'); 119 | }); 120 | }); 121 | 122 | describe('When valid div', () => { 123 | beforeEach(() => { 124 | instance.Checkout.createForm('targetDiv', '1234'); 125 | window.location.replace = jest.fn(); 126 | }); 127 | 128 | afterEach(() => { instance.destroy(); }); 129 | 130 | test('It redirects to correct URL', (done) => { 131 | constants.BANCARD_URL = ''; 132 | const url = 'http://example.com'; 133 | const message = 'sample'; 134 | 135 | window.addEventListener('message', () => { 136 | expect(window.location.replace).toBeCalledWith(`${url}?status=${message}`); 137 | done(); 138 | }); 139 | 140 | window.postMessage({ return_url: url, message }, '*'); 141 | }); 142 | 143 | describe('When invalid styles', () => { 144 | const customStyles = { 145 | 'wrong-style': '#FFFFFF', 146 | 'header-text-color': '#FFFFFF', 147 | 'header-show': 'wrong-value', 148 | }; 149 | 150 | const options = { styles: customStyles }; 151 | 152 | beforeEach(() => { instance.Checkout.createForm('targetDiv', '1234', options); }); 153 | 154 | afterEach(() => { instance.destroy(); }); 155 | 156 | const allowedStyles = { 157 | 'header-background-color': 'color', 158 | 'header-text-color': 'color', 159 | 'header-show': 'boolean', 160 | }; 161 | 162 | global.console = { warn: jest.fn() }; 163 | fetch.mockResponse(JSON.stringify({ allowed_styles: allowedStyles })); 164 | 165 | test('It throws a warning', () => { 166 | expect(global.console.warn) 167 | .toHaveBeenCalledWith('Invalid Value: the value wrong-value for the style header-show is not valid.'); 168 | expect(global.console.warn) 169 | .toHaveBeenCalledWith('Invalid Style Object: the style wrong-style is not allowed'); 170 | }); 171 | }); 172 | 173 | describe('When destroying the library', () => { 174 | test("It's correctly destroyed", () => { 175 | instance.destroy(); 176 | 177 | expect(document.querySelectorAll('iframe').length).toBe(0); 178 | }); 179 | 180 | test("Calling destroy twice doesn't break the page", () => { 181 | instance.destroy(); 182 | instance.destroy(); 183 | }); 184 | 185 | test('It can be reinitialized correctly', () => { 186 | instance.Checkout.createForm('targetDiv', '1234'); 187 | 188 | expect(document.querySelectorAll('iframe').length).toBe(1); 189 | }); 190 | }); 191 | }); 192 | 193 | describe('Preauthorization', () => { 194 | beforeEach(() => { 195 | instance.Preauthorization.createForm('targetDiv', '1234'); 196 | window.location.replace = jest.fn(); 197 | }); 198 | 199 | afterEach(() => { instance.destroy(); }); 200 | 201 | test('It creates the iframe', () => { 202 | expect(document.querySelectorAll('iframe').length).toBe(1); 203 | }); 204 | 205 | test('Iframe points to correct URL', () => { 206 | expect(document.querySelectorAll('iframe')[0].getAttribute('src')) 207 | .toBe('https://desa.infonet.com.py:8085/checkout/preauthorization/new?process_id=1234'); 208 | }); 209 | }); 210 | 211 | describe('Charge3DS', () => { 212 | beforeEach(() => { 213 | instance.Charge3DS.createForm('targetDiv', '1234'); 214 | window.location.replace = jest.fn(); 215 | }); 216 | 217 | afterEach(() => { instance.destroy(); }); 218 | 219 | test('It creates the iframe', () => { 220 | expect(document.querySelectorAll('iframe').length).toBe(1); 221 | }); 222 | 223 | test('Iframe points to correct URL', () => { 224 | expect(document.querySelectorAll('iframe')[0].getAttribute('src')) 225 | .toBe('https://desa.infonet.com.py:8085/checkout/charge_3ds/new?process_id=1234'); 226 | }); 227 | }); 228 | }); 229 | 230 | describe('When the div does not exist', () => { 231 | afterEach(() => { instance.destroy(); }); 232 | 233 | test('It throws exception', () => { 234 | expect(() => { instance.Checkout.createForm('nonexistentDiv', '1234'); }) 235 | .toThrowError(exceptions.DivDoesNotExist); 236 | }); 237 | }); 238 | 239 | describe('When invalid process id', () => { 240 | afterEach(() => { instance.destroy(); }); 241 | 242 | test('It throws exception', () => { 243 | expect(() => { instance.Checkout.createForm('targetDiv', ''); }) 244 | .toThrowError(exceptions.InvalidParameter); 245 | }); 246 | 247 | test('It throws exception', () => { 248 | expect(() => { instance.Checkout.createForm('targetDiv', 23); }) 249 | .toThrowError(exceptions.InvalidParameter); 250 | }); 251 | }); 252 | 253 | describe('When invalid alias token', () => { 254 | afterEach(() => { instance.destroy(); }); 255 | 256 | test('It throws exception', () => { 257 | expect(() => { instance.Confirmation.loadPinPad('targetDiv', ''); }) 258 | .toThrowError(exceptions.InvalidParameter); 259 | }); 260 | 261 | test('It throws exception', () => { 262 | expect(() => { instance.Confirmation.loadPinPad('targetDiv', 23); }) 263 | .toThrowError(exceptions.InvalidParameter); 264 | }); 265 | }); 266 | }); 267 | -------------------------------------------------------------------------------- /vpos/sdk/python/bancardconnectorpython/api.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | # 3 | # Copyright (c) [2018] [Victor Manuel Cajes Gonzalez - vcajes@gmail.com] 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. 22 | 23 | 24 | import os 25 | import json 26 | import hashlib 27 | import requests 28 | from bancardconnectorpython.constants import * 29 | from bancardconnectorpython.util import * 30 | from bancardconnectorpython.exceptions import * 31 | 32 | 33 | class BancardAPI(object): 34 | 35 | def __init__(self, options=None, **kwargs): 36 | """ 37 | Constructor of the BancardAPI class. 38 | 39 | :param options: Dictionary with configuration parameters for the API 40 | :type options: dict 41 | :param kwargs: Dictionary with any extra parameters that could be unpacked for the API. 42 | The required key-value pairs are: 43 | * public_key: the public key given by Bancard 44 | * private_key: the private key given by Bancard 45 | The optional key-value pair is: 46 | * environment: one the following two string constants: "sandbox" or "production". The default value is: "sandbox". 47 | :type kwargs: dict 48 | :raises BancardAPIConfigurationException: if the merge of options and kwargs does not contains the keys: environment public_key private_key 49 | """ 50 | 51 | try: 52 | self.options = merge_dict(options or {}, kwargs) 53 | self.environment = self.options.get("environment", ENVIRONMENT_SANDBOX) # by default the sandbox environment 54 | self.public_key = self.options["public_key"] # mandatory, raise exception if missing 55 | self.private_key = self.options["private_key"] # mandatory, raise exception if missing 56 | self.urls = BANCARD_URLS[self.environment] 57 | except (KeyError, ValueError, TypeError): 58 | raise BancardAPIConfigurationException("The configuration parameters for the BancardAPI are not valid.") 59 | 60 | @staticmethod 61 | def __call_bancard_webservice(params, wsurl): 62 | """ 63 | Sends the JSON params object to the WSURL of Bancard and returns the JSON parsed response 64 | :param params: values to send to the Bancard API 65 | :param wsurl: URL of the Bancard WebService 66 | :return the JSON object obtained after parsing the Bancard response 67 | """ 68 | bancard_body_request = json.dumps(params) if type(params) is dict else (params if type(params) is str else str(params)) 69 | headers = {"Content-Type": "application/json"} 70 | response = requests.post(wsurl, data=bancard_body_request, headers=headers) 71 | bancard_response = json.loads(response.content.decode("utf-8")) if response.content else dict() 72 | return bancard_response 73 | 74 | @staticmethod 75 | def validate_marketplace_charge_id(marketplace_charge_id): 76 | """ 77 | Validates that the marketplace_charge_id can be converted to an integer value (as required by the Bancard docs) 78 | :param marketplace_charge_id: values to send to the Bancard API 79 | :raises BancardAPIInvalidParameterException: if the marketplace_charge_id does not contains a valid value 80 | """ 81 | 82 | try: 83 | int(marketplace_charge_id) 84 | except (TypeError, ValueError): 85 | raise BancardAPIInvalidParameterException("The marketplace charge ID is required and must be a valid integer.") 86 | 87 | @staticmethod 88 | def validate_currency(currency): 89 | """ 90 | Validates that the currency belong to the allowed ones by Bancard (as required by the Bancard docs) 91 | :param currency: string that represents the currency to send to the Bancard API 92 | :raises BancardAPIInvalidParameterException: if the currency does not contains a valid value 93 | """ 94 | if type(currency) is not str or currency not in BANCARD_ALLOWED_CURRENCIES: 95 | raise BancardAPIInvalidParameterException("The currency must be any of the following strings: %s" % BANCARD_ALLOWED_CURRENCIES) 96 | 97 | @staticmethod 98 | def validate_amount(amount): 99 | """ 100 | Validates that the amount is a Decimal object greater than zero (as required by the Bancard docs) 101 | :param amount: Decimal value that represents the amount to send to the Bancard API 102 | :raises BancardAPIInvalidParameterException: if the amount does not contains a valid value 103 | """ 104 | if not isinstance(amount, Decimal) or amount <= Decimal(0): 105 | raise BancardAPIInvalidParameterException("The amount must be a decimal greater than Decimal(0).") 106 | 107 | @staticmethod 108 | def validate_description(description): 109 | """ 110 | Validates that the description has a minimum/maximum length (as required by the Bancard docs) 111 | :param description: string value that represents the charge description to send to the Bancard API 112 | :raises BancardAPIInvalidParameterException: if the description does not contains a valid value 113 | """ 114 | if type(description) is not str or not 0 <= len(description) <= 20: 115 | raise BancardAPIInvalidParameterException("The description must be a string between [0,20] characters.") 116 | 117 | @staticmethod 118 | def validate_approved_url(approved_url): 119 | """ 120 | Validates that the approved_url has a maximum length (as required by the Bancard docs) 121 | :param approved_url: string that represents the charge approved URL to send to the Bancard API 122 | :raises BancardAPIInvalidParameterException: if the approved_url does not contains a valid value 123 | """ 124 | if type(approved_url) is not str or not 1 <= len(approved_url) <= 255: 125 | raise BancardAPIInvalidParameterException("The approved_url must be a valid URL string containing [1,255] characters.") 126 | 127 | @staticmethod 128 | def validate_cancelled_url(cancelled_url): 129 | """ 130 | Validates that the cancelled_url parameter has a maximum length (as required by the Bancard docs) 131 | :param cancelled_url: string that represents the charge cancelled URL to send to the Bancard API 132 | :raises BancardAPIInvalidParameterException: if the cancelled_url does not contains a valid value 133 | """ 134 | if type(cancelled_url) is not str or not 1 <= len(cancelled_url) <= 255: 135 | raise BancardAPIInvalidParameterException("The cancelled_url must be a valid URL string containing [1,255] characters.") 136 | 137 | def generate_charge_token(self, marketplace_charge_id, amount, description, approved_url, cancelled_url, currency="PYG"): 138 | """ 139 | Generates a Bancard Proces ID Token so the end-user payer could pay later. 140 | 141 | :param marketplace_charge_id: The marketplace's custom ID of this charge request 142 | :type marketplace_charge_id: int or str 143 | :param amount: The amount that the payer should pay 144 | :type amount: Decimal 145 | :param description: The text message that the payer will see while making the payment 146 | :type description: str 147 | :param approved_url: The URL to which Bancard will redirect to the payer after the payer sucessfully completes the payment 148 | :type approved_url: str 149 | :param cancelled_url: The URL to which Bancard will redirect to the payer when the payment is not completed successfully for some reason 150 | :type cancelled_url: str 151 | :param currency: The currency of the amount to charge in the format ISO-4217 152 | :type currency: str 153 | :return: a tuple of: bancard_process_id, payment_url, bancard_response 154 | :rtype tuple (str, str, dict) 155 | :raises BancardAPIInvalidParameterException: if any of the input parameters is not valid 156 | :raises BancardAPIMarketplaceChargeIDAlreadyExistsException: if there is already another charge request in Bancard with the same marketplace_charge_id 157 | :raises BancardAPIChargeRejectedException: if Bancard rejected the process id generation request 158 | """ 159 | 160 | BancardAPI.validate_marketplace_charge_id(marketplace_charge_id) 161 | BancardAPI.validate_amount(amount) 162 | BancardAPI.validate_description(description) 163 | BancardAPI.validate_approved_url(approved_url) 164 | BancardAPI.validate_cancelled_url(cancelled_url) 165 | BancardAPI.validate_currency(currency) 166 | 167 | amount_str = "%s.00" % currency_decimal_to_string(currency, amount) 168 | 169 | bancard_token = "%s%s%s%s" % (self.private_key, marketplace_charge_id, amount_str, currency) 170 | if is_python_version_greater_igual_than_3x(): 171 | bancard_token = bytes(bancard_token, "UTF-8") 172 | 173 | bancard_body_request = { 174 | "public_key": self.public_key, 175 | "operation": { 176 | "token": hashlib.md5(bancard_token).hexdigest(), 177 | "shop_process_id": str(marketplace_charge_id), 178 | "currency": currency, 179 | "amount": amount_str, 180 | "additional_data": "", 181 | "description": description, 182 | "return_url": approved_url, 183 | "cancel_url": cancelled_url 184 | } 185 | } 186 | 187 | bancard_response = BancardAPI.__call_bancard_webservice(bancard_body_request, self.urls[CHARGE_TOKEN_GENERATOR_KEY]) 188 | if bancard_response.get("status", None) == "success": 189 | # build the payment URL that should be opener to the payer 190 | bancard_process_id = str(bancard_response["process_id"]) 191 | payment_url = "%s%s" % (self.urls[PAYMENT_WEB_URL_KEY], bancard_process_id) 192 | 193 | # return the bancard_process_id, payment_url and the full bancard json response 194 | return bancard_process_id, payment_url, bancard_response 195 | 196 | # in any other case Bancard did not create the charge process ID 197 | bancard_msg_key, bancard_msg_error = "[NoBancardErrorKey]", "[NoBancardErrorMessage]" 198 | bancard_tx_messages = bancard_response.get('messages', list()) 199 | if type(bancard_tx_messages) is list: 200 | for bancard_msg_item in bancard_tx_messages: 201 | bancard_msg_key = bancard_msg_item.get("key", "[NoErrorKey]") 202 | bancard_msg_error = bancard_msg_item.get("dsc", "[NoBancardErrorMessage]") 203 | 204 | # check if the error was due to a repeated marketplace charge ID 205 | if bancard_msg_key == 'InvalidOperationError' and "process has already been taken" in bancard_msg_error: 206 | raise BancardAPIMarketplaceChargeIDAlreadyExistsException( 207 | "The marketplace charge ID %s already exists in Bancard. Check the charge status with the Bancard CONFIRMATION WebService." % marketplace_charge_id, bancard_response) 208 | 209 | # by default the charge process id was not generated by bancard 210 | raise BancardAPIChargeRejectedException(bancard_msg_error, bancard_response) 211 | 212 | def get_charge_status(self, marketplace_charge_id, amount, currency="PYG"): 213 | """ 214 | Calls to the confirmations Bancard WebService to check the status of the charge request in order to know if it is pending/payed/rejected/rolledback. 215 | 216 | :param marketplace_charge_id: The marketplace's custom ID of this charge request 217 | :type marketplace_charge_id: int or str 218 | :param amount: The amount that the payer should pay 219 | :type amount: Decimal 220 | :param currency: The currency of the amount to charge in the format ISO-4217 221 | :type currency: str 222 | :return: a tuple of: already_payed, authorization_number, bancard_response 223 | :rtype tuple (bool, str, dict) 224 | :raises BancardAPIInvalidParameterException: if any of the input parameters is not valid 225 | :raises BancardAPIChargeInconsistentValuesException: if the payment of marketplace_charge_id has been payed but does not match the currency/amount parameters 226 | :raises BancardAPIPaymentMethodNotEnabledException: if Bancard rejeted the payment because the payer's payment method was not enabled for making payments 227 | :raises BancardAPIPaymentTransactionInvalidException: if the payment has been rejected by Bancard because the transaction was not valid for some reason 228 | :raises BancardAPIPaymentMethodNotEnoughFundsException: if the payment has been rejected by Bancard because the payment method did not have enough funds 229 | :raises BancardAPIPaymentRejectecUnknownReasonException: if the payment has been rejected by Bancard due to an unhandled/unknown reason 230 | """ 231 | 232 | BancardAPI.validate_marketplace_charge_id(marketplace_charge_id) 233 | BancardAPI.validate_amount(amount) 234 | BancardAPI.validate_currency(currency) 235 | 236 | amount_str = "%s.00" % currency_decimal_to_string(currency, amount) 237 | 238 | bancard_token = "%s%s%s" % (self.private_key, marketplace_charge_id, "get_confirmation") 239 | if is_python_version_greater_igual_than_3x(): 240 | bancard_token = bytes(bancard_token, "UTF-8") 241 | 242 | bancard_body_request = { 243 | "public_key": self.public_key, 244 | "operation": { 245 | "token": hashlib.md5(bancard_token).hexdigest(), 246 | "shop_process_id": marketplace_charge_id 247 | } 248 | } 249 | 250 | bancard_response = BancardAPI.__call_bancard_webservice(bancard_body_request, self.urls[CONFIRMATIONS_KEY]) 251 | if bancard_response.get("status", None) == "success": 252 | confirmation = bancard_response.get("confirmation", dict()) 253 | response_code = confirmation.get("response_code", None) 254 | if response_code == '00': 255 | if Decimal(confirmation["amount"]) == Decimal(amount_str) and confirmation["currency"] == currency: 256 | return True, confirmation.get("authorization_number", None), bancard_response 257 | else: 258 | # this marketplace charge id was found in Bancard but is has another amount/currency 259 | raise BancardAPIChargeInconsistentValuesException("Duplicated charge ID %s in Bancard rejected due to inconsistency in amount/currency", bancard_response) 260 | else: 261 | # check if a specific error message has been received 262 | if response_code in ['05', '15']: 263 | raise BancardAPIPaymentMethodNotEnabledException("Bancard reported that the payer's payment method was not enabled for making payments.", bancard_response) 264 | elif response_code == '12': 265 | raise BancardAPIPaymentTransactionInvalidException("Bancard reported that the transaction is not valid.", bancard_response) 266 | elif response_code == '51': 267 | raise BancardAPIPaymentMethodNotEnoughFundsException("Bancard reported that your payment card does not have enough funds.", bancard_response) 268 | 269 | raise BancardAPIPaymentRejectecUnknownReasonException("Bancard reported that the payment has been rejected.", bancard_response) 270 | 271 | bancard_tx_messages = bancard_response.get('messages')[0] 272 | if bancard_tx_messages.get("key", None) == 'PaymentNotFoundError': 273 | # remember that you, as a marketplace, should rollback this transaction if the payer did not confirm this payment within 10 minutes 274 | return False, None, bancard_response 275 | 276 | # by default you can assume that the transaction has been rejected 277 | raise BancardAPIPaymentRejectecUnknownReasonException("Bancard reported that the payment has been rejected: %s" % bancard_tx_messages.get("dsc", ""), bancard_response) 278 | 279 | def rollback_charge(self, marketplace_charge_id): 280 | """ 281 | Calls to the rollback Bancard WebService to rollback a given charge request. 282 | 283 | :param marketplace_charge_id: The marketplace's custom ID of this charge request 284 | :type marketplace_charge_id: int or str 285 | :return: a tuple of: successfull_rollback, bancard_response 286 | :rtype tuple (bool, dict) 287 | :raises BancardAPIInvalidParameterException: if any of the input parameters is not valid 288 | :raises BancardAPINotRolledBackException: if Bancard denied the rollback request for some reason (i.e.: already couponned) 289 | """ 290 | 291 | BancardAPI.validate_marketplace_charge_id(marketplace_charge_id) 292 | 293 | bancard_token = "%s%s%s%s" % (self.private_key, marketplace_charge_id, "rollback", "0.00") 294 | if is_python_version_greater_igual_than_3x(): 295 | bancard_token = bytes(bancard_token, "UTF-8") 296 | 297 | bancard_body_request = { 298 | "public_key": self.public_key, 299 | "operation": { 300 | "token": hashlib.md5(bancard_token).hexdigest(), 301 | "shop_process_id": marketplace_charge_id 302 | } 303 | } 304 | 305 | bancard_response = BancardAPI.__call_bancard_webservice(bancard_body_request, self.urls[ROLLBACK_KEY]) 306 | 307 | bancard_tx_status = bancard_response.get("status", "") 308 | if bancard_tx_status == "success": 309 | # the bancard payment was rolled-back successfuly 310 | return True, bancard_response 311 | 312 | bancard_tx_messages = bancard_response.get('messages')[0] 313 | if bancard_tx_messages.get("key") in ['PaymentNotFoundError']: 314 | # the bancard charge has never been payed, but anyways Bancard rolled-back it successfully 315 | return True, bancard_response 316 | 317 | # bancard was not able to roll-back the payment 318 | raise BancardAPINotRolledBackException("Bancard was not able to roll-back the payment: %s" % bancard_tx_messages.get("dsc", ""), bancard_response) 319 | 320 | def process_vpos_webhook(self, bancard_data, original_marketplace_charge_id, original_amount, original_currency="PYG"): 321 | """ 322 | Manage the webhook data received from the Bancard VPOS after a successfull/rejected payment from the end-user 323 | 324 | :param bancard_data: The full content received in the Bancard wehbook 325 | :type bancard_data: str or dict 326 | :param original_marketplace_charge_id: The marketplace's custom ID of this charge request 327 | :type original_marketplace_charge_id: int or str 328 | :param original_amount: The amount that the payer should pay 329 | :type original_amount: Decimal 330 | :param original_currency: The currency of the amount to charge in the format ISO-4217 331 | :type original_currency: str 332 | :return: a tuple of: successfull_rollback, bancard_response 333 | :rtype tuple (bool, dict) 334 | :raises BancardAPIInvalidParameterException: if any of the input parameters is not valid 335 | :raises BancardAPIInvalidWebhookDataException: if the webhook data is not how it is supposed to be (someone might be trying to hack you) 336 | :raises BancardAPIInvalidWebhookTokenException: if the token generated as the specs is not equal to the one that Bancard sent (someone might be trying to hack you) 337 | :raises BancardAPIPaymentMethodNotEnabledException: if Bancard rejeted the payment because the payer's payment method was not enabled for making payments 338 | :raises BancardAPIPaymentTransactionInvalidException: if the payment has been rejected by Bancard because the transaction was not valid for some reason 339 | :raises BancardAPIPaymentMethodNotEnoughFundsException: if the payment has been rejected by Bancard because the payment method did not have enough funds 340 | :raises BancardAPIPaymentRejectecUnknownReasonException: if the payment has been rejected by Bancard due to an unhandled/unknown reason 341 | """ 342 | 343 | BancardAPI.validate_marketplace_charge_id(original_marketplace_charge_id) 344 | BancardAPI.validate_amount(original_amount) 345 | BancardAPI.validate_currency(original_currency) 346 | 347 | try: 348 | # parse the data received by Bancard 349 | bancard_operation_data = json.loads(bancard_data) if type(bancard_data) is str else bancard_data 350 | bancard_operation = bancard_operation_data["operation"] 351 | marketplace_charge_id = bancard_operation["shop_process_id"] 352 | 353 | if str(marketplace_charge_id) != str(original_marketplace_charge_id): 354 | raise BancardAPIInvalidWebhookDataException("Invalid Bancard webhook data.", bancard_data) 355 | 356 | # validate the token received by bancard 357 | amount_str = "%s.00" % currency_decimal_to_string(original_currency, original_amount) 358 | 359 | required_bancard_token = "%s%s%s%s%s" % (self.private_key, original_marketplace_charge_id, "confirm", amount_str, original_currency) 360 | if is_python_version_greater_igual_than_3x(): 361 | required_bancard_token = bytes(required_bancard_token, "UTF-8") 362 | 363 | if bancard_operation["token"] != required_bancard_token: 364 | raise BancardAPIInvalidWebhookTokenException("The Bancard Webhook did not pass the token validation: %s != %s" % (bancard_operation["token"], required_bancard_token)) 365 | 366 | # check the status of the Bancard transaction 367 | if bancard_operation["response_code"] == '00': 368 | return True, bancard_operation["authorization_number"], bancard_data 369 | else: 370 | # check if a specific error message has been received 371 | if bancard_operation["response_code"] in ['05', '15']: 372 | raise BancardAPIPaymentMethodNotEnabledException("Bancard reported that the payer's payment method was not enabled for making payments.", bancard_data) 373 | elif bancard_operation["response_code"] == '12': 374 | raise BancardAPIPaymentTransactionInvalidException("Bancard reported that the transaction is not valid.", bancard_data) 375 | elif bancard_operation["response_code"] == '51': 376 | raise BancardAPIPaymentMethodNotEnoughFundsException("Bancard reported that your payment card does not have enough funds.", bancard_data) 377 | 378 | raise BancardAPIPaymentRejectecUnknownReasonException("Bancard reported that the payment has been rejected.", bancard_data) 379 | except: 380 | raise BancardAPIInvalidWebhookDataException("Invalid Bancard webhook data.", bancard_data) 381 | 382 | @staticmethod 383 | def get_marketplace_charge_id_from_bancard_webhook(bancard_data): 384 | """ 385 | Obtains the marketplace_charge_id from the data received in the Bancard webhook. 386 | This method could be useful to get your charge object from your DB 387 | 388 | :param bancard_data: The json data received by the bancard webhook 389 | :type bancard_data: str or dict 390 | :return: the marketplace charge id 391 | :rtype str 392 | :raises BancardAPIInvalidWebhookDataException: if there was a problem while parsing the Bancard webhook data (someone might be trying to hack you) 393 | """ 394 | 395 | try: 396 | # parse the data received by Bancard 397 | bancard_operation_data = json.loads(bancard_data) if type(bancard_data) is str else bancard_data 398 | bancard_operation = bancard_operation_data["operation"] 399 | marketplace_charge_id = bancard_operation["shop_process_id"] 400 | return str(marketplace_charge_id) 401 | except: 402 | raise BancardAPIInvalidWebhookDataException("Invalid Bancard webhook data.", bancard_data) 403 | 404 | 405 | __api__ = None 406 | 407 | 408 | def connector(): 409 | """ 410 | Returns the global BancardAPI. If there is no API yet, create one by using the OS environment variables. 411 | The OS environment variables that should be configured are: 412 | BANCARD_ENVIRONMENT: "sandbox" or "production" 413 | BANCARD_PUBLIC_KEY: your_bancard_marketplace_public_key 414 | BANCARD_PRIVATE_KEY: your_bancard_marketplace_private_key 415 | 416 | :return: the marketplace charge id 417 | :rtype str 418 | :raises BancardAPIConfigurationException: if there was not a default api yet, and any of the required OS environment variables were missing 419 | """ 420 | 421 | global __api__ 422 | if __api__ is None: 423 | try: 424 | environment = os.environ.get("BANCARD_ENVIRONMENT", ENVIRONMENT_SANDBOX) # possible values: ["sandbox", "production"], and by default "sandbox" 425 | public_key = os.environ["BANCARD_PUBLIC_KEY"] 426 | private_key = os.environ["BANCARD_PRIVATE_KEY"] 427 | except KeyError: 428 | raise BancardAPIConfigurationException("The BancardAPI requires the following OS environment variables: BANCARD_ENVIRONMENT BANCARD_PUBLIC_KEY BANCARD_PRIVATE_KEY") 429 | 430 | # creates the BancardAPI reference to the global variable __api__ 431 | __api__ = BancardAPI(environment=environment, public_key=public_key, private_key=private_key) 432 | return __api__ 433 | 434 | 435 | def set_config(options=None, **config): 436 | """ 437 | Create new BancardAPI object with the given configuration parameters. 438 | 439 | :param options: Dictionary with configuration parameters for the API 440 | :type options: dict 441 | :param config: Dictionary with any extra parameters that could be unpacked for the API. The required values are: 442 | * environment: sandbox or production 443 | * public_key: the public key given by Bancard 444 | * private_key: the private key given by Bancard 445 | :type config: dict 446 | :return: the reference to the just created BancardAPI instance 447 | :rtype str 448 | :raises BancardAPIConfigurationException: if the BancardAPI couldn't be created due to missing configuration parameters 449 | """ 450 | 451 | global __api__ 452 | __api__ = BancardAPI(options or dict(), **config) 453 | return __api__ 454 | 455 | 456 | configure = set_config 457 | --------------------------------------------------------------------------------