├── .gitignore
├── CONTRIBUTORS
├── LICENSE
├── MANIFEST.in
├── README.rst
├── cielo
├── __init__.py
├── authorize.xml
├── capture.xml
├── card_token.xml
├── main.py
└── util.py
├── docs
├── Makefile
└── source
│ ├── conf.py
│ └── index.rst
├── setup.py
└── tests.py
/.gitignore:
--------------------------------------------------------------------------------
1 | *.py[co]
2 |
3 | # Packages
4 | *.egg
5 | *.egg-info
6 | dist
7 | build
8 | eggs
9 | parts
10 | bin
11 | var
12 | sdist
13 | develop-eggs
14 | .installed.cfg
15 |
16 | # Installer logs
17 | pip-log.txt
18 |
19 | # Unit test / coverage reports
20 | .coverage
21 | .tox
22 |
23 | #Translations
24 | *.mo
25 |
26 | #Mr Developer
27 | .mr.developer.cfg
28 |
--------------------------------------------------------------------------------
/CONTRIBUTORS:
--------------------------------------------------------------------------------
1 | Renato Pedigoni
2 | Rafael Bouchabki
3 | Ana Carolina Tortaro
4 | José Guilherme Tavares
5 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2013 Renato Pedigoni
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
13 | all 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
21 | THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | include README.rst
2 | include cielo/authorize.xml
3 | include cielo/capture.xml
4 | include cielo/card_token.xml
5 |
--------------------------------------------------------------------------------
/README.rst:
--------------------------------------------------------------------------------
1 | ============
2 | python-cielo
3 | ============
4 |
5 | python-cielo is a lightweight lib for making payments over the Cielo webservice (Brazil)
6 |
7 | Installation
8 | ^^^^^^^^^^^^^
9 | Use ``PIP`` or ``easy_install``: ::
10 |
11 | pip install python-cielo
12 |
13 |
14 | Documentation
15 | ^^^^^^^^^^^^^
16 | Docs are hosted at `ReadTheDocs `_.
17 |
--------------------------------------------------------------------------------
/cielo/__init__.py:
--------------------------------------------------------------------------------
1 | from main import PaymentAttempt, GetAuthorizedException, CaptureException, CreditCardToken
2 |
--------------------------------------------------------------------------------
/cielo/authorize.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | %(affiliation_id)s
5 | %(api_key)s
6 |
7 |
8 | %(card_number)s
9 | %(expiration)s
10 | 1
11 | %(cvc2)s
12 | %(card_holders_name)s
13 |
14 |
15 | %(order_id)s
16 | %(total)s
17 | 986
18 | %(date)s
19 | PT
20 |
21 |
22 | %(card_type)s
23 | %(transaction_type)s
24 | %(installments)s
25 |
26 | 3
27 | false
28 |
29 |
--------------------------------------------------------------------------------
/cielo/capture.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | %(transaction_id)s
4 |
5 | %(affiliation_id)s
6 | %(api_key)s
7 |
8 |
9 |
--------------------------------------------------------------------------------
/cielo/card_token.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | %(affiliation_id)s
5 | %(api_key)s
6 |
7 |
8 | %(card_number)s
9 | %(expiration)s
10 | %(card_holders_name)s
11 |
12 |
13 |
--------------------------------------------------------------------------------
/cielo/main.py:
--------------------------------------------------------------------------------
1 | # coding: utf-8
2 | from datetime import datetime
3 | import os
4 | import requests
5 | import xml.dom.minidom
6 | from decimal import Decimal
7 | from util import moneyfmt
8 |
9 |
10 | SANDBOX_URL = 'https://qasecommerce.cielo.com.br/servicos/ecommwsec.do'
11 | PRODUCTION_URL = 'https://ecommerce.cbmp.com.br/servicos/ecommwsec.do'
12 | CIELO_MSG_ERRORS = {
13 | '001': u'A mensagem XML está fora do formato especificado pelo arquivo ecommerce.xsd (001-Mensagem inválida)',
14 | '002': u'Impossibilidade de autenticar uma requisição da loja virtual. (002-Credenciais inválidas)',
15 | '003': u'Não existe transação para o identificador informado. (003-Transação inexistente)',
16 | '010': u'A transação, com ou sem cartão, está divergente com a permissão do envio dessa informação. (010-Inconsistência no envio do cartão)',
17 | '011': u'A transação está configurada com uma modalidade de pagamento não habilitada para a loja. (011-Modalidade não habilitada)',
18 | '012': u'O número de parcelas solicitado ultrapassa o máximo permitido. (012-Número de parcelas inválido)',
19 | '020': u'Não é permitido realizar autorização para o status da transação. (020-Status não permite autorização)',
20 | '021': u'Não é permitido realizar autorização, pois o prazo está vencido. (021-Prazo de autorização vencido)',
21 | '022': u'EC não possui permissão para realizar a autorização.(022-EC não autorizado)',
22 | '030': u'A captura não pode ser realizada, pois a transação não está autorizada.(030-Transação não autorizada para captura)',
23 | '031': u'A captura não pode ser realizada, pois o prazo para captura está vencido.(031-Prazo de captura vencido)',
24 | '032': u'O valor solicitado para captura não é válido.(032-Valor de captura inválido)',
25 | '033': u'Não foi possível realizar a captura.(033-Falha ao capturar)',
26 | '040': u'O cancelamento não pode ser realizado, pois o prazo está vencido.(040-Prazo de cancelamento vencido)',
27 | '041': u'O atual status da transação não permite cancelament.(041-Status não permite cancelamento)',
28 | '042': u'Não foi possível realizar o cancelamento.(042-Falha ao cancelar)',
29 | '099': u'Falha no sistema.(099-Erro inesperado)',
30 | }
31 |
32 |
33 | class GetAuthorizedException(Exception):
34 | def __init__(self, id, message=None):
35 | self.id = id
36 | self.message = message
37 |
38 | def __str__(self):
39 | return u'%s - %s' % (self.id, self.message)
40 |
41 |
42 | class CaptureException(Exception):
43 | pass
44 |
45 |
46 | class CreditCardBlockedException(Exception):
47 | pass
48 |
49 |
50 | class PaymentAttempt(object):
51 | VISA, MASTERCARD, DINERS, DISCOVER, ELO, AMEX = 'visa', 'mastercard', 'diners', 'discover', 'elo', 'amex'
52 | CARD_TYPE_C = (
53 | (VISA, u'Visa'),
54 | (MASTERCARD, u'Mastercard'),
55 | (DINERS, u'Diners'),
56 | (DISCOVER, u'Discover'),
57 | (ELO, u'ELO'),
58 | (AMEX, u'American express'),
59 | )
60 |
61 | CASH, INSTALLMENT_STORE, INSTALLMENT_CIELO = 1, 2, 3
62 | TRANSACTION_TYPE_C = (
63 | (CASH, u'À vista'),
64 | (INSTALLMENT_STORE, u'Parcelado (estabelecimento)'),
65 | (INSTALLMENT_CIELO, u'Parcelado (Cielo)'),
66 | )
67 |
68 | def __init__(self, affiliation_id, api_key, total, card_type, installments, order_id, card_number, cvc2,
69 | exp_month, exp_year, card_holders_name, transaction=CASH, sandbox=False):
70 |
71 | assert isinstance(total, Decimal), u'total must be an instance of Decimal'
72 | assert installments in range(1, 13), u'installments must be a integer between 1 and 12'
73 |
74 | assert (installments == 1 and transaction == self.CASH) \
75 | or installments > 1 and transaction != self.CASH, \
76 | u'if installments = 1 then transaction must be None or "cash"'
77 |
78 | if len(str(exp_year)) == 2:
79 | exp_year = '20%s' % exp_year # FIXME: bug do milênio em 2100
80 |
81 | self.url = SANDBOX_URL if sandbox else PRODUCTION_URL
82 | self.card_type = card_type
83 | self.affiliation_id = affiliation_id
84 | self.api_key = api_key
85 | self.transaction = transaction
86 | self.transaction_type = transaction # para manter assinatura do pyrcws
87 | self.total = moneyfmt(total, sep='', dp='')
88 | self.installments = installments
89 | self.order_id = order_id
90 | self.card_number = card_number
91 | self.cvc2 = cvc2
92 | self.exp_month = exp_month
93 | self.exp_year = exp_year
94 | self.expiration = '%s%s' % (exp_year, exp_month)
95 | self.card_holders_name = card_holders_name
96 | self._authorized = False
97 |
98 | self.sandbox = sandbox
99 |
100 | def get_authorized(self):
101 | self.date = datetime.now().strftime('%Y-%m-%dT%H:%M:%S')
102 | self.payload = open(
103 | os.path.join(os.path.dirname(os.path.abspath(__file__)), 'authorize.xml'), 'r').read() % self.__dict__
104 |
105 | self.response = requests.post(
106 | self.url,
107 | data={'mensagem': self.payload, })
108 |
109 | self.dom = xml.dom.minidom.parseString(self.response.content)
110 |
111 | if self.dom.getElementsByTagName('erro'):
112 | self.error_id = self.dom.getElementsByTagName(
113 | 'erro')[0].getElementsByTagName('codigo')[0].childNodes[0].data
114 | try:
115 | self.error_message = CIELO_MSG_ERRORS[self.error_id]
116 | except IndexError:
117 | self.error_message = u'Erro não especificado, ver documentação (código %s)' % self.error_id
118 | raise GetAuthorizedException(self.error_id, self.error_message)
119 |
120 | self.status = int(
121 | self.dom.getElementsByTagName('status')[0].childNodes[0].data)
122 | if self.status != 4:
123 | self.error_id = self.dom.getElementsByTagName(
124 | 'autorizacao')[0].getElementsByTagName(
125 | 'codigo')[0].childNodes[0].data
126 | self.error_message = self.dom.getElementsByTagName(
127 | 'autorizacao')[0].getElementsByTagName(
128 | 'mensagem')[0].childNodes[0].data
129 | self._authorized = False
130 | raise GetAuthorizedException(self.error_id, self.error_message)
131 |
132 | self.transaction_id = self.dom.getElementsByTagName(
133 | 'tid')[0].childNodes[0].data
134 | self.pan = self.dom.getElementsByTagName('pan')[0].childNodes[0].data
135 |
136 | self._authorized = True
137 | return True
138 |
139 | def capture(self):
140 | assert self._authorized, u'get_authorized(...) must be called before capture(...)'
141 |
142 | payload = open(os.path.join(os.path.dirname(os.path.abspath(__file__)), 'capture.xml'), 'r').read() % self.__dict__
143 |
144 | response = requests.post(self.url, data={
145 | 'mensagem': payload,
146 | })
147 |
148 | dom = xml.dom.minidom.parseString(response.content)
149 | status = int(dom.getElementsByTagName('status')[0].childNodes[0].data)
150 |
151 | if status != 6:
152 | # 6 = capturado
153 | raise CaptureException()
154 | return True
155 |
156 |
157 | class CreditCardToken(object):
158 | def __init__(self, affiliation_id, api_key, card_number, exp_month, exp_year, card_holders_name, sandbox=False):
159 |
160 | if len(str(exp_year)) == 2:
161 | exp_year = '20%s' % exp_year # FIXME: bug do milênio em 2100
162 |
163 | self.url = SANDBOX_URL if sandbox else PRODUCTION_URL
164 | self.affiliation_id = affiliation_id
165 | self.api_key = api_key
166 | self.card_number = card_number
167 | self.exp_month = exp_month
168 | self.exp_year = exp_year
169 | self.expiration = '%s%s' % (exp_year, exp_month)
170 | self.card_holders_name = card_holders_name
171 | self._authorized = False
172 |
173 | self.sandbox = sandbox
174 |
175 | def create(self):
176 | payload = open(os.path.join(os.path.dirname(os.path.abspath(__file__)), 'card_token.xml'), 'r').read() % self.__dict__
177 |
178 | response = requests.post(self.url, data={
179 | 'mensagem': payload,
180 | })
181 |
182 | dom = xml.dom.minidom.parseString(response.content)
183 | status = int(dom.getElementsByTagName('status')[0].childNodes[0].data)
184 | token = dom.getElementsByTagName('codigo-token')[0].childNodes[0].data
185 | truncated_card = dom.getElementsByTagName('numero-cartao-truncado')[0].childNodes[0].data
186 |
187 | if status != 1:
188 | # Cartão de crédito bloqueado
189 | raise CreditCardBlockedException()
190 |
191 | return {
192 | 'token': token,
193 | 'truncated': truncated_card,
194 | }
195 |
--------------------------------------------------------------------------------
/cielo/util.py:
--------------------------------------------------------------------------------
1 | from decimal import Decimal
2 |
3 | def moneyfmt(value, places=2, curr='', sep=',', dp='.',
4 | pos='', neg='-', trailneg=''):
5 | """Convert Decimal to a money formatted string.
6 |
7 | places: required number of places after the decimal point
8 | curr: optional currency symbol before the sign (may be blank)
9 | sep: optional grouping separator (comma, period, space, or blank)
10 | dp: decimal point indicator (comma or period)
11 | only specify as blank when places is zero
12 | pos: optional sign for positive numbers: '+', space or blank
13 | neg: optional sign for negative numbers: '-', '(', space or blank
14 | trailneg:optional trailing minus indicator: '-', ')', space or blank
15 |
16 | >>> d = Decimal('-1234567.8901')
17 | >>> moneyfmt(d, curr='$')
18 | '-$1,234,567.89'
19 | >>> moneyfmt(d, places=0, sep='.', dp='', neg='', trailneg='-')
20 | '1.234.568-'
21 | >>> moneyfmt(d, curr='$', neg='(', trailneg=')')
22 | '($1,234,567.89)'
23 | >>> moneyfmt(Decimal(123456789), sep=' ')
24 | '123 456 789.00'
25 | >>> moneyfmt(Decimal('-0.02'), neg='<', trailneg='>')
26 | '<0.02>'
27 |
28 | """
29 | q = Decimal(10) ** -places # 2 places --> '0.01'
30 | sign, digits, exp = value.quantize(q).as_tuple()
31 | result = []
32 | digits = map(str, digits)
33 | build, next = result.append, digits.pop
34 | if sign:
35 | build(trailneg)
36 | for i in range(places):
37 | build(next() if digits else '0')
38 | build(dp)
39 | if not digits:
40 | build('0')
41 | i = 0
42 | while digits:
43 | build(next())
44 | i += 1
45 | if i == 3 and digits:
46 | i = 0
47 | build(sep)
48 | build(curr)
49 | build(neg if sign else pos)
50 | return ''.join(reversed(result))
51 |
--------------------------------------------------------------------------------
/docs/Makefile:
--------------------------------------------------------------------------------
1 | # Makefile for Sphinx documentation
2 | #
3 |
4 | # You can set these variables from the command line.
5 | SPHINXOPTS =
6 | SPHINXBUILD = sphinx-build
7 | PAPER =
8 | BUILDDIR = build
9 |
10 | # Internal variables.
11 | PAPEROPT_a4 = -D latex_paper_size=a4
12 | PAPEROPT_letter = -D latex_paper_size=letter
13 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source
14 | # the i18n builder cannot share the environment and doctrees with the others
15 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source
16 |
17 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext
18 |
19 | help:
20 | @echo "Please use \`make ' where is one of"
21 | @echo " html to make standalone HTML files"
22 | @echo " dirhtml to make HTML files named index.html in directories"
23 | @echo " singlehtml to make a single large HTML file"
24 | @echo " pickle to make pickle files"
25 | @echo " json to make JSON files"
26 | @echo " htmlhelp to make HTML files and a HTML help project"
27 | @echo " qthelp to make HTML files and a qthelp project"
28 | @echo " devhelp to make HTML files and a Devhelp project"
29 | @echo " epub to make an epub"
30 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
31 | @echo " latexpdf to make LaTeX files and run them through pdflatex"
32 | @echo " text to make text files"
33 | @echo " man to make manual pages"
34 | @echo " texinfo to make Texinfo files"
35 | @echo " info to make Texinfo files and run them through makeinfo"
36 | @echo " gettext to make PO message catalogs"
37 | @echo " changes to make an overview of all changed/added/deprecated items"
38 | @echo " linkcheck to check all external links for integrity"
39 | @echo " doctest to run all doctests embedded in the documentation (if enabled)"
40 |
41 | clean:
42 | -rm -rf $(BUILDDIR)/*
43 |
44 | html:
45 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
46 | @echo
47 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
48 |
49 | dirhtml:
50 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
51 | @echo
52 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
53 |
54 | singlehtml:
55 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
56 | @echo
57 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
58 |
59 | pickle:
60 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
61 | @echo
62 | @echo "Build finished; now you can process the pickle files."
63 |
64 | json:
65 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
66 | @echo
67 | @echo "Build finished; now you can process the JSON files."
68 |
69 | htmlhelp:
70 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
71 | @echo
72 | @echo "Build finished; now you can run HTML Help Workshop with the" \
73 | ".hhp project file in $(BUILDDIR)/htmlhelp."
74 |
75 | qthelp:
76 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
77 | @echo
78 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \
79 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
80 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/python-cielo.qhcp"
81 | @echo "To view the help file:"
82 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/python-cielo.qhc"
83 |
84 | devhelp:
85 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
86 | @echo
87 | @echo "Build finished."
88 | @echo "To view the help file:"
89 | @echo "# mkdir -p $$HOME/.local/share/devhelp/python-cielo"
90 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/python-cielo"
91 | @echo "# devhelp"
92 |
93 | epub:
94 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
95 | @echo
96 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub."
97 |
98 | latex:
99 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
100 | @echo
101 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
102 | @echo "Run \`make' in that directory to run these through (pdf)latex" \
103 | "(use \`make latexpdf' here to do that automatically)."
104 |
105 | latexpdf:
106 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
107 | @echo "Running LaTeX files through pdflatex..."
108 | $(MAKE) -C $(BUILDDIR)/latex all-pdf
109 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
110 |
111 | text:
112 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
113 | @echo
114 | @echo "Build finished. The text files are in $(BUILDDIR)/text."
115 |
116 | man:
117 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
118 | @echo
119 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man."
120 |
121 | texinfo:
122 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
123 | @echo
124 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
125 | @echo "Run \`make' in that directory to run these through makeinfo" \
126 | "(use \`make info' here to do that automatically)."
127 |
128 | info:
129 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
130 | @echo "Running Texinfo files through makeinfo..."
131 | make -C $(BUILDDIR)/texinfo info
132 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
133 |
134 | gettext:
135 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
136 | @echo
137 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
138 |
139 | changes:
140 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
141 | @echo
142 | @echo "The overview file is in $(BUILDDIR)/changes."
143 |
144 | linkcheck:
145 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
146 | @echo
147 | @echo "Link check complete; look for any errors in the above output " \
148 | "or in $(BUILDDIR)/linkcheck/output.txt."
149 |
150 | doctest:
151 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
152 | @echo "Testing of doctests in the sources finished, look at the " \
153 | "results in $(BUILDDIR)/doctest/output.txt."
154 |
--------------------------------------------------------------------------------
/docs/source/conf.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | #
3 | # python-cielo documentation build configuration file, created by
4 | # sphinx-quickstart on Tue Jun 19 18:10:31 2012.
5 | #
6 | # This file is execfile()d with the current directory set to its containing dir.
7 | #
8 | # Note that not all possible configuration values are present in this
9 | # autogenerated file.
10 | #
11 | # All configuration values have a default; values that are commented out
12 | # serve to show the default.
13 |
14 | import sys, os
15 |
16 | # If extensions (or modules to document with autodoc) are in another directory,
17 | # add these directories to sys.path here. If the directory is relative to the
18 | # documentation root, use os.path.abspath to make it absolute, like shown here.
19 | #sys.path.insert(0, os.path.abspath('.'))
20 |
21 | # -- General configuration -----------------------------------------------------
22 |
23 | # If your documentation needs a minimal Sphinx version, state it here.
24 | #needs_sphinx = '1.0'
25 |
26 | # Add any Sphinx extension module names here, as strings. They can be extensions
27 | # coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
28 | extensions = []
29 |
30 | # Add any paths that contain templates here, relative to this directory.
31 | templates_path = ['_templates']
32 |
33 | # The suffix of source filenames.
34 | source_suffix = '.rst'
35 |
36 | # The encoding of source files.
37 | #source_encoding = 'utf-8-sig'
38 |
39 | # The master toctree document.
40 | master_doc = 'index'
41 |
42 | # General information about the project.
43 | project = u'python-cielo'
44 | copyright = u'2013, Renato Pedigoni'
45 |
46 | # The version info for the project you're documenting, acts as replacement for
47 | # |version| and |release|, also used in various other places throughout the
48 | # built documents.
49 | #
50 | # The short X.Y version.
51 | version = '0.5'
52 | # The full version, including alpha/beta/rc tags.
53 | release = '0.5'
54 |
55 | # The language for content autogenerated by Sphinx. Refer to documentation
56 | # for a list of supported languages.
57 | #language = None
58 |
59 | # There are two options for replacing |today|: either, you set today to some
60 | # non-false value, then it is used:
61 | #today = ''
62 | # Else, today_fmt is used as the format for a strftime call.
63 | #today_fmt = '%B %d, %Y'
64 |
65 | # List of patterns, relative to source directory, that match files and
66 | # directories to ignore when looking for source files.
67 | exclude_patterns = []
68 |
69 | # The reST default role (used for this markup: `text`) to use for all documents.
70 | #default_role = None
71 |
72 | # If true, '()' will be appended to :func: etc. cross-reference text.
73 | #add_function_parentheses = True
74 |
75 | # If true, the current module name will be prepended to all description
76 | # unit titles (such as .. function::).
77 | #add_module_names = True
78 |
79 | # If true, sectionauthor and moduleauthor directives will be shown in the
80 | # output. They are ignored by default.
81 | #show_authors = False
82 |
83 | # The name of the Pygments (syntax highlighting) style to use.
84 | pygments_style = 'sphinx'
85 |
86 | # A list of ignored prefixes for module index sorting.
87 | #modindex_common_prefix = []
88 |
89 |
90 | # -- Options for HTML output ---------------------------------------------------
91 |
92 | # The theme to use for HTML and HTML Help pages. See the documentation for
93 | # a list of builtin themes.
94 | html_theme = 'nature'
95 |
96 | # Theme options are theme-specific and customize the look and feel of a theme
97 | # further. For a list of options available for each theme, see the
98 | # documentation.
99 | #html_theme_options = {}
100 |
101 | # Add any paths that contain custom themes here, relative to this directory.
102 | #html_theme_path = []
103 |
104 | # The name for this set of Sphinx documents. If None, it defaults to
105 | # " v documentation".
106 | #html_title = None
107 |
108 | # A shorter title for the navigation bar. Default is the same as html_title.
109 | #html_short_title = None
110 |
111 | # The name of an image file (relative to this directory) to place at the top
112 | # of the sidebar.
113 | #html_logo = None
114 |
115 | # The name of an image file (within the static path) to use as favicon of the
116 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
117 | # pixels large.
118 | #html_favicon = None
119 |
120 | # Add any paths that contain custom static files (such as style sheets) here,
121 | # relative to this directory. They are copied after the builtin static files,
122 | # so a file named "default.css" will overwrite the builtin "default.css".
123 | html_static_path = ['_static']
124 |
125 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
126 | # using the given strftime format.
127 | #html_last_updated_fmt = '%b %d, %Y'
128 |
129 | # If true, SmartyPants will be used to convert quotes and dashes to
130 | # typographically correct entities.
131 | #html_use_smartypants = True
132 |
133 | # Custom sidebar templates, maps document names to template names.
134 | #html_sidebars = {}
135 |
136 | # Additional templates that should be rendered to pages, maps page names to
137 | # template names.
138 | #html_additional_pages = {}
139 |
140 | # If false, no module index is generated.
141 | #html_domain_indices = True
142 |
143 | # If false, no index is generated.
144 | #html_use_index = True
145 |
146 | # If true, the index is split into individual pages for each letter.
147 | #html_split_index = False
148 |
149 | # If true, links to the reST sources are added to the pages.
150 | #html_show_sourcelink = True
151 |
152 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
153 | #html_show_sphinx = True
154 |
155 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
156 | #html_show_copyright = True
157 |
158 | # If true, an OpenSearch description file will be output, and all pages will
159 | # contain a tag referring to it. The value of this option must be the
160 | # base URL from which the finished HTML is served.
161 | #html_use_opensearch = ''
162 |
163 | # This is the file name suffix for HTML files (e.g. ".xhtml").
164 | #html_file_suffix = None
165 |
166 | # Output file base name for HTML help builder.
167 | htmlhelp_basename = 'python-cielodoc'
168 |
169 |
170 | # -- Options for LaTeX output --------------------------------------------------
171 |
172 | latex_elements = {
173 | # The paper size ('letterpaper' or 'a4paper').
174 | #'papersize': 'letterpaper',
175 |
176 | # The font size ('10pt', '11pt' or '12pt').
177 | #'pointsize': '10pt',
178 |
179 | # Additional stuff for the LaTeX preamble.
180 | #'preamble': '',
181 | }
182 |
183 | # Grouping the document tree into LaTeX files. List of tuples
184 | # (source start file, target name, title, author, documentclass [howto/manual]).
185 | latex_documents = [
186 | ('index', 'python-cielo.tex', u'python-cielo Documentation',
187 | u'Renato Pedigoni', 'manual'),
188 | ]
189 |
190 | # The name of an image file (relative to this directory) to place at the top of
191 | # the title page.
192 | #latex_logo = None
193 |
194 | # For "manual" documents, if this is true, then toplevel headings are parts,
195 | # not chapters.
196 | #latex_use_parts = False
197 |
198 | # If true, show page references after internal links.
199 | #latex_show_pagerefs = False
200 |
201 | # If true, show URL addresses after external links.
202 | #latex_show_urls = False
203 |
204 | # Documents to append as an appendix to all manuals.
205 | #latex_appendices = []
206 |
207 | # If false, no module index is generated.
208 | #latex_domain_indices = True
209 |
210 |
211 | # -- Options for manual page output --------------------------------------------
212 |
213 | # One entry per manual page. List of tuples
214 | # (source start file, name, description, authors, manual section).
215 | man_pages = [
216 | ('index', 'python-cielo', u'python-cielo Documentation',
217 | [u'Renato Pedigoni'], 1)
218 | ]
219 |
220 | # If true, show URL addresses after external links.
221 | #man_show_urls = False
222 |
223 |
224 | # -- Options for Texinfo output ------------------------------------------------
225 |
226 | # Grouping the document tree into Texinfo files. List of tuples
227 | # (source start file, target name, title, author,
228 | # dir menu entry, description, category)
229 | texinfo_documents = [
230 | ('index', 'python-cielo', u'python-cielo Documentation',
231 | u'Renato Pedigoni', 'python-cielo', 'One line description of project.',
232 | 'Miscellaneous'),
233 | ]
234 |
235 | # Documents to append as an appendix to all manuals.
236 | #texinfo_appendices = []
237 |
238 | # If false, no module index is generated.
239 | #texinfo_domain_indices = True
240 |
241 | # How to display URL addresses: 'footnote', 'no', or 'inline'.
242 | #texinfo_show_urls = 'footnote'
243 |
--------------------------------------------------------------------------------
/docs/source/index.rst:
--------------------------------------------------------------------------------
1 | .. python-cielo documentation master file, created by
2 | sphinx-quickstart on Tue Jun 19 18:10:31 2012.
3 | You can adapt this file completely to your liking, but it should at least
4 | contain the root `toctree` directive.
5 |
6 | python-cielo
7 | ============
8 |
9 | Biblioteca para autorização e captura de pagamentos pelo webservice da Cielo.
10 |
11 | Autorização e captura
12 | ---------------------
13 |
14 | Para efetuar uma transação utilize o seguinte modelo: ::
15 |
16 | from decimal import Decimal
17 | from cielo import PaymentAttempt, GetAuthorizedException
18 |
19 | params = {
20 | 'affiliation_id': '1234567890',
21 | 'api_key': 'ABCDEFG123456789',
22 | 'card_type': PaymentAttempt.VISA,
23 | 'total': Decimal('1.00'),
24 | 'order_id': '7DSD163AH1', # strings são permitidas
25 | 'card_number': '4012001037141112',
26 | 'cvc2': 423, # código de segurança
27 | 'exp_month': 1,
28 | 'exp_year': 2010,
29 | 'transaction': PaymentAttempt.CASH,
30 | 'card_holders_name': 'JOAO DA SILVA',
31 | 'installments': 1,
32 | }
33 |
34 | attempt = PaymentAttempt(**params)
35 | try:
36 | attempt.get_authorized()
37 | except GetAuthorizedException, e:
38 | print u'Não foi possível processar: %s' % e
39 | else:
40 | attempt.capture()
41 |
42 |
43 | Parâmetros
44 | ^^^^^^^^^^
45 | Verifique abaixo a lista de parâmetros esperados no construtor da classe ``PaymentAttempt``.
46 |
47 | ========================== =============================================== ======================================
48 | Atributo Descrição Observações
49 | ========================== =============================================== ======================================
50 | ``affiliation_id`` Número de afiliação junto à Cielo
51 | ``api_key`` Chave de acesso para o webservice
52 | ``card_type`` Bandeira do cartão Veja as bandeiras suportadas
53 | ``total`` Valor total do pedido (utilizar ``Decimal``)
54 | ``order_id`` Identificador único do pedido
55 | ``card_number`` Número do cartão (sem pontos)
56 | ``cvc2`` Código de segurança
57 | ``exp_month`` Mês de vencimento
58 | ``exp_year`` Ano de vencimento
59 | ``card_holders_name`` Nome impresso no cartão
60 | ``installments`` Número de parcelas
61 | ``transaction`` Tipo da transação / parcelamento Veja os tipos de transações suportados
62 | ``sandbox`` Ambiente de desenvolvimento Default: ``False``
63 | ========================== =============================================== ======================================
64 |
65 |
66 |
67 | Bandeiras suportadas
68 | ^^^^^^^^^^^^^^^^^^^^
69 | Atualmente as seguintes bandeiras são suportadas:
70 |
71 | * Visa: ``PaymentAttempt.VISA``
72 | * Mastercard: ``PaymentAttempt.MASTERCARD``
73 | * Diners: ``PaymentAttempt.DINERS``
74 | * Discover: ``PaymentAttempt.DISCOVER``
75 | * ELO: ``PaymentAttempt.ELO``
76 | * American express: ``PaymentAttempt.AMEX``
77 |
78 |
79 | Tipos de transações
80 | ^^^^^^^^^^^^^^^^^^^
81 | Atualmente os seguintes tipos de transações são suportados:
82 |
83 | * À vista (uma parcela): ``PaymentAttempt.CASH``
84 | * Parcelado pelo estabelecimento: ``PaymentAttempt.INSTALLMENT_STORE``
85 | * Parcelado pela emissora: ``PaymentAttempt.INSTALLMENT_CIELO``
86 |
87 | .. warning::
88 | Antes de iniciar as vendas, verifique as taxas de cada tipo de transação junto à Cielo.
89 |
90 |
91 | Indices and tables
92 | ==================
93 |
94 | * :ref:`genindex`
95 | * :ref:`modindex`
96 | * :ref:`search`
97 |
98 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | import os
2 | from setuptools import setup, find_packages
3 |
4 | VERSION = (0, 5)
5 |
6 | f = open(os.path.join(os.path.dirname(__file__), 'README.rst'))
7 | readme = f.read()
8 | f.close()
9 |
10 | setup(
11 | name='python-cielo',
12 | version='.'.join(map(str, VERSION)),
13 | description='python-cielo is a lightweight lib for making payments over the Cielo webservice (Brazil)',
14 | long_description=readme,
15 | classifiers=[
16 | 'Programming Language :: Python',
17 | 'Topic :: Software Development :: Libraries :: Python Modules',
18 | ],
19 | keywords='cielo e-commerce',
20 | author='Renato Pedigoni',
21 | author_email='renatopedigoni@gmail.com',
22 | url='http://github.com/rpedigoni/python-cielo',
23 | license='BSD',
24 | packages=find_packages(),
25 | include_package_data=True,
26 | zip_safe=False,
27 | install_requires=['requests'],
28 | )
29 |
--------------------------------------------------------------------------------
/tests.py:
--------------------------------------------------------------------------------
1 | import unittest
2 | from decimal import Decimal
3 | from cielo import GetAuthorizedException, PaymentAttempt, CreditCardToken
4 |
5 |
6 | class MainTest(unittest.TestCase):
7 | def test_payment_attempt_authorized(self):
8 | params = {
9 | 'affiliation_id': '1006993069',
10 | 'api_key': '25fbb99741c739dd84d7b06ec78c9bac718838630f30b112d033ce2e621b34f3',
11 | 'card_type': PaymentAttempt.VISA,
12 | 'total': Decimal('1.00'), # when amount ends with .00 attempt is automatically authorized
13 | 'order_id': '7DSD163AH1', # strings are allowed here
14 | 'card_number': '4012001037141112',
15 | 'cvc2': 423,
16 | 'exp_month': 1,
17 | 'exp_year': 2010,
18 | 'card_holders_name': 'JOAO DA SILVA',
19 | 'installments': 1,
20 | 'transaction': PaymentAttempt.CASH,
21 | 'sandbox': True,
22 | }
23 |
24 | attempt = PaymentAttempt(**params)
25 | self.assertTrue(attempt.get_authorized())
26 |
27 | def test_payment_attempt_unauthorized(self):
28 | params = {
29 | 'affiliation_id': '1006993069',
30 | 'api_key': '25fbb99741c739dd84d7b06ec78c9bac718838630f30b112d033ce2e621b34f3',
31 | 'card_type': PaymentAttempt.VISA,
32 | 'total': Decimal('1.01'), # when amount does not end with .00 attempt is automatically cancelled
33 | 'order_id': '7DSD63A1H1', # strings are allowed here
34 | 'card_number': '4012001037141112',
35 | 'cvc2': 423,
36 | 'exp_month': 1,
37 | 'exp_year': 2010,
38 | 'card_holders_name': 'JOAO DA SILVA',
39 | 'installments': 1,
40 | 'transaction': PaymentAttempt.CASH,
41 | 'sandbox': True,
42 | }
43 |
44 | attempt = PaymentAttempt(**params)
45 | self.assertRaises(GetAuthorizedException, attempt.get_authorized)
46 |
47 | def test_payment_attempt_capture(self):
48 | params = {
49 | 'affiliation_id': '1006993069',
50 | 'api_key': '25fbb99741c739dd84d7b06ec78c9bac718838630f30b112d033ce2e621b34f3',
51 | 'card_type': PaymentAttempt.VISA,
52 | 'total': Decimal('1.00'), # when amount ends with .00 attempt is automatically authorized
53 | 'order_id': '7DSD163AH1', # strings are allowed here
54 | 'card_number': '4012001037141112',
55 | 'cvc2': 423,
56 | 'exp_month': 1,
57 | 'exp_year': 2010,
58 | 'card_holders_name': 'JOAO DA SILVA',
59 | 'installments': 1,
60 | 'transaction': PaymentAttempt.CASH,
61 | 'sandbox': True,
62 | }
63 |
64 | attempt = PaymentAttempt(**params)
65 | self.assertTrue(attempt.get_authorized())
66 | self.assertTrue(attempt.capture())
67 | # self.assertRaises(GetAuthorizedException, attempt.get_authorized)
68 |
69 | def test_credit_card_token_creation(self):
70 | params = {
71 | 'affiliation_id': '1006993069',
72 | 'api_key': '25fbb99741c739dd84d7b06ec78c9bac718838630f30b112d033ce2e621b34f3',
73 | 'card_number': '4012001037141112',
74 | 'exp_month': 1,
75 | 'exp_year': 2010,
76 | 'card_holders_name': 'JOAO DA SILVA',
77 | 'sandbox': True,
78 | }
79 |
80 | cc_token = CreditCardToken(**params)
81 | self.assertEqual(cc_token.create(), {
82 | 'token': 'O/sN7IgUNo4FKXy6SeQRc+BbuZiFvYo4Sqdph0EWaoI=',
83 | 'truncated': '401200******1112',
84 | })
85 |
86 | if __name__ == '__main__':
87 | unittest.main()
88 |
--------------------------------------------------------------------------------