def issue_credit(self, order_number, basket, reference_number, amount, currency): try: payment = paypalrestsdk.Payment.find(reference_number, api=self.paypal_api) sale = self._get_payment_sale(payment) if not sale: logger.error('Unable to find a Sale associated with PayPal Payment [%s].', payment.id) refund = sale.refund({ 'amount': { 'total': six.text_type(amount), 'currency': currency, } }) except: msg = 'An error occurred while attempting to issue a credit (via PayPal) for order [{}].'.format( order_number) logger.exception(msg) raise GatewayError(msg) if refund.success(): transaction_id = refund.id self.record_processor_response(refund.to_dict(), transaction_id=transaction_id, basket=basket) return transaction_id error = refund.error entry = self.record_processor_response(error, transaction_id=error['debug_id'], basket=basket) msg = "Failed to refund PayPal payment [{sale_id}]. " \ "PayPal's response was recorded in entry [{response_id}].".format(sale_id=sale.id, response_id=entry.id) raise GatewayError(msg)
def issue_credit(self, source, amount, currency): order = source.order try: order_request_token = source.reference security = Security() token = UsernameToken(self.merchant_id, self.transaction_key) security.tokens.append(token) client = Client(self.soap_api_url, transport=RequestsTransport()) client.set_options(wsse=security) credit_service = client.factory.create('ns0:CCCreditService') credit_service._run = 'true' # pylint: disable=protected-access credit_service.captureRequestID = source.reference purchase_totals = client.factory.create('ns0:PurchaseTotals') purchase_totals.currency = currency purchase_totals.grandTotalAmount = unicode(amount) response = client.service.runTransaction( merchantID=self.merchant_id, merchantReferenceCode=order.number, orderRequestToken=order_request_token, ccCreditService=credit_service, purchaseTotals=purchase_totals) request_id = response.requestID ppr = self.record_processor_response( suds_response_to_dict(response), transaction_id=request_id, basket=order.basket) except: msg = 'An error occurred while attempting to issue a credit (via CyberSource) for order [{}].'.format( order.number) logger.exception(msg) raise GatewayError(msg) if response.decision == 'ACCEPT': source.refund(amount, reference=request_id) event_type, __ = PaymentEventType.objects.get_or_create( name=PaymentEventTypeName.REFUNDED) PaymentEvent.objects.create(event_type=event_type, order=order, amount=amount, reference=request_id, processor_name=self.NAME) else: raise GatewayError( 'Failed to issue CyberSource credit for order [{order_number}]. ' 'Complete response has been recorded in entry [{response_id}]'. format(order_number=order.number, response_id=ppr.id))
def issue_credit(self, source, amount, currency): order = source.order try: payment = paypalrestsdk.Payment.find(source.reference, api=self.paypal_api) sale = self._get_payment_sale(payment) if not sale: logger.error( 'Unable to find a Sale associated with PayPal Payment [%s].', payment.id) refund = sale.refund( {'amount': { 'total': unicode(amount), 'currency': currency, }}) except: msg = 'An error occurred while attempting to issue a credit (via PayPal) for order [{}].'.format( order.number) logger.exception(msg) raise GatewayError(msg) basket = order.basket if refund.success(): transaction_id = refund.id self.record_processor_response(refund.to_dict(), transaction_id=transaction_id, basket=basket) source.refund(amount, reference=transaction_id) event_type, __ = PaymentEventType.objects.get_or_create( name=PaymentEventTypeName.REFUNDED) PaymentEvent.objects.create(event_type=event_type, order=order, amount=amount, reference=transaction_id, processor_name=self.NAME) else: error = refund.error entry = self.record_processor_response( error, transaction_id=error['debug_id'], basket=basket) msg = "Failed to refund PayPal payment [{sale_id}]. " \ "PayPal's response was recorded in entry [{response_id}].".format(sale_id=sale.id, response_id=entry.id) raise GatewayError(msg)
def initiate_payment(self, basket, request, form_data): """ Initiate payment using the Cybersource REST payment api. Returns: (payment_processor_response, transaction_id) Raises: GatewayError: when the REST api call fails """ transient_token_jwt = request.POST['payment_token'] try: payment_processor_response = self.authorize_payment_api( transient_token_jwt, basket, request, form_data, ) transaction_id = payment_processor_response.processor_information.transaction_id return payment_processor_response, transaction_id except ApiException as e: if e.body is None: self.record_processor_response( { 'status': e.status, 'reason': e.reason, }, transaction_id=None, basket=basket) logger.exception('Payment failed') # This will display the generic error on the frontend raise GatewayError() return e, e.headers['v-c-correlation-id']
def issue_credit(self, order_number, basket, reference_number, amount, currency): """ Verify the payment processor used for the original order responds as expected to the refund request, and the response is saved in the database, with error handling. """ try: client = Client(self.soap_api_url, wsse=UsernameToken(self.merchant_id, self.transaction_key)) credit_service = { 'captureRequestID': reference_number, 'run': 'true', } purchase_totals = { 'currency': currency, 'grandTotalAmount': str(amount), } response = client.service.runTransaction( merchantID=self.merchant_id, merchantReferenceCode=order_number, orderRequestToken=reference_number, ccCreditService=credit_service, purchaseTotals=purchase_totals) request_id = response.requestID ppr = self.record_processor_response(serialize_object(response), transaction_id=request_id, basket=basket) except: msg = 'An error occurred while attempting to issue a credit (via CyberSource) for order [{}].'.format( order_number) logger.exception(msg) raise GatewayError(msg) if response.decision == 'ACCEPT': return request_id raise GatewayError( 'Failed to issue CyberSource credit for order [{order_number}]. ' 'Complete response has been recorded in entry [{response_id}]'. format(order_number=order_number, response_id=ppr.id))
def _fetch_response_xml(self, request_xml): # Need to fill in HTTP request here conn = http_client.HTTPSConnection(self._host, 443, timeout=30) headers = {"Content-type": "application/xml", "Accept": ""} conn.request("POST", self._path, request_xml.encode('utf8'), headers) response = conn.getresponse() response_xml = response.read() if response.status != http_client.OK: raise GatewayError("Unable to communicate with payment gateway (code: %s, response: %s)" % (response.status, response_xml)) conn.close() return response_xml
def get_transaction_parameters(self, basket, request=None, use_client_side_checkout=True, **kwargs): """ Creates Payment form URL from paystack. Arguments: basket (Basket): The basket of products being purchased. request (Request, optional): A Request object which is used to construct Paystack's `return_url`. use_client_side_checkout (bool, optional): This value is not used. **kwargs: Additional parameters; not used by this method. Returns: dict: paystack-specific parameters required to complete a transaction. Must contain a URL of payment form to which users can be directed in order to approve a newly created payment. Raises: GatewayError: Indicates a general error or unexpected behavior on the part of PayPal which prevented a payment from being created. """ data = { 'amount': self.get_basket_amount(basket.total_incl_tax), 'email': basket.owner.email, 'callback_url': self.return_url, 'metadata': { 'cancel_action:': self.cancel_url, 'order_number': basket.order_number, 'basket_id': basket.id, 'custom_fields': self.get_paystack_custom_fields_data(basket) }, } success, response = self.paystack_client.handler( INITIALIZE_TRANSACTION_CODE, data) if success: logger.info( "Successfully got hosted Paystack payment page for basket: %d.", basket.id) data = response.get('data') if data: return {'payment_page_url': data.get('authorization_url')} logger.error("Failed to get Paystack payment form for basket: %d.", basket.id) raise GatewayError( "Paystack payment creation failure: unable to get Paystack form token." )
def issue_credit(self, order_number, basket, reference_number, amount, currency): try: refund = stripe.Refund.create(charge=reference_number) except: msg = 'An error occurred while attempting to issue a credit (via Stripe) for order [{}].'.format( order_number) logger.exception(msg) raise GatewayError(msg) transaction_id = refund.id # NOTE: Refund objects subclass dict so there is no need to do any data transformation # before storing the response in the database. self.record_processor_response(refund, transaction_id=transaction_id, basket=basket) return transaction_id
def raise_api_error(self, message, response=None, response_data=None, basket=None): error_response = None if response is not None: error_response = { "status_code": response.status_code, "content": response.content.decode(), "data": response_data, } error = {"message": message, "response": error_response} entry = self.record_processor_response( error, transaction_id=basket.order_number if basket else None, basket=basket) logger.error( u"Failed request to Saferpay API for basket [%d], response stored in entry [%d].", basket.id if basket else None, entry.id, exc_info=True, ) raise GatewayError(error)
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 get_transaction_parameters(self, basket, request=None, use_client_side_checkout=False, **kwargs): """ Create a new PayPal payment. Arguments: basket (Basket): The basket of products being purchased. request (Request, optional): A Request object which is used to construct PayPal's `return_url`. use_client_side_checkout (bool, optional): This value is not used. **kwargs: Additional parameters; not used by this method. Returns: dict: PayPal-specific parameters required to complete a transaction. Must contain a URL to which users can be directed in order to approve a newly created payment. Raises: GatewayError: Indicates a general error or unexpected behavior on the part of PayPal which prevented a payment from being created. """ # PayPal requires that item names be at most 127 characters long. PAYPAL_FREE_FORM_FIELD_MAX_SIZE = 127 return_url = urljoin(get_ecommerce_url(), reverse('paypal:execute')) data = { 'intent': 'sale', 'redirect_urls': { 'return_url': return_url, 'cancel_url': self.cancel_url, }, 'payer': { 'payment_method': 'paypal', }, 'transactions': [{ 'amount': { 'total': str(basket.total_incl_tax), 'currency': basket.currency, }, # Paypal allows us to send additional transaction related data in 'description' & 'custom' field # Free form field, max length 127 characters # description : program_id:<program_id> 'description': "program_id:{}".format(get_basket_program_uuid(basket)), 'item_list': { 'items': [ { 'quantity': line.quantity, # PayPal requires that item names be at most 127 characters long. # for courseid we're using 'name' field along with title, # concatenated field will be 'courseid|title' 'name': middle_truncate(self.get_courseid_title(line), PAYPAL_FREE_FORM_FIELD_MAX_SIZE), # PayPal requires that the sum of all the item prices (where price = price * quantity) # equals to the total amount set in amount['total']. 'price': str(line.line_price_incl_tax_incl_discounts / line.quantity), 'currency': line.stockrecord.price_currency, } for line in basket.all_lines() ], }, 'invoice_number': basket.order_number, }], } if waffle.switch_is_active('create_and_set_webprofile'): locale_code = self.resolve_paypal_locale( request.COOKIES.get(settings.LANGUAGE_COOKIE_NAME)) web_profile_id = self.create_temporary_web_profile(locale_code) if web_profile_id is not None: data['experience_profile_id'] = web_profile_id else: try: web_profile = PaypalWebProfile.objects.get( name=self.DEFAULT_PROFILE_NAME) data['experience_profile_id'] = web_profile.id except PaypalWebProfile.DoesNotExist: pass available_attempts = 1 if waffle.switch_is_active('PAYPAL_RETRY_ATTEMPTS'): available_attempts = self.retry_attempts for i in range(1, available_attempts + 1): try: payment = paypalrestsdk.Payment(data, api=self.paypal_api) payment.create() if payment.success(): break if i < available_attempts: logger.warning( u"Creating PayPal payment for basket [%d] was unsuccessful. Will retry.", basket.id, exc_info=True) else: error = self._get_error(payment) # pylint: disable=unsubscriptable-object entry = self.record_processor_response( error, transaction_id=error['debug_id'], basket=basket) logger.error(u"%s [%d], %s [%d].", "Failed to create PayPal payment for basket", basket.id, "PayPal's response recorded in entry", entry.id, exc_info=True) raise GatewayError(error) except: # pylint: disable=bare-except if i < available_attempts: logger.warning( u"Creating PayPal payment for basket [%d] resulted in an exception. Will retry.", basket.id, exc_info=True) else: logger.exception( u"After %d retries, creating PayPal payment for basket [%d] still experienced exception.", i, basket.id) raise entry = self.record_processor_response(payment.to_dict(), transaction_id=payment.id, basket=basket) logger.info( "Successfully created PayPal payment [%s] for basket [%d].", payment.id, basket.id) for link in payment.links: if link.rel == 'approval_url': approval_url = link.href break else: logger.error( "Approval URL missing from PayPal payment [%s]. PayPal's response was recorded in entry [%d].", payment.id, entry.id) raise GatewayError( 'Approval URL missing from PayPal payment response. See entry [{}] for details.' .format(entry.id)) parameters = { 'payment_page_url': approval_url, } return parameters
def get_transaction_parameters(self, basket, request=None): """ Create a new PayPal payment. Arguments: basket (Basket): The basket of products being purchased. Keyword Arguments: request (Request): A Request object which is used to construct PayPal's `return_url`. Returns: dict: PayPal-specific parameters required to complete a transaction. Must contain a URL to which users can be directed in order to approve a newly created payment. Raises: GatewayError: Indicates a general error or unexpected behavior on the part of PayPal which prevented a payment from being created. """ return_url = urljoin(get_ecommerce_url(), reverse('paypal_execute')) data = { 'intent': 'sale', 'redirect_urls': { 'return_url': return_url, 'cancel_url': self.cancel_url, }, 'payer': { 'payment_method': 'paypal', }, 'transactions': [{ 'amount': { 'total': unicode(basket.total_incl_tax), 'currency': basket.currency, }, 'item_list': { 'items': [ { 'quantity': line.quantity, # PayPal requires that item names be at most 127 characters long. 'name': middle_truncate(line.product.title, 127), # PayPal requires that the sum of all the item prices (where price = price * quantity) # equals to the total amount set in amount['total']. 'price': unicode(line.line_price_incl_tax_incl_discounts / line.quantity), 'currency': line.stockrecord.price_currency, } for line in basket.all_lines() ], }, 'invoice_number': basket.order_number, }], } try: web_profile = PaypalWebProfile.objects.get(name=self.DEFAULT_PROFILE_NAME) data['experience_profile_id'] = web_profile.id except PaypalWebProfile.DoesNotExist: pass payment = paypalrestsdk.Payment(data, api=self.paypal_api) payment.create() # Raise an exception for payments that were not successfully created. Consuming code is # responsible for handling the exception. if not payment.success(): error = self._get_error(payment) entry = self.record_processor_response(error, transaction_id=error['debug_id'], basket=basket) # pylint: disable=unsubscriptable-object logger.error( u"Failed to create PayPal payment for basket [%d]. PayPal's response was recorded in entry [%d].", basket.id, entry.id ) raise GatewayError(error) entry = self.record_processor_response(payment.to_dict(), transaction_id=payment.id, basket=basket) logger.info(u"Successfully created PayPal payment [%s] for basket [%d].", payment.id, basket.id) for link in payment.links: if link.rel == 'approval_url': approval_url = link.href break else: logger.error( u"Approval URL missing from PayPal payment [%s]. PayPal's response was recorded in entry [%d].", payment.id, entry.id ) raise GatewayError( 'Approval URL missing from PayPal payment response. See entry [{}] for details.'.format(entry.id)) parameters = { 'payment_page_url': approval_url, } return parameters
def get_transaction_parameters(self, basket, request=None, use_client_side_checkout=False, **kwargs): """ Create a new Alipay payment. Arguments: basket (Basket): The basket of products being purchased. request (Request, optional): A Request object which is used to construct PayPal's `return_url`. use_client_side_checkout (bool, optional): This value is not used. **kwargs: Additional parameters; not used by this method. Returns: dict: PayPal-specific parameters required to complete a transaction. Must contain a URL to which users can be directed in order to approve a newly created payment. Raises: GatewayError: Indicates a general error or unexpected behavior on the part of PayPal which prevented a payment from being created. """ return_url = urljoin(get_ecommerce_url(), reverse('alipay:execute')) data = { 'intent': 'sale', 'redirect_urls': { 'return_url': return_url, 'cancel_url': self.cancel_url, }, 'payer': { 'payment_method': 'alipay', }, 'transactions': [{ 'amount': { 'total': unicode(basket.total_incl_tax), 'currency': basket.currency, }, 'item_list': { 'items': [ { 'quantity': line.quantity, # PayPal requires that item names be at most 127 characters long. 'name': middle_truncate(line.product.title, 127), # PayPal requires that the sum of all the item prices (where price = price * quantity) # equals to the total amount set in amount['total']. 'price': unicode(line.line_price_incl_tax_incl_discounts / line.quantity), 'currency': line.stockrecord.price_currency, } for line in basket.all_lines() ], }, 'invoice_number': basket.order_number, }], } if waffle.switch_is_active('create_and_set_webprofile'): locale_code = self.resolve_alipay_locale( request.COOKIES.get(settings.LANGUAGE_COOKIE_NAME)) web_profile_id = self.create_temporary_web_profile(locale_code) if web_profile_id is not None: data['experience_profile_id'] = web_profile_id else: try: web_profile = AlipayWebProfile.objects.get( name=self.DEFAULT_PROFILE_NAME) data['experience_profile_id'] = web_profile.id except AlipayWebProfile.DoesNotExist: pass available_attempts = 1 if waffle.switch_is_active('PAYPAL_RETRY_ATTEMPTS'): available_attempts = self.retry_attempts for i in range(1, available_attempts + 1): try: payment = alipay_sdk.Payment(data, api=self.alipay_api) payment.create() if payment.success(): break else: if i < available_attempts: logger.warning( u"Creating AliPay payment for basket [%d] was unsuccessful. Will retry.", basket.id, exc_info=True) else: error = self._get_error(payment) # pylint: disable=unsubscriptable-object entry = self.record_processor_response( error, transaction_id=error['debug_id'], basket=basket) logger.error( u"%s [%d], %s [%d].", "Failed to create AliPay payment for basket", basket.id, "AliPay's response recorded in entry", entry.id, exc_info=True) raise GatewayError(error) except: # pylint: disable=bare-except if i < available_attempts: logger.warning( u"Creating AliPay payment for basket [%d] resulted in an exception. Will retry.", basket.id, exc_info=True) else: logger.exception( u"After %d retries, creating AliPay payment for basket [%d] still experienced exception.", i, basket.id) raise entry = self.record_processor_response(payment.to_dict(), transaction_id=payment.id, basket=basket) logger.info( "Successfully created AliPay payment [%s] for basket [%d].", payment.id, basket.id) id = payment.id order_string = self.alipay_api.api_alipay_trade_page_pay( out_trade_no=payment.id, total_amount=unicode(basket.total_incl_tax), #0.01, subject=middle_truncate(line.product.title, 127), return_url=return_url, ) parameters = { 'payment_page_url': self.alipay_api.default_endpoint() + '?' + order_string, #'payment_page_url': 'error_test', } return parameters
def get_transaction_parameters(self, basket, request=None, use_client_side_checkout=True, **kwargs): """ Create a new AuthorizeNet payment form token. Visit following links for more information and detail https://developer.authorize.net/api/reference/#accept-suite-get-an-accept-payment-page https://developer.authorize.net/api/reference/features/accept_hosted.html (redirection method) Arguments: basket (Basket): The basket of products being purchased. request (Request, optional): A Request object which is used to construct AuthorizeNet's `return_url`. use_client_side_checkout (bool, optional): This value is not used. **kwargs: Additional parameters; not used by this method. Returns: dict: AuthorizeNet-specific parameters required to complete a transaction. Must contain a URL to which users can be directed in order to approve a newly created payment. Raises: GatewayError: Indicates a general error or unexpected behavior on the part of AuthorizeNet which prevented a payment from being created. """ merchant_auth = apicontractsv1.merchantAuthenticationType() merchant_auth.name = self.merchant_auth_name merchant_auth.transactionKey = self.transaction_key settings = self.get_authorizenet_payment_settings(basket) order = apicontractsv1.orderType() order.invoiceNumber = basket.order_number transaction_request = apicontractsv1.transactionRequestType() transaction_request.transactionType = AUTH_CAPTURE_TRANSACTION_TYPE transaction_request.amount = unicode(basket.total_incl_tax) transaction_request.order = order line_items_list = self.get_authorizenet_lineitems(basket) payment_page_request = apicontractsv1.getHostedPaymentPageRequest() payment_page_request.merchantAuthentication = merchant_auth payment_page_request.transactionRequest = transaction_request payment_page_request.hostedPaymentSettings = settings transaction_request.lineItems = line_items_list payment_page_controller = getHostedPaymentPageController(payment_page_request) payment_page_controller.execute() payment_page_response = payment_page_controller.getresponse() authorize_form_token = None if payment_page_response is not None: if payment_page_response.messages.resultCode == apicontractsv1.messageTypeEnum.Ok: logger.info( "%s [%d].", "Successfully got hosted payment page for basket", basket.id, exc_info=True ) if payment_page_response.messages is not None: logger.info('Message Code : %s', payment_page_response.messages.message[0]['code'].text) logger.info('Message Text : %s', payment_page_response.messages.message[0]['text'].text) authorize_form_token = str(payment_page_response.token) else: logger.error('Failed to get AuthorizeNet payment token.') if payment_page_response.messages is not None: logger.error( '\nCode:%s \nText:%s', payment_page_response.messages.message[0]['code'].text, payment_page_response.messages.message[0]['text'].text ) raise GatewayError(payment_page_response.messages.message[0]['text'].text) else: logger.error( "%s [%d].", "Failed to create AuthorizeNet payment for basket", basket.id, exc_info=True ) raise GatewayError('AuthorizeNet payment creation failure: unable to get AuthorizeNet form token') parameters = { 'payment_page_url': self.autorizenet_redirect_url, 'token': authorize_form_token } return parameters