def handle_processor_response(self, response, basket=None): """ Executes an approved Paystack transaction. This method will record payment processor response for future usage. Arguments: response: Transaction response received from Paystack after successfull payment. basket (Basket): Basket being purchased via the payment processor. Returns: HandledProcessorResponse """ authorization = response.get('authorization') transaction_id = response.get('id') self.record_processor_response(authorization, transaction_id=transaction_id, basket=basket) logger.info( "Successfully executed Paystack payment [%s] for basket: %d.", transaction_id, basket.id) currency = response.get('currency') total = basket.total_incl_tax return HandledProcessorResponse( transaction_id=transaction_id, total=total, currency=currency, card_number=authorization.get('last4'), card_type=authorization.get('card_type'))
def handle_processor_response(self, transaction_response, basket=None): """ Execute an approved AuthorizeNet transaction. This method will record payment processor response for future usage. Arguments: transaction_response: Transaction details received from authorizeNet after successfull payment basket (Basket): Basket being purchased via the payment processor. Returns: HandledProcessorResponse """ transaction_id = transaction_response.transaction.transId transaction_dict = LxmlObjectJsonEncoder().encode(transaction_response) self.record_processor_response(transaction_dict, transaction_id=transaction_id, basket=basket) logger.info("Successfully executed AuthorizeNet payment [%s] for basket [%d].", transaction_id, basket.id) currency = basket.currency total = float(transaction_response.transaction.settleAmount) card_info = transaction_response.transaction.payment.creditCard return HandledProcessorResponse( transaction_id=transaction_id, total=total, currency=currency, card_number=card_info.cardNumber, card_type=card_info.cardType )
def handle_processor_response(self, response, basket=None): """ Handle a response (i.e., "merchant notification") from CyberSource. Arguments: response (dict): Dictionary of parameters received from the payment processor. Keyword Arguments: basket (Basket): Basket being purchased via the payment processor. Raises: UserCancelled: Indicates the user cancelled payment. TransactionDeclined: Indicates the payment was declined by the processor. GatewayError: Indicates a general error on the part of the processor. InvalidCyberSourceDecision: Indicates an unknown decision value. Known values are ACCEPT, CANCEL, DECLINE, ERROR. PartialAuthorizationError: Indicates only a portion of the requested amount was authorized. Returns: HandledProcessorResponse """ # Validate the signature if not self.is_signature_valid(response): raise InvalidSignatureError # Raise an exception for payments that were not accepted. Consuming code should be responsible for handling # and logging the exception. decision = response['decision'].lower() if decision != 'accept': reason_code = int(response['reason_code']) if decision == 'error' and reason_code == 104: raise DuplicateReferenceNumber raise { 'cancel': UserCancelled, 'decline': TransactionDeclined, 'error': GatewayError }.get(decision, InvalidCybersourceDecision) # Raise an exception if the authorized amount differs from the requested amount. # Note (CCB): We should never reach this point in production since partial authorization is disabled # for our account, and should remain that way until we have a proper solution to allowing users to # complete authorization for the entire order. if response['auth_amount'] != response['req_amount']: raise PartialAuthorizationError currency = response['req_currency'] total = Decimal(response['req_amount']) transaction_id = response['transaction_id'] card_number = response['req_card_number'] card_type = CYBERSOURCE_CARD_TYPE_MAP.get(response['req_card_type']) return HandledProcessorResponse(transaction_id=transaction_id, total=total, currency=currency, card_number=card_number, card_type=card_type)
def handle_processor_response(self, response, basket=None): return HandledProcessorResponse( transaction_id=basket.id, total=basket.total_incl_tax, currency=basket.currency, card_number=basket.owner.username, card_type='Visa' )
def handle_processor_response(self, response, basket=None): """ Handle a response (i.e., "merchant notification") from FOMO Pay. This method does the following: 1. Verify the validity of the response. 2. Create PaymentEvents and Sources for successful payments. Arguments: response (dict): Dictionary of parameters received from the payment processor. Keyword Arguments: basket (Basket): Basket being purchased via the payment processor. Raises: TransactionDeclined: Indicates the payment was declined by the processor. GatewayError: Indicates a general error on the part of the processor. """ # Validate the signature if not self.is_signature_valid(response): raise InvalidSignatureError # Raise an exception for payments that were not accepted. Consuming code should be responsible for handling # and logging the exception. decision = response['result'] if decision != self.PAYMENT_APPROVED: # FOMO Pay is not explicit to say what is the cause of the error, # it is necessary that we make our own checks. # Check if the user made a payment request twice for the same order. if Order.objects.filter(number=response['transaction']).exists(): raise DuplicateReferenceNumber # Raise an exception if the authorized amount differs from the requested amount. if response.get('cash_amount') != basket.total_incl_tax: raise PartialAuthorizationError raise { 'cancel': UserCancelled, 'decline': TransactionDeclined, 'error': GatewayError, 'review': AuthorizationError, }.get(decision, InvalidFomoPayDecision) currency = response.get('cash_currency', basket.currency) total = Decimal(response.get('cash_amount', basket.total_incl_tax)) transaction_id = response.get('payment_id') return HandledProcessorResponse(transaction_id=transaction_id, total=total, currency=currency, card_type='WeChat QR Payment', card_number='.')
def handle_processor_response(self, response, basket=None): """ Handle a response (i.e., "merchant notification") from CyberSource. Arguments: response (dict): Dictionary of parameters received from the payment processor. Keyword Arguments: basket (Basket): Basket being purchased via the payment processor. Raises: AuthorizationError: Authorization was declined. UserCancelled: Indicates the user cancelled payment. TransactionDeclined: Indicates the payment was declined by the processor. GatewayError: Indicates a general error on the part of the processor. InvalidCyberSourceDecision: Indicates an unknown decision value. Known values are ACCEPT, CANCEL, DECLINE, ERROR, REVIEW. PartialAuthorizationError: Indicates only a portion of the requested amount was authorized. Returns: HandledProcessorResponse """ data = response['data'] status = response['status'] message = response['message'] try: chargecode = response['data']['chargecode'] except: chargecode = None if chargecode == "00" or chargecode == "0": pass else: logger.error("Transaction failed: response", str(response)) raise TransactionDeclined currency = response['data']['currency'] total = Decimal(response['data']['amount']) try: transaction_id = response['data']['txid'] # Error Notifications does not include a transaction id. except: transaction_id = None card_number = response['data']['card']['last4digits'] card_type = response['data']['card']['brand'] return HandledProcessorResponse( transaction_id=transaction_id, total=total, currency=currency, card_number=card_number, card_type=card_type )
def handle_processor_response(self, response, basket=None): # pylint: disable=unused-argument """ Handle a response (i.e., "merchant notification"). This method does the following: 1. Verify the validity of the response. 2. Create PaymentEvents and Sources for successful payments. Arguments: response (dict): Dictionary of parameters received from the payment processor. Keyword Arguments: basket (Basket): Basket being purchased via the payment processor. Raises: TransactionDeclined: Indicates the payment was declined by the processor. GatewayError: Indicates a general error on the part of the processor. InvalidEdnxDecision: Indicates an unknown decision value Returns: HandleProcessorResponse: Payment info (transaction_id, total, currency, card_number and card_type) """ # Validate the signature if not self.is_signature_valid(response): raise InvalidSignatureError # Raise an exception for payments that were not accepted. Consuming code should be responsible for handling # and logging the exception. transaction_state = response['state_pol'] if transaction_state != self.TRANSACTION_ACCEPTED: exception = { self.TRANSACTION_DECLINED: TransactionDeclined, self.TRANSACTION_ERROR: GatewayError, self.TRANSACTION_PENDING: TransactionPending, }.get(transaction_state, InvalidEdnxDecision) raise exception currency = response.get('currency') total = Decimal(response.get('value')) transaction_id = response.get('transaction_id') card_number = response.get('cc_number', '') card_type = response.get('lapPaymentMethod', '') return HandledProcessorResponse( transaction_id=transaction_id, total=total, currency=currency, card_number=card_number, card_type=card_type, )
def handle_processor_response(self, response, basket=None): """ Verify that the payment was successfully processed -- because Trust but Verify. https://saferpay.github.io/jsonapi/index.html#Payment_v1_PaymentPage_Assert If payment did not succeed, raise GatewayError and log error. Once payment has been verified, call Capture to finalize the payment https://saferpay.github.io/jsonapi/index.html#Payment_v1_Transaction_Capture The generated CaptureId will be used to generate refunds. Args: response (str): this is actually the request token. """ token = response assert_request_data = {"Token": token} # Check payment was successful (this will raise in case of invalid payment) assert_data = self.make_api_json_request( "Payment/v1/PaymentPage/Assert", method="POST", data=assert_request_data, basket=basket, ) total = int(assert_data["Transaction"]["Amount"]["Value"]) / 100.0 currency = assert_data["Transaction"]["Amount"]["CurrencyCode"] card_number = assert_data["PaymentMeans"]["Card"]["MaskedNumber"] card_type = assert_data["PaymentMeans"]["Brand"]["PaymentMethod"] transaction_id = assert_data["Transaction"]["Id"] # Finalize payment capture_request_data = { "TransactionReference": { "TransactionId": transaction_id } } capture_data = self.make_api_json_request( "Payment/v1/Transaction/Capture", method="POST", data=capture_request_data, basket=basket, ) capture_id = capture_data["CaptureId"] return HandledProcessorResponse( transaction_id=capture_id, total=total, currency=currency, card_number=card_number, card_type=card_type, )
def handle_processor_response(self, response, basket=None): token = response logger.info( 'Response received from handle_processor_response:RazorPay [%s]', str(response)) order_number = basket.order_number currency = basket.currency return HandledProcessorResponse( transaction_id=None, # transaction_id, total=None, # total, currency=currency, card_number=None, # card_number, card_type=None #card_type )
def handle_processor_response(self, response, basket=None): transaction_id = response.get('out_trade_no') PaymentProcessorResponse.objects.filter( processor_name=self.NAME, transaction_id=transaction_id).update(response=response, basket=basket) total = Decimal(response.get('total_fee')) email = response.get('buyer_email') label = 'PayPal ({})'.format(email) if email else 'PayPal Account' return HandledProcessorResponse(transaction_id=transaction_id, total=total, currency='CNY', card_number=label, card_type=None)
def handle_processor_response(self, response, basket=None): token = response order_number = basket.order_number currency = basket.currency # NOTE: In the future we may want to get/create a Customer. See https://stripe.com/docs/api#customers. try: charge = stripe.Charge.create( amount=self._get_basket_amount(basket), currency=currency, source=token, description=order_number, metadata={'order_number': order_number}) transaction_id = charge.id # NOTE: Charge objects subclass the dict class so there is no need to do any data transformation # before storing the response in the database. self.record_processor_response(charge, transaction_id=transaction_id, basket=basket) logger.info( 'Successfully created Stripe charge [%s] for basket [%d].', transaction_id, basket.id) except stripe.error.CardError as ex: base_message = "Stripe payment for basket [%d] declined with HTTP status [%d]" exception_format_string = "{}: %s".format(base_message) body = ex.json_body logger.exception(exception_format_string, basket.id, ex.http_status, body) self.record_processor_response(body, basket=basket) raise TransactionDeclined(base_message, basket.id, ex.http_status) from ex total = basket.total_incl_tax card_number = charge.source.last4 card_type = STRIPE_CARD_TYPE_MAP.get(charge.source.brand) return HandledProcessorResponse(transaction_id=transaction_id, total=total, currency=currency, card_number=card_number, card_type=card_type)
def handle_processor_response(self, response, basket=None): """ Handle a response (i.e., "merchant notification") from CyberSource. Arguments: response (dict): Dictionary of parameters received from the payment processor. Keyword Arguments: basket (Basket): Basket being purchased via the payment processor. Raises: AuthorizationError: Authorization was declined. UserCancelled: Indicates the user cancelled payment. TransactionDeclined: Indicates the payment was declined by the processor. GatewayError: Indicates a general error on the part of the processor. InvalidCyberSourceDecision: Indicates an unknown decision value. Known values are ACCEPT, CANCEL, DECLINE, ERROR, REVIEW. PartialAuthorizationError: Indicates only a portion of the requested amount was authorized. Returns: HandledProcessorResponse """ # Validate the signature if not self.is_signature_valid(response): raise InvalidSignatureError # Raise an exception for payments that were not accepted. Consuming code should be responsible for handling # and logging the exception. decision = response['decision'].lower() if decision != 'accept': reason_code = int(response['reason_code']) if decision == 'error' and reason_code == 104: # This means user submitted payment request twice within 15 min. # We need to check if user first payment notification was handled successfuly and user has an order # if user has an order we can raise DuplicateReferenceNumber exception else we need to continue # the order creation process. to upgrade user in correct course mode. if Order.objects.filter( number=response['req_reference_number']).exists(): raise DuplicateReferenceNumber else: logger.info( 'Received duplicate CyberSource payment notification for basket [%d] which is not associated ' 'with any existing order. Continuing to validation and order creation processes.', basket.id, ) else: raise { 'cancel': UserCancelled, 'decline': TransactionDeclined, 'error': GatewayError, 'review': AuthorizationError, }.get(decision, InvalidCybersourceDecision) # Raise an exception if the authorized amount differs from the requested amount. # Note (CCB): We should never reach this point in production since partial authorization is disabled # for our account, and should remain that way until we have a proper solution to allowing users to # complete authorization for the entire order if response['auth_amount'] and response['auth_amount'] != response[ 'req_amount']: raise PartialAuthorizationError currency = response['req_currency'] total = Decimal(response['req_amount']) transaction_id = response.get( 'transaction_id', None) # Error Notifications does not include a transaction id. card_number = response['req_card_number'] card_type = CYBERSOURCE_CARD_TYPE_MAP.get(response['req_card_type']) return HandledProcessorResponse(transaction_id=transaction_id, total=total, currency=currency, card_number=card_number, card_type=card_type)
def handle_processor_response(self, response, basket=None): """ Execute an approved PayPal payment. This method creates PaymentEvents and Sources for approved payments. Arguments: response (dict): Dictionary of parameters returned by PayPal in the `return_url` query string. Keyword Arguments: basket (Basket): Basket being purchased via the payment processor. Raises: GatewayError: Indicates a general error or unexpected behavior on the part of PayPal which prevented an approved payment from being executed. Returns: HandledProcessorResponse """ data = {'payer_id': response.get('PayerID')} # By default PayPal payment will be executed only once. available_attempts = 1 # Add retry attempts (provided in the configuration) # if the waffle switch 'ENABLE_PAYPAL_RETRY' is set if waffle.switch_is_active('PAYPAL_RETRY_ATTEMPTS'): available_attempts = available_attempts + self.retry_attempts for attempt_count in range(1, available_attempts + 1): payment = paypalrestsdk.Payment.find(response.get('paymentId'), api=self.paypal_api) payment.execute(data) if payment.success(): # On success break the loop. break # Raise an exception for payments that were not successfully executed. Consuming code is # responsible for handling the exception error = self._get_error(payment) # pylint: disable=unsubscriptable-object entry = self.record_processor_response( error, transaction_id=error['debug_id'], basket=basket) logger.warning( "Failed to execute PayPal payment on attempt [%d]. " "PayPal's response was recorded in entry [%d].", attempt_count, entry.id) # After utilizing all retry attempts, raise the exception 'GatewayError' if attempt_count == available_attempts: logger.error( "Failed to execute PayPal payment [%s]. " "PayPal's response was recorded in entry [%d].", payment.id, entry.id) raise GatewayError self.record_processor_response(payment.to_dict(), transaction_id=payment.id, basket=basket) logger.info( "Successfully executed PayPal payment [%s] for basket [%d].", payment.id, basket.id) currency = payment.transactions[0].amount.currency total = Decimal(payment.transactions[0].amount.total) transaction_id = payment.id # payer_info.email may be None, see: # http://stackoverflow.com/questions/24090460/paypal-rest-api-return-empty-payer-info-for-non-us-accounts email = payment.payer.payer_info.email label = 'PayPal ({})'.format(email) if email else 'PayPal Account' return HandledProcessorResponse(transaction_id=transaction_id, total=total, currency=currency, card_number=label, card_type=None)
def fulfill_basket(self, basket_id, site): logger.info('Trying to complete order for frozen basket %d', basket_id) basket = self.get_valid_basket(basket_id) if not basket: return False # We need to check if an order for basket exists. try: order = Order.objects.get(basket=basket) logger.info('Basket %d does have a existing order %s', basket.id, basket.order_number) except Order.DoesNotExist: order = None # if no order exists we need to create a new order. if not order: basket.strategy = strategy.Default() # Need to handle the case that applied voucher has been expired. # This will create the order with out discount but subsequently # run the fulfillment to update course mode. try: Applicator().apply(basket, user=basket.owner) except ValueError: basket.clear_vouchers() payment_notification = self.get_payment_notification(basket) if not payment_notification: return False if payment_notification.transaction_id.startswith('PAY'): card_number = 'Paypal Account' card_type = None else: card_number = payment_notification.response['req_card_number'] card_type = CYBERSOURCE_CARD_TYPE_MAP.get( payment_notification.response['req_card_type']) self.payment_processor = _get_payment_processor( site, payment_notification.processor_name) # Create handled response handled_response = HandledProcessorResponse( transaction_id=payment_notification.transaction_id, total=basket.total_excl_tax, currency=basket.currency, card_number=card_number, card_type=card_type) # Record Payment and try to place order try: self.record_payment( basket=basket, handled_processor_response=handled_response) shipping_method = NoShippingRequired() shipping_charge = shipping_method.calculate(basket) order_total = OrderTotalCalculator().calculate( basket, shipping_charge) user = basket.owner # Given a basket, order number generation is idempotent. Although we've already # generated this order number once before, it's faster to generate it again # than to retrieve an invoice number from PayPal. order_number = basket.order_number self.handle_order_placement( order_number=order_number, user=user, basket=basket, shipping_address=None, shipping_method=shipping_method, shipping_charge=shipping_charge, billing_address=None, order_total=order_total, ) logger.info('Successfully created order for basket %d', basket.id) return True except: # pylint: disable=bare-except logger.exception('Unable to create order for basket %d', basket.id) return False # Start order fulfillment if order exists but is not fulfilled. elif order.is_fulfillable: order_lines = order.lines.all() line_quantities = [line.quantity for line in order_lines] shipping_event, __ = ShippingEventType.objects.get_or_create( name=SHIPPING_EVENT_NAME) EventHandler().handle_shipping_event(order, shipping_event, order_lines, line_quantities) if order.is_fulfillable: logger.error('Unable to fulfill order for basket %d', basket.id) return False return True
def fulfill_basket(self, basket_id, site): logger.info('Trying to create order for frozen basket %d', basket_id) try: basket = Basket.objects.get(id=basket_id) except Basket.DoesNotExist: logger.info('Basket %d does not exist', basket_id) return False basket.strategy = strategy.Default() Applicator().apply(basket, user=basket.owner) transaction_responses = basket.paymentprocessorresponse_set.filter( transaction_id__isnull=False) unique_transaction_ids = set( [response.transaction_id for response in transaction_responses]) if len(unique_transaction_ids) > 1: logger.info( 'Basket %d has more than one transaction id, not Fulfilling', basket_id) return False response = transaction_responses[0] if response.transaction_id.startswith('PAY'): card_number = 'PayPal Account' card_type = None self.payment_processor = _get_payment_processor(site, 'paypal') else: card_number = response.response['req_card_number'] card_type = CYBERSOURCE_CARD_TYPE_MAP.get( response.response['req_card_type']) self.payment_processor = _get_payment_processor( site, 'cybersource') handled_response = HandledProcessorResponse( transaction_id=response.transaction_id, total=basket.total_excl_tax, currency=basket.currency, card_number=card_number, card_type=card_type) try: self.record_payment(basket=basket, handled_processor_response=handled_response) shipping_method = NoShippingRequired() shipping_charge = shipping_method.calculate(basket) order_total = OrderTotalCalculator().calculate( basket, shipping_charge) user = basket.owner # Given a basket, order number generation is idempotent. Although we've already # generated this order number once before, it's faster to generate it again # than to retrieve an invoice number from PayPal. order_number = basket.order_number self.handle_order_placement( order_number=order_number, user=user, basket=basket, shipping_address=None, shipping_method=shipping_method, shipping_charge=shipping_charge, billing_address=None, order_total=order_total, ) return True except: # pylint: disable=bare-except logger.exception('Unable to fulfill basket %d', basket.id) return False
def fulfill_basket(self, basket_id, site): logger.info('Trying to complete order for frozen basket %d', basket_id) # Validate the basket. try: basket = Basket.objects.get(id=basket_id) except Basket.DoesNotExist: logger.info('Basket %d does not exist', basket_id) return False # Make sure basket is Frozen which means payment was done. if basket.status != basket.FROZEN: return False # We need to check if an order for basket exists. try: order = Order.objects.get(basket=basket) logger.info('Basket %d does have a existing order %s', basket.id, basket.order_number) except Order.DoesNotExist: order = None # if no order exists we need to create a new order. if not order: basket.strategy = strategy.Default() # Need to handle the case that applied voucher has been expired. # This will create the order with out discount but subsequently # run the fulfillment to update course mode. try: Applicator().apply(basket, user=basket.owner) except ValueError: basket.clear_vouchers() # Filter the successful payment processor response which in case # of Cybersource includes "u'decision': u'ACCEPT'" and in case of # Paypal includes "u'state': u'approved'". successful_transaction = basket.paymentprocessorresponse_set.filter( Q(response__contains='ACCEPT') | Q(response__contains='approved')) # In case of no successful transactions log and stop the process. if not successful_transaction: logger.info( 'Basket %d does not have any successful payment response', basket_id) return False unique_transaction_ids = set([ response.transaction_id for response in successful_transaction ]) if len(unique_transaction_ids) > 1: logger.warning( 'Basket %d has more than one successful transaction id, using the first one', basket_id) successful_payment_notification = successful_transaction[0] self.payment_processor = _get_payment_processor( site, successful_payment_notification.processor_name) # Create handled response handled_response = HandledProcessorResponse( transaction_id=successful_payment_notification.transaction_id, total=basket.total_excl_tax, currency=basket.currency, card_number=json.loads( successful_payment_notification.response).get( 'req_card_number', 'Paypal Account'), card_type=CYBERSOURCE_CARD_TYPE_MAP.get( json.loads(successful_payment_notification.response).get( 'req_card_type'), None)) # Record Payment and try to place order try: self.record_payment( basket=basket, handled_processor_response=handled_response) shipping_method = NoShippingRequired() shipping_charge = shipping_method.calculate(basket) order_total = OrderTotalCalculator().calculate( basket, shipping_charge) user = basket.owner # Given a basket, order number generation is idempotent. Although we've already # generated this order number once before, it's faster to generate it again # than to retrieve an invoice number from PayPal. order_number = basket.order_number self.handle_order_placement( order_number=order_number, user=user, basket=basket, shipping_address=None, shipping_method=shipping_method, shipping_charge=shipping_charge, billing_address=None, order_total=order_total, ) logger.info('Successfully created order for basket %d', basket.id) return True except: # pylint: disable=bare-except logger.exception('Unable to create order for basket %d', basket.id) return False # Start order fulfillment if order exists but is not fulfilled. elif order.is_fulfillable: order_lines = order.lines.all() line_quantities = [line.quantity for line in order_lines] shipping_event, __ = ShippingEventType.objects.get_or_create( name=SHIPPING_EVENT_NAME) EventHandler().handle_shipping_event(order, shipping_event, order_lines, line_quantities) if order.is_fulfillable: logger.error('Unable to fulfill order for basket %d', basket.id) return False return True
def handle_processor_response(self, response, basket=None, forMobile=False): token = response order_number = basket.order_number currency = basket.currency basket_id = json.dumps(basket.id) # NOTE: In the future we may want to get/create a Customer. See https://stripe.com/docs/api#customers. tracking_context = basket.owner.tracking_context or {} if tracking_context.get('customer_id') and not forMobile: token_data = stripe.Token.retrieve(token, ) selected_card = tracking_context.get('selected_payment_card_id', None) if selected_card: card_retrieve = stripe.Customer.retrieve_source( tracking_context.get('customer_id'), selected_card, ) customer = stripe.Customer.modify( tracking_context.get('customer_id'), default_source=selected_card) else: src = stripe.Customer.create_source( tracking_context.get('customer_id'), source=token) customer = stripe.Customer.modify( tracking_context.get('customer_id'), default_source=src["id"]) customer_id = customer['id'] basket.owner.tracking_context.update({'token': token}) basket.owner.save() if token is None: customer_id = tracking_context.get('customer_id') elif not tracking_context.get('customer_id', None): billing_address = self.get_address_from_token(token) address = { 'city': billing_address.line4, 'country': billing_address.country, 'line1': billing_address.line1, 'line2': billing_address.line2, 'postal_code': billing_address.postcode, 'state': billing_address.state } customer = stripe.Customer.create(source=token, email=basket.owner.email, address=address, name=basket.owner.full_name) customer_id = customer['id'] basket.owner.tracking_context = basket.owner.tracking_context or {} basket.owner.tracking_context.update({ 'customer_id': customer_id, 'token': token }) basket.owner.save() else: customer_id = tracking_context.get('customer_id') if not forMobile: try: charge = stripe.Charge.create( amount=self._get_basket_amount(basket), currency=currency, customer=customer_id, description=order_number, metadata={ 'order_number': order_number, 'basket_id': basket_id }) transaction_id = charge.id # NOTE: Charge objects subclass the dict class so there is no need to do any data transformation # before storing the response in the database. self.record_processor_response(charge, transaction_id=transaction_id, basket=basket) logger.info( 'Successfully created Stripe charge [%s] for basket [%d].', transaction_id, basket.id) except stripe.error.CardError as ex: base_message = "Stripe payment for basket [%d] declined with HTTP status [%d]" exception_format_string = "{}: %s".format(base_message) body = ex.json_body logger.exception(exception_format_string, basket.id, ex.http_status, body) self.record_processor_response(body, basket=basket) raise TransactionDeclined(base_message, basket.id, ex.http_status) total = basket.total_incl_tax card_number = charge.source.last4 card_type = STRIPE_CARD_TYPE_MAP.get(charge.source.brand) return HandledProcessorResponse(transaction_id=transaction_id, total=total, currency=currency, card_number=card_number, card_type=card_type) else: payment_intent = stripe.PaymentIntent.create( amount=self._get_basket_amount(basket), currency=currency, customer=customer_id, description=order_number, metadata={ 'order_number': order_number, 'basket_id': basket_id }) client_secret = payment_intent.client_secret total = basket.total_incl_tax card_number = "" card_type = "" return HandledMobileProcessorResponse( transaction_id=payment_intent.id, total=total, currency=currency, client_secret=client_secret, card_number=card_number, card_type=card_type, )
def request_apple_pay_authorization(self, basket, billing_address, payment_token): """ Authorizes an Apple Pay payment. For details on the process, see the CyberSource Simple Order API documentation at https://www.cybersource.com/developers/integration_methods/apple_pay/. Args: basket (Basket) billing_address (BillingAddress) payment_token (dict) Returns: HandledProcessorResponse Raises: GatewayError """ try: client = Client(self.soap_api_url, wsse=UsernameToken(self.merchant_id, self.transaction_key)) card_type = APPLE_PAY_CYBERSOURCE_CARD_TYPE_MAP[ payment_token['paymentMethod']['network'].lower()] bill_to = { 'firstName': billing_address.first_name, 'lastName': billing_address.last_name, 'street1': billing_address.line1, 'street2': billing_address.line2, 'city': billing_address.line4, 'state': billing_address.state, 'postalCode': billing_address.postcode, 'country': billing_address.country.iso_3166_1_a2, 'email': basket.owner.email, } purchase_totals = { 'currency': basket.currency, 'grandTotalAmount': str(basket.total_incl_tax), } encrypted_payment = { 'descriptor': 'RklEPUNPTU1PTi5BUFBMRS5JTkFQUC5QQVlNRU5U', 'data': base64.b64encode(json.dumps(payment_token['paymentData'])), 'encoding': 'Base64', } card = { 'cardType': card_type, } auth_service = { 'run': 'true', } capture_service = { 'run': 'true', } # Enable Export Compliance for SDN validation, amongst other checks. # See https://www.cybersource.com/products/fraud_management/export_compliance/ export_service = { 'run': 'true', } item = [{ 'id': index, 'productCode': line.product.get_product_class().slug, 'productName': clean_field_value(line.product.title), 'quantity': line.quantity, 'productSKU': line.stockrecord.partner_sku, 'taxAmount': str(line.line_tax), 'unitPrice': str(line.unit_price_incl_tax), } for index, line in enumerate(basket.all_lines())] response = client.service.runTransaction( merchantID=self.merchant_id, merchantReferenceCode=basket.order_number, billTo=bill_to, purchaseTotals=purchase_totals, encryptedPayment=encrypted_payment, card=card, ccAuthService=auth_service, ccCaptureService=capture_service, exportService=export_service, paymentSolution='001', item=item, ) except: msg = 'An error occurred while authorizing an Apple Pay (via CyberSource) for basket [{}]'.format( basket.id) logger.exception(msg) raise GatewayError(msg) request_id = response.requestID ppr = self.record_processor_response(serialize_object(response), transaction_id=request_id, basket=basket) if response.decision == 'ACCEPT': currency = basket.currency total = basket.total_incl_tax transaction_id = request_id return HandledProcessorResponse( transaction_id=transaction_id, total=total, currency=currency, card_number='Apple Pay', card_type=CYBERSOURCE_CARD_TYPE_MAP.get(card_type)) else: msg = ( 'CyberSource rejected an Apple Pay authorization request for basket [{basket_id}]. ' 'Complete response has been recorded in entry [{response_id}]') msg = msg.format(basket_id=basket.id, response_id=ppr.id) logger.warning(msg) raise GatewayError(msg)
def handle_processor_response(self, response, basket): """ Handle a response (i.e., "merchant notification") from redsys. """ payment = RedsysResponseForm(response) self.order_number = basket.order_number #alphanumeric_characters = re.compile('[^a-zA-Z0-9]') #self.order_number = '%s%s' % (self.order_number_prefix,re.sub(alphanumeric_characters, '', basket.order_number)) if payment.is_valid(): logger.debug('processing payment gateway response for payment %s' % self.order_number) # REAL # signature = compute_signature(self.order_number, # payment.cleaned_data['Ds_MerchantParameters'].encode(), # self.shared_secret) # TEST signature = compute_signature( self.order_number, payment.cleaned_data['Ds_MerchantParameters'].encode(), Redsys.shared_secret_test) logger.debug('received signature: %s' % payment.cleaned_data['Ds_Signature']) logger.debug('calculated signature: %s' % signature.decode()) if not compare_signatures(signature.decode(), payment.cleaned_data['Ds_Signature']): logger.debug('signature mismatch - possible attack') return HttpResponse() binary_merchant_parameters = base64.b64decode( payment.cleaned_data['Ds_MerchantParameters']) merchant_parameters = json.loads( binary_merchant_parameters.decode()) transaction_type = merchant_parameters['Ds_TransactionType'] response_code = int(merchant_parameters['Ds_Response']) if response_code < 100: # Authorised transaction for payments if transaction_type == '0': #captured_amount = int(merchant_parameters['Ds_Amount']) / 100 transaction_id = merchant_parameters['Ds_Order'] extra_data = merchant_parameters self.record_processor_response( response, transaction_id=self.order_number, basket=basket) logger.info('payment %s confirmed' % self.order_number) logger.info('basket %s' % basket) logger.info('mparams %s' % merchant_parameters) return HandledProcessorResponse( transaction_id=transaction_id, total=basket.total_incl_tax, currency=basket.currency, # card_number=basket.label, card_number=merchant_parameters[ 'Ds_AuthorisationCode'], card_type=None) else: logger.debug( 'authorised payment response but unrecognised transaction type %s' % transaction_type) if response_code == 900: # Authorised transaction for refunds and confirmations if transaction_type == '3': extra_data = merchant_parameters logger.debug('payment %s automatic refund' % self.order_number) else: logger.debug( 'authorised refund response but unrecognised transaction type %s' % transaction_type) if response_code > 100 and response_code != 900: # any of a long list of errors/rejections extra_data = merchant_parameters # perhaps import and raise PaymentError from django-payments logger.debug('rejected: %s' % binary_merchant_parameters.decode())
def handle_processor_response(self, response, basket=None): """ Handle a response (i.e., "merchant notification") from CyberSource. Arguments: response (dict): Dictionary of parameters received from the payment processor. Keyword Arguments: basket (Basket): Basket being purchased via the payment processor. Raises: AuthorizationError: Authorization was declined. UserCancelled: Indicates the user cancelled payment. TransactionDeclined: Indicates the payment was declined by the processor. GatewayError: Indicates a general error on the part of the processor. InvalidCyberSourceDecision: Indicates an unknown decision value. Known values are ACCEPT, CANCEL, DECLINE, ERROR, REVIEW. PartialAuthorizationError: Indicates only a portion of the requested amount was authorized. Returns: HandledProcessorResponse """ # Validate the signature if not self.is_signature_valid(response.raw_json): raise InvalidSignatureError if response.decision != Decision.accept: if response.duplicate_payment: # This means user submitted payment request twice within 15 min. # We need to check if user first payment notification was handled successfuly and user has an order # if user has an order we can raise DuplicateReferenceNumber exception else we need to continue # the order creation process. to upgrade user in correct course mode. if Order.objects.filter(number=response.order_id).exists(): raise DuplicateReferenceNumber # If we failed to capture a successful payment, and then the user submits payment again within a 15 # minute window, then we get a duplicate payment message with no amount attached. We can't create an # order in that case. if response.total is None: raise DuplicateReferenceNumber logger.info( 'Received duplicate CyberSource payment notification for basket [%d] which is not associated ' 'with any existing order. Continuing to validation and order creation processes.', basket.id, ) else: raise { Decision.cancel: UserCancelled, Decision.decline: TransactionDeclined, Decision.error: GatewayError, Decision.review: AuthorizationError, }.get(response.decision, InvalidCybersourceDecision(response.decision)) transaction_id = response.transaction_id if transaction_id and response.decision == Decision.accept: if Order.objects.filter(number=response.order_id).exists(): if PaymentProcessorResponse.objects.filter( transaction_id=transaction_id).exists(): raise RedundantPaymentNotificationError raise ExcessivePaymentForOrderError if response.partial_authorization: # Raise an exception if the authorized amount differs from the requested amount. # Note (CCB): We should never reach this point in production since partial authorization is disabled # for our account, and should remain that way until we have a proper solution to allowing users to # complete authorization for the entire order raise PartialAuthorizationError return HandledProcessorResponse(transaction_id=response.transaction_id, total=response.total, currency=response.currency, card_number=response.card_number, card_type=response.card_type)