PageRenderTime 77ms CodeModel.GetById 61ms app.highlight 7ms RepoModel.GetById 0ms app.codeStats 0ms

/pypal/service/adaptive_payment.py

https://github.com/birknilson/pypal
Python | 225 lines | 202 code | 15 blank | 8 comment | 11 complexity | b6d90e550c1a929bb3d6b96e33696feb MD5 | raw file
  1# -*- coding: utf-8 -*-
  2
  3import logging
  4
  5from pypal import currency
  6from pypal.util import check_required, set_nonempty_param
  7
  8PRODUCTION_ENDPOINT = 'https://svcs.paypal.com'
  9SANDBOX_ENDPOINT = 'https://svcs.sandbox.paypal.com'
 10
 11ACTION_PAY = 'PAY'
 12ACTION_CREATE = 'CREATE'
 13ACTION_PAY_PRIMARY = 'PAY_PRIMARY'
 14
 15SUPPORTED_PAY_ACTIONS = frozenset([ACTION_PAY,
 16                                   ACTION_CREATE,
 17                                   ACTION_PAY_PRIMARY])
 18
 19
 20FEE_PAYER_SENDER = 'SENDER'
 21FEE_PAYER_PRIMARY_RECEIVER = 'PRIMARYRECEIVER'
 22FEE_PAYER_EACH_RECEIVER = 'EACHRECEIVER'
 23FEE_PAYER_SECONDARY_RECEIVERS = 'SECONDARYONLY'
 24
 25SUPPORTED_FEE_PAYERS = frozenset([FEE_PAYER_SENDER,
 26                                  FEE_PAYER_PRIMARY_RECEIVER,
 27                                  FEE_PAYER_EACH_RECEIVER,
 28                                  FEE_PAYER_SECONDARY_RECEIVERS])
 29
 30EXECUTE_STATUS_CREATED = 'CREATED'
 31EXECUTE_STATUS_COMPLETED = 'COMPLETED'
 32EXECUTE_STATUS_INCOMPLETE = 'INCOMPLETE'
 33EXECUTE_STATUS_ERROR = 'ERROR'
 34EXECUTE_STATUS_REVERSAL_ERROR = 'REVERSALERROR'
 35EXECUTE_STATUS_PROCESSING = 'PROCESSING'
 36EXECUTE_STATUS_PENDING = 'PENDING'
 37
 38##############################################################################
 39# FUNCTIONS WHICH FURTHER AIDS IMPLEMENTATION OF THIS SERVICE
 40##############################################################################
 41
 42class ReceiverList(list):
 43    """An extension of the native list type which ensures all contained items
 44    are dictionaries - containing the necessary arguments needed per receiver.
 45
 46    """
 47    def __init__(self, iterable):
 48        if iterable:
 49            self.extend(iterable)
 50
 51    def append(self, obj):
 52        email = obj.get('email', None)
 53        amount = obj.get('amount', None)
 54        if not email and not amount:
 55            return False
 56
 57        sanitized = {'email': obj.get('email'),
 58                     'amount': obj.get('amount'),
 59                     'primary': obj.get('primary', 'false')}
 60        super(type(self), self).append(sanitized)
 61
 62    def extend(self, iterable):
 63        for obj in iterable:
 64            self.append(obj)
 65
 66def call(client, method, params):
 67    """A wrapper of the ``'pypal.Client.call'`` method which
 68    will set the API endpoints for this service depending
 69    on the environment, i.e sandbox or not.
 70
 71    :param client: An instance of ``'pypal.Client'``
 72    :param method: The API method to execute
 73    :param params: The arguments to send
 74    """
 75    endpoint = (PRODUCTION_ENDPOINT, SANDBOX_ENDPOINT)
 76    endpoint = endpoint[int(client.config.in_sandbox)]
 77    return client.call('AdaptivePayments', method,
 78                       endpoint=endpoint, **params)
 79
 80def get_payment_url(client,
 81                    action_type,
 82                    currency_code,
 83                    cancel_url,
 84                    return_url,
 85                    ipn_callback_url=None,
 86                    receivers=None,
 87                    fees_payer=None,
 88                    extra={},
 89                    embedded=False):
 90    """Executes the Pay API call and returns the intended redirect URL
 91    directly using the necessary pay key returned in the PayPal response.
 92
 93    This function is a wrapper of ``'pay'`` which will execute the necessary
 94    API calls and using the response this function will generate the URL.
 95    """
 96    response = pay(**locals())
 97    if not response.success:
 98        return None
 99
100    pay_key = response.get('payKey', None)
101    if not pay_key:
102        return None
103    return generate_pay_url(client, pay_key, embedded=embedded)
104
105def generate_pay_url(client, pay_key, embedded=False):
106    """Retrieves the pay key associated with prepared payment procedures and
107    generates the intended URL to redirect end-users in order to finialize
108    payments.
109
110    :param client: An instance of ``'pypal.Client'``
111    :param pay_key: The payment token received from PayPal
112    :param embedded: Whether or not to generate an url which
113                     is intended for an embedded payment experience.
114    """
115    paths = ('/cgi-bin/webscr?cmd=_ap-payment&paykey=%s',
116             '/webapps/adaptivepayment/flow/pay?paykey=%s')
117    return client.get_paypal_url(paths[embedded] % pay_key)
118
119##############################################################################
120# FUNCTIONS WHICH DIRECTLY CORRESPONDS TO PAYPAL API CALLS
121##############################################################################
122
123def pay(client,
124        action_type,
125        currency_code,
126        cancel_url,
127        return_url,
128        ipn_callback_url,
129        receivers=None,
130        fees_payer=None,
131        extra={}):
132    """Execute the Pay API call which will prepare the payment procedure.
133    Most importantly it will return a pay key which should be utilized in
134    order to identify the transaction.
135
136    :param client: An instance of ``'pypal.Client'``
137    :param action_type: The payment action type
138    :param currency_code: Which currency code to utilize in the transaction
139    :param cancel_url: The URL which the end-user is sent to on
140                       payment cancellation.
141    :param return_url: The URL which the end-user is sent to on completed
142                       payment, in all cases be it success or failure.
143    :param ipn_callback_url: The URL which will receive the IPN notifications
144                             related to this operation. The Adaptive Payment API
145                             requires this to be explicitly set and does not
146                             fallback on the default IPN URL for the
147                             application.
148    :param receivers: A list of the receivers of this transaction
149    :param fees_payer: Who will pay the PayPal fees
150    :param extra: Additional key-value arguments to send to PayPal
151    """
152    check_required(locals(), ('cancel_url', 'return_url', 'currency_code',
153                              'action_type', 'receivers', 'ipn_callback_url'))
154
155    if not currency.is_valid_code(currency_code):
156        raise ValueError('Given currency code (%s) '
157                         'is not supported' % currency_code)
158
159    if action_type not in SUPPORTED_PAY_ACTIONS:
160        raise ValueError('Given payment action (%s) is not any of the '
161                         'supported types; %s' % (action_type,
162                                                  SUPPORTED_PAY_ACTIONS))
163
164    if fees_payer and fees_payer not in SUPPORTED_FEE_PAYERS:
165        raise ValueError('Given value (%s) for the fees_payer argument '
166                         'is not supported by PayPal' % fees_payer)
167
168    if not isinstance(receivers, ReceiverList):
169        if not isinstance(receivers, (list, tuple)):
170            receivers = [receivers]
171        receivers = ReceiverList(receivers)
172
173    extra.update({'actionType': action_type,
174                  'receiverList': { 'receiver': receivers },
175                  'currencyCode': currency_code,
176                  'cancelUrl': cancel_url,
177                  'returnUrl': return_url})
178
179    set_nonempty_param(extra, 'ipnNotificationUrl', ipn_callback_url)
180    set_nonempty_param(extra, 'feesPayer', fees_payer)
181    return call(client, 'Pay', extra)
182
183def get_payment_options(client, pay_key):
184    return call(client, 'GetPaymentOptions', {'payKey': pay_key})
185
186def set_payment_options(client,
187                        pay_key,
188                        receiver_options,
189                        display_options=None,
190                        sender_options=None,
191                        shipping_address_id=None,
192                        initiating_entity=None,
193                        extra={}):
194    """Execute the SetPaymentOptions API call which will customize
195    behavior of the payment procedure at PayPal.
196
197    :param client: An instance of ``'pypal.Client'``
198    :param pay_key: The PayPal token associated with the transaction
199    :param sender_options: Dictionary containing sender customizations
200    :param receiver_options: Dictionary containing receiver customizations
201    :param display_options: Dictionary containing display customizations
202    :param shipping_address_id: The PayPal identifier for the shipping
203                                address to set.
204    :param initiating_entity: Dictionary containing initiating entity
205                              customizations.
206    :param extra: Additional key-value arguments to send to PayPal
207    """
208    extra['payKey'] = pay_key
209    set_nonempty_param(extra, 'initiatingEntity', initiating_entity)
210    set_nonempty_param(extra, 'displayOptions', display_options)
211    set_nonempty_param(extra, 'shippingAddressId', shipping_address_id)
212    set_nonempty_param(extra, 'senderOptions', sender_options)
213    set_nonempty_param(extra, 'receiverOptions', receiver_options)
214    return call(client, 'SetPaymentOptions', extra)
215
216def execute(client, pay_key):
217    return call(client, 'ExecutePayment', {'payKey': pay_key})
218
219def get_shipping_addresses(client, key):
220    """Execute the GetShippingAddresses API call which will retrieve
221    the shipping address which was set by the buyer.
222
223    :param token: Either a payment or preapproval key
224    """
225    return call(client, 'GetShippingAddresses', {'key': key})