diff --git a/addons/payment_acquirer/models/payment_acquirer.py b/addons/payment_acquirer/models/payment_acquirer.py index d7795a0a93e..6dbf7248ce2 100644 --- a/addons/payment_acquirer/models/payment_acquirer.py +++ b/addons/payment_acquirer/models/payment_acquirer.py @@ -295,6 +295,67 @@ class PaymentTransaction(osv.Model): # SERVER2SERVER RELATED METHODS # -------------------------------------------------- - def create_s2s(self, cr, uid, tx_values, cc_values, context=None): - tx_id = self.create(cr, uid, tx_values, context=context) + def s2s_create(self, cr, uid, values, cc_values, context=None): + tx_id, tx_result = self.s2s_send(cr, uid, values, cc_values, context=context) + self.s2s_feedback(cr, uid, tx_id, tx_result, context=context) return tx_id + + def s2s_send(self, cr, uid, values, cc_values, context=None): + """ Create and send server-to-server transaction. + + :param dict values: transaction values + :param dict cc_values: credit card values that are not stored into the + payment.transaction object. Acquirers should + handle receiving void or incorrect cc values. + Should contain : + + - holder_name + - number + - cvc + - expiry_date + - brand + - expiry_date_yy + - expiry_date_mm + """ + tx_id, result = None, None + + if values.get('acquirer_id'): + acquirer = self.pool['payment.acquirer'].browse(cr, uid, values.get('acquirer_id'), context=context) + custom_method_name = '_%s_s2s_send' % acquirer.name + if hasattr(self, custom_method_name): + tx_id, result = getattr(self, custom_method_name)(cr, uid, values, cc_values, context=context) + + if tx_id is None and result is None: + tx_id = super(PaymentTransaction, self).create(cr, uid, values, context=context) + return (tx_id, result) + + def s2s_feedback(self, cr, uid, tx_id, data, context=None): + """ Handle the feedback of a server-to-server transaction. """ + tx = self.browse(cr, uid, tx_id, context=context) + invalid_parameters = None + + invalid_param_method_name = '_%s_s2s_get_invalid_parameters' % tx.acquirer_id.name + if hasattr(self, invalid_param_method_name): + invalid_parameters = getattr(self, invalid_param_method_name)(cr, uid, tx, data, context=context) + + if invalid_parameters: + _error_message = '%s: incorrect tx data:\n' % (tx.acquirer_id.name) + for item in invalid_parameters: + _error_message += '\t%s: received %s instead of %s\n' % (item[0], item[1], item[2]) + _logger.error(_error_message) + return False + + feedback_method_name = '_%s_s2s_validate' % tx.acquirer_id.name + if hasattr(self, feedback_method_name): + return getattr(self, feedback_method_name)(cr, uid, tx, data, context=context) + + return True + + def s2s_get_tx_status(self, cr, uid, tx_id, context=None): + tx = self.browse(cr, uid, tx_id, context=context) + + invalid_param_method_name = '_%s_s2s_get_tx_status' % tx.acquirer_id.name + if hasattr(self, invalid_param_method_name): + return getattr(self, invalid_param_method_name)(cr, uid, tx, context=context) + + return True diff --git a/addons/payment_acquirer_paypal/models/paypal.py b/addons/payment_acquirer_paypal/models/paypal.py index 665c9fd2a17..0bb125f6e21 100644 --- a/addons/payment_acquirer_paypal/models/paypal.py +++ b/addons/payment_acquirer_paypal/models/paypal.py @@ -82,7 +82,7 @@ class AcquirerPaypal(osv.Model): for explanation why we use Authorization header instead of urllib2 password manager """ - res = dict() + res = dict.fromkeys(ids, False) parameters = urllib.urlencode({'grant_type': 'client_credentials'}) request = urllib2.Request('https://api.sandbox.paypal.com/v1/oauth2/token', parameters) # add other headers (https://developer.paypal.com/webapps/developer/docs/integration/direct/make-your-first-call/) @@ -226,7 +226,7 @@ class TxPaypal(osv.Model): # SERVER2SERVER RELATED METHODS # -------------------------------------------------- - def paypal_s2s_create(self, cr, uid, values, context=None): + def _paypal_s2s_send(self, cr, uid, values, cc_values, context=None): tx_id = self.create(cr, uid, values, context=context) tx = self.browse(cr, uid, tx_id, context=context) @@ -236,25 +236,96 @@ class TxPaypal(osv.Model): } data = { 'intent': 'sale', - 'redirect_urls': { + 'transactions': [{ + 'amount': { + 'total': '%.2f' % tx.amount, + 'currency': tx.currency_id.name, + }, + 'description': tx.reference, + }] + } + if cc_values: + data['payer'] = { + 'payment_method': 'credit_card', + 'funding_instruments': [{ + 'credit_card': { + 'number': cc_values['number'], + 'type': cc_values['brand'], + 'expire_month': cc_values['expiry_mm'], + 'expire_year': cc_values['expiry_yy'], + 'cvv2': cc_values['cvc'], + 'first_name': tx.partner_name, + 'last_name': tx.partner_name, + 'billing_address': { + 'line1': tx.partner_address, + 'city': tx.partner_city, + 'country_code': tx.partner_country_id.code, + 'postal_code': tx.partner_zip, + } + } + }] + } + else: + data['redirect_urls'] = { 'return_url': 'http://example.com/your_redirect_url/', 'cancel_url': 'http://example.com/your_cancel_url/', }, - 'payer': { + data['payer'] = { 'payment_method': 'paypal', - }, - 'transactions': [ - { - 'amount': { - 'total': '7.47', - 'currency': 'USD', - } - } - ] - } + } data = json.dumps(data) request = urllib2.Request('https://api.sandbox.paypal.com/v1/payments/payment', data, headers) - result = urllib2.urlopen(request).read() - return result + result = urllib2.urlopen(request) + print 'result', result + + res2 = result.read() + print res2 + return (tx_id, res2) + + def _paypal_s2s_get_invalid_parameters(self, cr, uid, tx, data, context=None): + invalid_parameters = [] + return invalid_parameters + + def _paypal_s2s_validate(self, cr, uid, tx, data, context=None): + values = json.loads(data) + status = values.get('state') + if status in ['approved']: + _logger.info('Validated Paypal s2s payment for tx %s: set as done' % (tx.reference)) + tx.write({ + 'state': 'done', + 'date_validate': values.get('udpate_time', fields.datetime.now()), + 'paypal_txn_id': values['id'], + }) + return True + elif status in ['pending', 'expired']: + _logger.info('Received notification for Paypal s2s payment %s: set as pending' % (tx.reference)) + tx.write({ + 'state': 'pending', + # 'state_message': data.get('pending_reason', ''), + 'paypal_txn_id': values['id'], + }) + return True + else: + error = 'Received unrecognized status for Paypal s2s payment %s: %s, set as error' % (tx.reference, status) + _logger.info(error) + tx.write({ + 'state': 'error', + # 'state_message': error, + 'paypal_txn_id': values['id'], + }) + return False + + def _paypal_s2s_get_tx_status(self, cr, uid, tx, context=None): + headers = { + 'Content-Type': 'application/json', + 'Authorization': 'Bearer %s' % tx.acquirer_id._paypal_s2s_get_access_token()[tx.acquirer_id.id], + } + data = json.dumps({}) + url = 'https://api.sandbox.paypal.com/v1/payments/payment/%s' % (tx.paypal_txn_id) + print url + request = urllib2.Request(url, data, headers) + result = urllib2.urlopen(request) + print result + return True diff --git a/addons/payment_acquirer_paypal/tests/test_paypal.py b/addons/payment_acquirer_paypal/tests/test_paypal.py index b9f2af8119e..63ae9670a41 100644 --- a/addons/payment_acquirer_paypal/tests/test_paypal.py +++ b/addons/payment_acquirer_paypal/tests/test_paypal.py @@ -7,10 +7,6 @@ from openerp.osv.orm import except_orm from openerp.tools import mute_logger from lxml import objectify -# import requests -# import urlparse -import urllib -import urllib2 import urlparse @@ -37,6 +33,18 @@ class PaypalCommon(PaymentAcquirerCommon): }) # tde+seller@openerp.com - tde+buyer@openerp.com - tde+buyer-it@openerp.com + # some CC + self.amex = (('378282246310005', '123'), ('371449635398431', '123')) + self.amex_corporate = (('378734493671000', '123')) + self.autralian_bankcard = (('5610591081018250', '123')) + self.dinersclub = (('30569309025904', '123'), ('38520000023237', '123')) + self.discover = (('6011111111111117', '123'), ('6011000990139424', '123')) + self.jcb = (('3530111333300000', '123'), ('3566002020360505', '123')) + self.mastercard = (('5555555555554444', '123'), ('5105105105105100', '123')) + self.visa = (('4111111111111111', '123'), ('4012888888881881', '123'), ('4222222222222', '123')) + self.dankord_pbs = (('76009244561', '123'), ('5019717010103742', '123')) + self.switch_polo = (('6331101999990016', '123')) + class PaypalServer2Server(PaypalCommon): @@ -44,18 +52,94 @@ class PaypalServer2Server(PaypalCommon): cr, uid, context = self.cr, self.uid, {} res = self.payment_acquirer._paypal_s2s_get_access_token(cr, uid, [self.paypal_id], context=context) - print res, res.get('access_token') + self.assertTrue(res[self.paypal_id] is not False, 'paypal: did not generate access token') - res = self.payment_transaction.paypal_s2s_create( + tx_id = self.payment_transaction.s2s_create( cr, uid, { 'amount': 0.01, 'acquirer_id': self.paypal_id, 'currency_id': self.currency_euro_id, 'reference': 'test_reference', - 'paypal_txn_id': '61E67681CH3238416', + 'partner_id': self.buyer_id, + }, { + 'number': self.visa[0][0], + 'cvc': self.visa[0][1], + 'brand': 'visa', + 'expiry_mm': 9, + 'expiry_yy': 2015, }, context=context ) - print res + + tx = self.payment_transaction.browse(cr, uid, tx_id, context=context) + print tx.paypal_txn_id + self.payment_transaction.s2s_get_tx_status(cr, uid, tx_id, context=context) +# { +# "id":"PAY-2LL14628DB722091TKKHXZHQ", +# "create_time":"2013-11-22T15:47:42Z", +# "update_time":"2013-11-22T15:48:05Z", +# "state":"pending", +# "intent":"sale", +# "payer": { +# "payment_method":"credit_card", +# "funding_instruments": [{ +# "credit_card": { +# "type":"visa", +# "number":"xxxxxxxxxxxx1111", +# "expire_month":"9", +# "expire_year":"2015", +# "first_name":"Norbert Buyer", +# "last_name":"Norbert Buyer", +# "billing_address": { +# "line1":"Huge Street 2/543", +# "city":"Sin City", +# "postal_code":"1000", +# "country_code":"BE" +# } +# } +# }] +# }, +# "transactions": [{ +# "amount": { +# "total":"0.01", +# "currency":"EUR", +# "details": { +# "subtotal":"0.01" +# } +# }, +# "related_resources": [{ +# "sale": { +# "id":"4KU52719R3958614J", +# "create_time":"2013-11-22T15:47:42Z", +# "update_time":"2013-11-22T15:48:05Z", +# "state":"pending", +# "amount": { +# "total":"0.01", +# "currency":"EUR" +# }, +# "pending_reason":"multicurrency", +# "parent_payment":"PAY-2LL14628DB722091TKKHXZHQ", +# "links": [{ +# "href":"https://api.sandbox.paypal.com/v1/payments/sale/4KU52719R3958614J", +# "rel":"self", +# "method":"GET" +# },{ +# "href":"https://api.sandbox.paypal.com/v1/payments/sale/4KU52719R3958614J/refund", +# "rel":"refund", +# "method":"POST" +# },{ +# "href":"https://api.sandbox.paypal.com/v1/payments/payment/PAY-2LL14628DB722091TKKHXZHQ", +# "rel":"parent_payment", +# "method":"GET" +# }] +# } +# }] +# }], +# "links": [{ +# "href":"https://api.sandbox.paypal.com/v1/payments/payment/PAY-2LL14628DB722091TKKHXZHQ", +# "rel":"self", +# "method":"GET" +# }] +# } class PaypalForm(PaypalCommon):