def test_get_tx_from_notification_data_creates_refund_tx_when_missing( self): source_tx = self._create_transaction( 'direct', state='done', acquirer_reference=self.original_reference) data = dict( self.webhook_notification_payload, amount={ 'currency': 'USD', 'value': payment_utils.to_minor_currency_units(-self.amount, source_tx.currency_id) }, eventCode='REFUND', ) refund_tx = self.env[ 'payment.transaction']._get_tx_from_notification_data( 'adyen', data) self.assertTrue( refund_tx, msg= "If no refund tx is found with received refund data, a refund tx should be created" ) self.assertNotEqual(refund_tx, source_tx) self.assertEqual(refund_tx.source_transaction_id, source_tx)
def _stripe_create_payment_intent(self): """ Create and return a PaymentIntent. :return: The Payment Intent :rtype: dict """ if not self.token_id.stripe_payment_method: # Pre-SCA token -> migrate it self.token_id._stripe_sca_migrate_customer() payment_intent = self.acquirer_id._stripe_make_request( 'payment_intents', payload={ 'amount': payment_utils.to_minor_currency_units(self.amount, self.currency_id), 'currency': self.currency_id.name.lower(), 'confirm': True, 'customer': self.token_id.acquirer_ref, 'off_session': True, 'payment_method': self.token_id.stripe_payment_method, 'description': self.reference, }) return payment_intent
def _stripe_prepare_payment_intent_payload(self): """ Prepare the payload for the creation of a payment intent in Stripe format. Note: This method serves as a hook for modules that would fully implement Stripe Connect. Note: self.ensure_one() :return: The Stripe-formatted payload for the payment intent request :rtype: dict """ return { 'amount': payment_utils.to_minor_currency_units(self.amount, self.currency_id), 'currency': self.currency_id.name.lower(), 'confirm': True, 'customer': self.token_id.acquirer_ref, 'off_session': True, 'payment_method': self.token_id.stripe_payment_method, 'description': self.reference, 'capture_method': 'manual' if self.acquirer_id.capture_manually else 'automatic', }
def _stripe_create_payment_intent(self): """ Create and return a PaymentIntent. Note: self.ensure_one() :return: The Payment Intent :rtype: dict """ if not self.token_id.stripe_payment_method: # Pre-SCA token -> migrate it self.token_id._stripe_sca_migrate_customer() response = self.acquirer_id._stripe_make_request( 'payment_intents', payload={ 'amount': payment_utils.to_minor_currency_units(self.amount, self.currency_id), 'currency': self.currency_id.name.lower(), 'confirm': True, 'customer': self.token_id.acquirer_ref, 'off_session': True, 'payment_method': self.token_id.stripe_payment_method, 'description': self.reference, }, offline=self.operation == 'offline', ) if 'error' not in response: payment_intent = response else: # A processing error was returned in place of the payment intent error_msg = response['error'].get('message') self._set_error("Stripe: " + _( "The communication with the API failed.\n" "Stripe gave us the following info about the problem:\n'%s'", error_msg )) # Flag transaction as in error now as the intent status might have a valid value payment_intent = response['error'].get('payment_intent') # Get the PI from the error return payment_intent
def test_get_tx_from_notification_data_returns_refund_tx(self): source_tx = self._create_transaction( 'direct', state='done', acquirer_reference=self.original_reference) refund_tx = self._create_transaction( 'direct', reference='RefundTx', acquirer_reference=self.psp_reference, amount=-source_tx.amount, operation='refund', source_transaction_id=source_tx.id) data = dict( self.webhook_notification_payload, amount={ 'currency': 'USD', 'value': payment_utils.to_minor_currency_units(-source_tx.amount, refund_tx.currency_id) }, eventCode='REFUND', ) returned_tx = self.env[ 'payment.transaction']._get_tx_from_notification_data( 'adyen', data) self.assertEqual(returned_tx, refund_tx, msg="The existing refund tx is the one returned")
def _get_specific_rendering_values(self, processing_values): """ Override of payment to return Odoo-specific rendering values. Note: self.ensure_one() from `_get_processing_values` :param dict processing_values: The generic and specific processing values of the transaction :return: The dict of acquirer-specific processing values :rtype: dict """ res = super()._get_specific_rendering_values(processing_values) if self.provider != 'odoo': return res converted_amount = payment_utils.to_minor_currency_units( self.amount, self.currency_id, CURRENCY_DECIMALS.get(self.currency_id.name)) # The lang is taken from the context rather than from the partner because it is not required # to be logged to make a payment and because the lang is not always set on the partner. # Adyen only supports a reduced set of languages but, instead of looking for the closest # match in https://docs.adyen.com/checkout/components-web/localization-components, we simply # provide the lang string as is (after adapting the format) and let Adyen find the best fit. lang_code = (self._context.get('lang') or 'en-US').replace('_', '-') base_url = self.acquirer_id._get_base_url() signature = payment_utils.generate_access_token( converted_amount, self.currency_id.name, self.reference) data = { 'adyen_uuid': self.acquirer_id.odoo_adyen_account_id.adyen_uuid, 'payout': self.acquirer_id.odoo_adyen_payout_id.code, 'amount': { 'value': converted_amount, 'currency': self.currency_id.name, }, 'reference': self.reference, 'shopperLocale': lang_code, 'shopperReference': self.acquirer_id._odoo_compute_shopper_reference( self.partner_id.id), 'recurringProcessingModel': 'CardOnFile', 'storePaymentMethod': self.tokenize, # True by default on Adyen side # Since the Pay by Link API redirects the customer without any payload, we use the # /payment/status route directly as return url. 'returnUrl': urls.url_join(base_url, '/payment/status'), 'metadata': { 'merchant_signature': signature, 'notification_url': urls.url_join(base_url, OdooController._notification_url), }, # Proxy-specific data } return { 'data': json.dumps(data), 'api_url': self.acquirer_id._odoo_get_api_url(), }
def test_failed_webhook_refund_notification_sets_refund_transaction_in_error( self): source_tx = self._create_transaction( 'direct', state='done', acquirer_reference=self.original_reference) payload = dict(self.webhook_notification_batch_data, notificationItems=[{ 'NotificationRequestItem': dict( self.webhook_notification_payload, amount={ 'currency': 'USD', 'value': payment_utils.to_minor_currency_units( -self.amount, source_tx.currency_id) }, eventCode='REFUND', success='false', ) }]) self._webhook_notification_flow(payload) refund_tx = self.env['payment.transaction'].search([ ('source_transaction_id', '=', source_tx.id) ]) self.assertEqual( refund_tx.state, 'error', msg= "After a failed refund notification, the refund state should be in 'error'.", )
def _send_payment_request(self): """ Override of payment to send a payment request to Adyen through the Odoo proxy. Note: self.ensure_one() :return: None :raise: UserError if the transaction is not linked to a token """ super()._send_payment_request() if self.provider != 'odoo': return # Make the payment request if not self.token_id: raise UserError("Odoo Payments: " + _("The transaction is not linked to a token.")) converted_amount = payment_utils.to_minor_currency_units( self.amount, self.currency_id, CURRENCY_DECIMALS.get(self.currency_id.name)) base_url = self.acquirer_id._get_base_url() signature = payment_utils.generate_access_token( converted_amount, self.currency_id.name, self.reference) data = { 'payout': self.acquirer_id.odoo_adyen_payout_id.code, 'amount': { 'value': converted_amount, 'currency': self.currency_id.name, }, 'reference': self.reference, 'paymentMethod': { 'type': self.token_id.odoo_payment_method_type, 'storedPaymentMethodId': self.token_id.acquirer_ref, }, 'shopperReference': self.acquirer_id._odoo_compute_shopper_reference( self.partner_id.id), 'recurringProcessingModel': 'Subscription', 'shopperInteraction': 'ContAuth', 'metadata': { 'merchant_signature': signature, 'notification_url': urls.url_join(base_url, OdooController._notification_url), }, # Proxy-specific data } response_content = self.acquirer_id.odoo_adyen_account_id._adyen_rpc( 'payments', data) # Handle the payment request response _logger.info("payment request response:\n%s", pprint.pformat(response_content)) self._handle_feedback_data('odoo', response_content)
def _ogone_send_order_request(self, request_3ds_authentication=False): """ Make a new order request to Ogone and return the lxml etree parsed from the response. :param bool request_3ds_authentication: Whether a 3DS authentication should be requested if necessary to process the payment :return: The lxml etree :raise: ValidationError if the response can not be parsed to an lxml etree """ base_url = self.acquirer_id.get_base_url() return_url = urls.url_join(base_url, OgoneController._directlink_return_url) data = { # DirectLink parameters 'PSPID': self.acquirer_id.ogone_pspid, 'ORDERID': self.reference, 'USERID': self.acquirer_id.ogone_userid, 'PSWD': self.acquirer_id.ogone_password, 'AMOUNT': payment_utils.to_minor_currency_units(self.amount, None, 2), 'CURRENCY': self.currency_id.name, 'CN': self.partner_name or '', # Cardholder Name 'EMAIL': self.partner_email or '', 'OWNERADDRESS': self.partner_address or '', 'OWNERZIP': self.partner_zip or '', 'OWNERTOWN': self.partner_city or '', 'OWNERCTY': self.partner_country_id.code or '', 'OWNERTELNO': self.partner_phone or '', 'OPERATION': 'SAL', # direct sale # Alias Manager parameters 'ALIAS': self.token_id.acquirer_ref, 'ALIASPERSISTEDAFTERUSE': 'Y' if self.token_id.active else 'N', 'ECI': 9, # Recurring (from eCommerce) # 3DS parameters 'ACCEPTURL': return_url, 'DECLINEURL': return_url, 'EXCEPTIONURL': return_url, 'LANGUAGE': self.partner_lang or 'en_US', 'FLAG3D': 'Y' if request_3ds_authentication else 'N', } data['SHASIGN'] = self.acquirer_id._ogone_generate_signature( data, incoming=False) _logger.info("making payment request:\n%s", pprint.pformat({ k: v for k, v in data.items() if k != 'PSWD' })) # Log the payment request data without the password response_content = self.acquirer_id._ogone_make_request( 'directlink', data) try: tree = objectify.fromstring(response_content) except etree.XMLSyntaxError: raise ValidationError( "Ogone: " + "Received badly structured response from the API.") _logger.info("received payment request response as an etree:\n%s", etree.tostring(tree, pretty_print=True, encoding='utf-8')) return tree
def _send_refund_request(self, amount_to_refund=None, create_refund_transaction=True): """ Override of payment to send a refund request to Adyen. Note: self.ensure_one() :param float amount_to_refund: The amount to refund :param bool create_refund_transaction: Whether a refund transaction should be created or not :return: The refund transaction if any :rtype: recordset of `payment.transaction` """ if self.provider != 'adyen': return super()._send_refund_request( amount_to_refund=amount_to_refund, create_refund_transaction=create_refund_transaction ) refund_tx = super()._send_refund_request( amount_to_refund=amount_to_refund, create_refund_transaction=True ) # Make the refund request to Adyen converted_amount = payment_utils.to_minor_currency_units( -refund_tx.amount, # The amount is negative for refund transactions refund_tx.currency_id, arbitrary_decimal_number=CURRENCY_DECIMALS.get(refund_tx.currency_id.name) ) data = { 'merchantAccount': self.acquirer_id.adyen_merchant_account, 'amount': { 'value': converted_amount, 'currency': refund_tx.currency_id.name, }, 'reference': refund_tx.reference, } response_content = refund_tx.acquirer_id._adyen_make_request( url_field_name='adyen_checkout_api_url', endpoint='/payments/{}/refunds', endpoint_param=self.acquirer_reference, payload=data, method='POST' ) _logger.info( "refund request response for transaction with reference %s:\n%s", self.reference, pprint.pformat(response_content) ) # Handle the refund request response psp_reference = response_content.get('pspReference') status = response_content.get('status') if psp_reference and status == 'received': # The PSP reference associated with this /refunds request is different from the psp # reference associated with the original payment request. refund_tx.acquirer_reference = psp_reference return refund_tx
def _get_specific_rendering_values(self, processing_values): """ Override of payment to return Ogone-specific rendering values. Note: self.ensure_one() from `_get_processing_values` :param dict processing_values: The generic and specific processing values of the transaction :return: The dict of acquirer-specific processing values :rtype: dict """ res = super()._get_specific_rendering_values(processing_values) if self.acquirer_id.provider != 'ogone': return res return_url = urls.url_join(self.acquirer_id.get_base_url(), OgoneController._return_url) rendering_values = { 'PSPID': self.acquirer_id.ogone_pspid, 'ORDERID': self.reference, 'AMOUNT': payment_utils.to_minor_currency_units(self.amount, None, 2), 'CURRENCY': self.currency_id.name, 'LANGUAGE': self.partner_lang or 'en_US', 'EMAIL': self.partner_email or '', 'OWNERADDRESS': self.partner_address or '', 'OWNERZIP': self.partner_zip or '', 'OWNERTOWN': self.partner_city or '', 'OWNERCTY': self.partner_country_id.code or '', 'OWNERTELNO': self.partner_phone or '', 'OPERATION': 'SAL', # direct sale 'USERID': self.acquirer_id.ogone_userid, 'ACCEPTURL': return_url, 'DECLINEURL': return_url, 'EXCEPTIONURL': return_url, 'CANCELURL': return_url, } if self.tokenize: rendering_values.update({ 'ALIAS': payment_utils.singularize_reference_prefix( prefix='ODOO-ALIAS'), 'ALIASUSAGE': _("Storing your payment details is necessary for future use."), }) rendering_values.update({ 'SHASIGN': self.acquirer_id._ogone_generate_signature(rendering_values, incoming=False).upper(), 'api_url': self.acquirer_id._ogone_get_api_url('hosted_payment_page'), }) return rendering_values
def _send_refund_request(self): """ Override of payment to send a refund request to Authorize. Note: self.ensure_one() :return: None :raise: ValidationError if a badly structured response is received """ super()._send_refund_request() if self.provider != 'ogone': return data = { 'PSPID': self.acquirer_id.ogone_pspid, 'ORDERID': self.reference, 'PAYID': self.acquirer_reference, 'USERID': self.acquirer_id.ogone_userid, 'PSWD': self.acquirer_id.ogone_password, 'AMOUNT': payment_utils.to_minor_currency_units(self.amount, None, 2), 'CURRENCY': self.currency_id.name, 'OPERATION': 'RFS', # refund } data['SHASIGN'] = self.acquirer_id._ogone_generate_signature( data, incoming=False) _logger.info("making refund request:\n%s", pprint.pformat({ k: v for k, v in data.items() if k != 'PSWD' })) # Log the refund request data without the password response_content = self.acquirer_id._ogone_make_request( 'maintenancedirect', data) try: tree = objectify.fromstring(response_content) except etree.XMLSyntaxError: raise ValidationError( "Ogone: " + "Received badly structured response from the API.") _logger.info("received refund request response as an etree:\n%s", etree.tostring(tree, pretty_print=True, encoding='utf-8')) feedback_data = { 'FEEDBACK_TYPE': 'directlink', 'ORDERID': tree.get('orderID'), 'tree': tree, } _logger.info("entering _handle_feedback_data with data:\n%s", pprint.pformat(feedback_data)) self._handle_feedback_data('ogone', feedback_data)
def test_redirect_form_values(self): """ Test the values of the redirect form inputs for online payments. """ return_url = self._build_url(OgoneController._hosted_payment_page_return_url) expected_values = { 'PSPID': self.ogone.ogone_pspid, 'ORDERID': self.reference, 'AMOUNT': str(payment_utils.to_minor_currency_units(self.amount, None, 2)), 'CURRENCY': self.currency.name, 'LANGUAGE': self.partner.lang, 'EMAIL': self.partner.email, 'OWNERZIP': self.partner.zip, 'OWNERADDRESS': payment_utils.format_partner_address( self.partner.street, self.partner.street2 ), 'OWNERCTY': self.partner.country_id.code, 'OWNERTOWN': self.partner.city, 'OWNERTELNO': self.partner.phone, 'OPERATION': 'SAL', # direct sale 'USERID': self.ogone.ogone_userid, 'ACCEPTURL': return_url, 'DECLINEURL': return_url, 'EXCEPTIONURL': return_url, 'CANCELURL': return_url, 'ALIAS': None, 'ALIASUSAGE': None, } expected_values['SHASIGN'] = self.ogone._ogone_generate_signature( expected_values, incoming=False ).upper() tx = self.create_transaction(flow='redirect') self.assertEqual(tx.tokenize, False) with mute_logger('odoo.addons.payment.models.payment_transaction'): processing_values = tx._get_processing_values() form_info = self._extract_values_from_html_form(processing_values['redirect_form_html']) self.assertEqual(form_info['action'], 'https://ogone.test.v-psp.com/ncol/test/orderstandard_utf8.asp') inputs = form_info['inputs'] self.assertEqual(len(expected_values), len(inputs)) for rendering_key, value in expected_values.items(): form_key = rendering_key.replace('_', '.') self.assertEqual( inputs[form_key], value, f"received value {inputs[form_key]} for input {form_key} (expected {value})" )
def _send_payment_request(self): """ Override of payment to send a payment request to Adyen. Note: self.ensure_one() :return: None :raise: UserError if the transaction is not linked to a token """ super()._send_payment_request() if self.provider != 'adyen': return # Make the payment request to Adyen if not self.token_id: raise UserError("Adyen: " + _("The transaction is not linked to a token.")) converted_amount = payment_utils.to_minor_currency_units( self.amount, self.currency_id, CURRENCY_DECIMALS.get(self.currency_id.name) ) data = { 'merchantAccount': self.acquirer_id.adyen_merchant_account, 'amount': { 'value': converted_amount, 'currency': self.currency_id.name, }, 'reference': self.reference, 'paymentMethod': { 'recurringDetailReference': self.token_id.acquirer_ref, }, 'shopperReference': self.token_id.adyen_shopper_reference, 'recurringProcessingModel': 'Subscription', 'shopperIP': payment_utils.get_customer_ip_address(), 'shopperInteraction': 'ContAuth', } response_content = self.acquirer_id._adyen_make_request( url_field_name='adyen_checkout_api_url', endpoint='/payments', payload=data, method='POST' ) # Handle the payment request response _logger.info( "payment request response for transaction with reference %s:\n%s", self.reference, pprint.pformat(response_content) ) self._handle_feedback_data('adyen', response_content)
def adyen_payment_methods(self, acquirer_id, amount=None, currency_id=None, partner_id=None): """ Query the available payment methods based on the transaction context. :param int acquirer_id: The acquirer handling the transaction, as a `payment.acquirer` id :param float amount: The transaction amount :param int currency_id: The transaction currency, as a `res.currency` id :param int partner_id: The partner making the transaction, as a `res.partner` id :return: The JSON-formatted content of the response :rtype: dict """ acquirer_sudo = request.env['payment.acquirer'].sudo().browse( acquirer_id) currency = request.env['res.currency'].browse(currency_id) currency_code = currency_id and currency.name converted_amount = amount and currency_code and payment_utils.to_minor_currency_units( amount, currency, CURRENCY_DECIMALS.get(currency_code)) partner_sudo = partner_id and request.env['res.partner'].sudo().browse( partner_id).exists() # The lang is taken from the context rather than from the partner because it is not required # to be logged in to make a payment, and because the lang is not always set on the partner. # Adyen only supports a limited set of languages but, instead of looking for the closest # match in https://docs.adyen.com/checkout/components-web/localization-components, we simply # provide the lang string as is (after adapting the format) and let Adyen find the best fit. lang_code = (request.context.get('lang') or 'en-US').replace('_', '-') shopper_reference = partner_sudo and f'ODOO_PARTNER_{partner_sudo.id}' data = { 'merchantAccount': acquirer_sudo.adyen_merchant_account, 'amount': converted_amount, 'countryCode': partner_sudo.country_id.code or None, # ISO 3166-1 alpha-2 (e.g.: 'BE') 'shopperLocale': lang_code, # IETF language tag (e.g.: 'fr-BE') 'shopperReference': shopper_reference, 'channel': 'Web', } response_content = acquirer_sudo._adyen_make_request( url_field_name='adyen_checkout_api_url', endpoint='/paymentMethods', payload=data, method='POST') _logger.info("paymentMethods request response:\n%s", pprint.pformat(response_content)) return response_content
def _stripe_prepare_payment_intent_payload(self): """ Prepare the payload for the creation of a payment intent in Stripe format. Note: This method is overridden by the internal module responsible for Stripe Connect. Note: self.ensure_one() :return: The Stripe-formatted payload for the payment intent request :rtype: dict """ return { 'amount': payment_utils.to_minor_currency_units(self.amount, self.currency_id), 'currency': self.currency_id.name.lower(), 'confirm': True, 'customer': self.token_id.acquirer_ref, 'off_session': True, 'payment_method': self.token_id.stripe_payment_method, 'description': self.reference, }
def _send_refund_request(self, amount_to_refund=None, create_refund_transaction=True): """ Override of payment to send a refund request to Stripe. Note: self.ensure_one() :param float amount_to_refund: The amount to refund. :param bool create_refund_transaction: Whether a refund transaction should be created or not. :return: The refund transaction, if any. :rtype: recordset of `payment.transaction` """ if self.provider != 'stripe': return super()._send_refund_request( amount_to_refund=amount_to_refund, create_refund_transaction=create_refund_transaction, ) refund_tx = super()._send_refund_request( amount_to_refund=amount_to_refund, create_refund_transaction=True) # Make the refund request to stripe. data = self.acquirer_id._stripe_make_request( 'refunds', payload={ 'charge': self.acquirer_reference, 'amount': payment_utils.to_minor_currency_units( -refund_tx. amount, # Refund transactions' amount is negative, inverse it. refund_tx.currency_id, ), }) _logger.info( "Refund request response for transaction wih reference %s:\n%s", self.reference, pprint.pformat(data)) # Handle the refund request response. notification_data = {} StripeController._include_refund_in_notification_data( data, notification_data) refund_tx._handle_notification_data('stripe', notification_data) return refund_tx
def _get_specific_rendering_values(self, processing_values): """ Override of payment to return Sips-specific rendering values. Note: self.ensure_one() from `_get_processing_values` :param dict processing_values: The generic and specific processing values of the transaction :return: The dict of acquirer-specific processing values :rtype: dict """ res = super()._get_specific_rendering_values(processing_values) if self.provider != 'sips': return res base_url = self.get_base_url() data = { 'amount': payment_utils.to_minor_currency_units(self.amount, self.currency_id), 'currencyCode': SUPPORTED_CURRENCIES[self.currency_id.name], # The ISO 4217 code 'merchantId': self.acquirer_id.sips_merchant_id, 'normalReturnUrl': urls.url_join(base_url, SipsController._return_url), 'automaticResponseUrl': urls.url_join(base_url, SipsController._webhook_url), 'transactionReference': self.reference, 'statementReference': self.reference, 'keyVersion': self.acquirer_id.sips_key_version, 'returnContext': json.dumps(dict(reference=self.reference)), } api_url = self.acquirer_id.sips_prod_url if self.acquirer_id.state == 'enabled' \ else self.acquirer_id.sips_test_url data = '|'.join([f'{k}={v}' for k, v in data.items()]) return { 'api_url': api_url, 'Data': data, 'InterfaceVersion': self.acquirer_id.sips_version, 'Seal': self.acquirer_id._sips_generate_shasign(data), }
def test_processing_values(self): tx = self.create_transaction(flow='direct') with mute_logger('odoo.addons.payment.models.payment_transaction'), \ patch( 'odoo.addons.payment.utils.generate_access_token', new=self._generate_test_access_token ): processing_values = tx._get_processing_values() converted_amount = 111111 self.assertEqual( payment_utils.to_minor_currency_units(self.amount, self.currency), converted_amount, ) self.assertEqual(processing_values['converted_amount'], converted_amount) with patch('odoo.addons.payment.utils.generate_access_token', new=self._generate_test_access_token): self.assertTrue( payment_utils.check_access_token( processing_values['access_token'], self.reference, converted_amount, self.partner.id))
def _verify_notification_signature(self, received_signature, tx): """ Check that the signature computed from the transaction values matches the received one. :param str received_signature: The signature sent with the notification :param recordset tx: The transaction of the notification, as a `payment.transaction` record :return: Whether the signatures match :rtype: str """ if not received_signature: _logger.warning("ignored notification with missing signature") return False converted_amount = payment_utils.to_minor_currency_units( tx.amount, tx.currency_id, CURRENCY_DECIMALS.get(tx.currency_id.name)) if not payment_utils.check_access_token( received_signature, converted_amount, tx.currency_id.name, tx.reference): _logger.warning("ignored notification with invalid signature") return False return True
def _get_specific_processing_values(self, processing_values): """ Override of payment to return Adyen-specific processing values. Note: self.ensure_one() from `_get_processing_values` :param dict processing_values: The generic processing values of the transaction :return: The dict of acquirer-specific processing values :rtype: dict """ res = super()._get_specific_processing_values(processing_values) if self.provider != 'adyen': return res converted_amount = payment_utils.to_minor_currency_units( self.amount, self.currency_id, CURRENCY_DECIMALS.get(self.currency_id.name)) return { 'converted_amount': converted_amount, 'access_token': payment_utils.generate_access_token( processing_values['reference'], converted_amount, processing_values['partner_id']) }
def _stripe_create_checkout_session(self): """ Create and return a Checkout Session. :return: The Checkout Session :rtype: dict """ # Filter payment method types by available payment method existing_pms = [ pm.name.lower() for pm in self.env['payment.icon'].search([]) ] linked_pms = [ pm.name.lower() for pm in self.acquirer_id.payment_icon_ids ] pm_filtered_pmts = filter( lambda pmt: pmt.name == 'card' # If the PM (payment.icon) record related to a PMT doesn't exist, don't filter out the # PMT because the user couldn't even have linked it to the acquirer in the first place. or (pmt.name in linked_pms or pmt.name not in existing_pms), PAYMENT_METHOD_TYPES) # Filter payment method types by country code country_code = self.partner_country_id and self.partner_country_id.code.lower( ) country_filtered_pmts = filter( lambda pmt: not pmt.countries or country_code in pmt.countries, pm_filtered_pmts) # Filter payment method types by currency name currency_name = self.currency_id.name.lower() currency_filtered_pmts = filter( lambda pmt: not pmt.currencies or currency_name in pmt.currencies, country_filtered_pmts) # Filter payment method types by recurrence if the transaction must be tokenized if self.tokenize: recurrence_filtered_pmts = filter( lambda pmt: pmt.recurrence == 'recurring', currency_filtered_pmts) else: recurrence_filtered_pmts = currency_filtered_pmts # Build the session values related to payment method types pmt_values = {} for pmt_id, pmt_name in enumerate( map(lambda pmt: pmt.name, recurrence_filtered_pmts)): pmt_values[f'payment_method_types[{pmt_id}]'] = pmt_name # Create the session according to the operation and return it customer = self._stripe_create_customer() common_session_values = self._get_common_stripe_session_values( pmt_values, customer) base_url = self.acquirer_id.get_base_url() if self.operation == 'online_redirect': return_url = f'{urls.url_join(base_url, StripeController._checkout_return_url)}' \ f'?reference={urls.url_quote_plus(self.reference)}' # Specify a future usage for the payment intent to: # 1. attach the payment method to the created customer # 2. trigger a 3DS check if one if required, while the customer is still present future_usage = 'off_session' if self.tokenize else None capture_method = 'manual' if self.acquirer_id.capture_manually else 'automatic' checkout_session = self.acquirer_id._stripe_make_request( 'checkout/sessions', payload={ **common_session_values, 'mode': 'payment', 'success_url': return_url, 'cancel_url': return_url, 'line_items[0][price_data][currency]': self.currency_id.name, 'line_items[0][price_data][product_data][name]': self.reference, 'line_items[0][price_data][unit_amount]': payment_utils.to_minor_currency_units( self.amount, self.currency_id), 'line_items[0][quantity]': 1, 'payment_intent_data[description]': self.reference, 'payment_intent_data[setup_future_usage]': future_usage, 'payment_intent_data[capture_method]': capture_method, }) self.stripe_payment_intent = checkout_session['payment_intent'] else: # 'validation' # {CHECKOUT_SESSION_ID} is a template filled by Stripe when the Session is created return_url = f'{urls.url_join(base_url, StripeController._validation_return_url)}' \ f'?reference={urls.url_quote_plus(self.reference)}' \ f'&checkout_session_id={{CHECKOUT_SESSION_ID}}' checkout_session = self.acquirer_id._stripe_make_request( 'checkout/sessions', payload={ **common_session_values, 'mode': 'setup', 'success_url': return_url, 'cancel_url': return_url, 'setup_intent_data[description]': self.reference, }) return checkout_session
def _send_payment_request(self): """ Override of payment to send a payment request to Ogone. Note: self.ensure_one() :return: None :raise: UserError if the transaction is not linked to a token """ super()._send_payment_request() if self.provider != 'ogone': return if not self.token_id: raise UserError("Ogone: " + _("The transaction is not linked to a token.")) # Make the payment request base_url = self.acquirer_id.get_base_url() data = { # DirectLink parameters 'PSPID': self.acquirer_id.ogone_pspid, 'ORDERID': self.reference, 'USERID': self.acquirer_id.ogone_userid, 'PSWD': self.acquirer_id.ogone_password, 'AMOUNT': payment_utils.to_minor_currency_units(self.amount, None, 2), 'CURRENCY': self.currency_id.name, 'CN': self.partner_name or '', # Cardholder Name 'EMAIL': self.partner_email or '', 'OWNERADDRESS': self.partner_address or '', 'OWNERZIP': self.partner_zip or '', 'OWNERTOWN': self.partner_city or '', 'OWNERCTY': self.partner_country_id.code or '', 'OWNERTELNO': self.partner_phone or '', 'OPERATION': 'SAL', # direct sale # Alias Manager parameters 'ALIAS': self.token_id.acquirer_ref, 'ALIASPERSISTEDAFTERUSE': 'Y', 'ECI': 9, # Recurring (from eCommerce) } data['SHASIGN'] = self.acquirer_id._ogone_generate_signature( data, incoming=False) _logger.info("making payment request:\n%s", pprint.pformat({ k: v for k, v in data.items() if k != 'PSWD' })) # Log the payment request data without the password response_content = self.acquirer_id._ogone_make_request(data) try: tree = objectify.fromstring(response_content) except etree.XMLSyntaxError: raise ValidationError( "Ogone: " + "Received badly structured response from the API.") # Handle the feedback data _logger.info("received payment request response as an etree:\n%s", etree.tostring(tree, pretty_print=True, encoding='utf-8')) feedback_data = {'ORDERID': tree.get('orderID'), 'tree': tree} _logger.info("entering _handle_feedback_data with data:\n%s", pprint.pformat(feedback_data)) self._handle_feedback_data('ogone', feedback_data)