forked from django-oscar/django-oscar-paypal
/
facade.py
186 lines (151 loc) · 6.83 KB
/
facade.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
"""
Bridging module between Oscar and the gateway module (which is Oscar agnostic)
"""
from oscar.apps.payment import exceptions
from paypal.payflow import gateway
from paypal.payflow import models
from paypal.payflow import codes
def authorize(order_number, amt, bankcard, billing_address=None):
"""
Make an *authorisation* request
This holds the money on the customer's bank account but does not mark the
transaction for settlement. This is the most common method to use for
fulfilling goods that require shipping. When the goods are ready to be
shipped, the transaction can be marked for settlement by calling the
delayed_capture method.
If successful, return nothing ("silence is golden") - if unsuccessful raise
an exception which can be caught and handled within view code.
:order_number: Order number for request
:amt: Amount for transaction
:bankcard: Instance of Oscar's Bankcard class (which is just a dumb wrapper
around the pertinent bankcard attributes).
:billing_address: A dict of billing address information (which can come from
the `cleaned_data` of a billing address form).
"""
return _submit_payment_details(gateway.authorize, order_number, amt, bankcard,
billing_address)
def sale(order_number, amt, bankcard, billing_address=None):
"""
Make a *sale* request
This holds the money on the customer's bank account and marks the
transaction for settlement that night. This is appropriate method to use
for products that can be immediately fulfilled - such as digital products.
If successful, return nothing ("silence is golden") - if unsuccessful raise
an exception which can be caught and handled within view code.
:order_number: Order number for request
:amt: Amount for transaction
:bankcard: Instance of Oscar's Bankcard class (which is just a dumb wrapper
around the pertinent bankcard attributes).
:billing_address: A dict of billing address information (which can come from
the `cleaned_data` of a billing address form.
"""
return _submit_payment_details(gateway.sale, order_number, amt, bankcard,
billing_address)
def _submit_payment_details(gateway_fn, order_number, amt, bankcard, billing_address=None):
# Oscar's bankcard class returns dates in form '01/02' - we strip the '/' to
# conform to PayPal's conventions.
exp_date = bankcard.expiry_date.replace('/', '')
# Remap address fields if set
address_fields = {}
if billing_address:
address_fields.update({
'first_name': billing_address['first_name'],
'last_name': billing_address['first_name'],
'street': billing_address['line1'],
'city': billing_address['line4'],
'state': billing_address['state'],
'zip': billing_address['postcode'].strip(' ')
})
txn = gateway_fn(
order_number,
card_number=bankcard.card_number,
cvv=bankcard.cvv,
expiry_date=exp_date,
amt=amt,
**address_fields)
if not txn.is_approved:
raise exceptions.UnableToTakePayment(txn.respmsg)
return txn
def delayed_capture(order_number, pnref=None, amt=None):
"""
Capture funds that have been previously authorized.
Notes:
* It's possible to capture a lower amount than the original auth
transaction - however...
* ...only one delayed capture is allowed for a given PNREF...
* ...If multiple captures are required, a 'reference transaction' needs to be
used.
* It's safe to retry captures if the first one fails or errors
:order_number: Order number
:pnref: The PNREF of the authorization transaction to use. If not
specified, the order number is used to retrieve the appropriate transaction.
:amt: A custom amount to capture.
"""
if pnref is None:
# No PNREF specified, look-up the auth transaction for this order number
# to get the PNREF from there.
try:
auth_txn = models.PayflowTransaction.objects.get(
comment1=order_number, trxtype=codes.AUTHORIZATION)
except models.PayflowTransaction.DoesNotExist:
raise exceptions.UnableToTakePayment(
"No authorization transaction found with PNREF=%s" % pnref)
pnref = auth_txn
txn = gateway.delayed_capture(order_number, pnref, amt)
if not txn.is_approved:
raise exceptions.UnableToTakePayment(txn.respmsg)
return txn
def referenced_sale(order_number, pnref, amt):
"""
Capture funds using the bank/address details of a previous transaction
This is equivalent to a *sale* transaction but without the user having to
enter their payment details.
There are two main uses for this:
1. This allows customers to checkout without having to re-enter their
payment details.
2. It allows an initial authorisation to be settled in multiple parts. The
first settle should use delayed_capture but any subsequent ones should
use this method.
:order_number: Order number.
:pnref: PNREF of a previous transaction to use.
:amt: The amount to settle for.
"""
txn = gateway.reference_transaction(
order_number, pnref, amt)
if not txn.is_approved:
raise exceptions.UnableToTakePayment(txn.respmsg)
return txn
def void(order_number, pnref):
"""
Void an authorisation transaction to prevent it from being settled
:order_number: Order number
:pnref: The PNREF of the transaction to void.
"""
txn = gateway.void(order_number, pnref)
if not txn.is_approved:
raise exceptions.PaymentError(txn.respmsg)
return txn
def credit(order_number, pnref=None, amt=None):
"""
Return funds that have been previously settled.
:order_number: Order number
:pnref: The PNREF of the authorization transaction to use. If not
specified, the order number is used to retrieve the appropriate transaction.
:amt: A custom amount to capture. If not specified, the entire transaction
is refuneded.
"""
if pnref is None:
# No PNREF specified, look-up the auth/sale transaction for this order number
# to get the PNREF from there.
try:
auth_txn = models.PayflowTransaction.objects.get(
comment1=order_number, trxtype__in=(codes.AUTHORIZATION,
codes.SALE))
except models.PayflowTransaction.DoesNotExist:
raise exceptions.UnableToTakePayment(
"No authorization transaction found with PNREF=%s" % pnref)
pnref = auth_txn
txn = gateway.credit(order_number, pnref, amt)
if not txn.is_approved:
raise exceptions.PaymentError(txn.respmsg)
return txn