Example #1
0
    def form_valid(self, form):
        email = form.cleaned_data.get('email')
        amount = form.cleaned_data.get('amount')
        payment_reason = form.cleaned_data.get('reason')

        account = Account.objects.filter(email=email).first()
        if not Account.objects.filter(email=email).first():
            messages.warning(self.request, 'This user does not exist.')
            return super().form_valid(form)

        payment = payments.start_payment(
            owner=account,
            amount=amount,
            payment_method='pay_by_admin',
        )

        payments.finish_payment(
            transaction_id=payment.transaction_id,
            status='processed',
            notes=f'{payment_reason} (by {self.request.user.email})',
        )

        messages.success(
            self.request,
            f'You successfully added {amount} USD to the balance of {email}')

        return super().form_valid(form)
Example #2
0
 def test_domain_register_order_successful(self):
     with mock.patch('billing.payments.by_transaction_id'
                     ) as mock_payment_by_transaction_id:
         # Add 100.0 to the balance of the user to register a domain
         mock_payment_by_transaction_id.return_value = mock.MagicMock(
             status='started', amount=100.0, owner=self.account)
         finish_payment('12345', status='processed')
     response = self.client.get('/billing/order/create/register/test.ai/')
     assert response.status_code == 200
Example #3
0
 def test_domain_renew_order_successful(self, mock_domain_search):
     mock_domain_search.return_value = mock.MagicMock(
         expiry_date=datetime.datetime(2099, 1, 1))
     with mock.patch('billing.payments.by_transaction_id'
                     ) as mock_payment_by_transaction_id:
         # Add 100.0 to the balance of the user to renew a domain
         mock_payment_by_transaction_id.return_value = mock.MagicMock(
             status='started',
             amount=100.0,
             owner=self.account,
         )
         finish_payment('12345', status='processed')
     response = self.client.get('/billing/order/create/renew/test.ai/')
     assert response.status_code == 200
Example #4
0
 def _check_rc_ok_is_incomplete(self, result, rc, fc, transaction_id, reference):
     if result != 'pass' or rc != 'OK' or fc != 'APPROVED':
         if fc == 'INCOMPLETE':
             self.message = 'Transaction was cancelled'
             if not payments.finish_payment(transaction_id=transaction_id, status='cancelled'):
                 logging.critical(f'payment not found, transaction_id is {transaction_id}')
                 raise exceptions.SuspiciousOperation()
         else:
             self.message = 'Transaction was declined'
             if not payments.finish_payment(transaction_id=transaction_id, status='declined',
                                            merchant_reference=reference):
                 logging.critical(f'payment not found, transaction_id is {transaction_id}')
                 raise exceptions.SuspiciousOperation()
         return True
     return False
Example #5
0
 def _check_rc_usercan_is_incomplete(self, result, rc, fc, transaction_id):
     if result != 'pass' and rc == 'USERCAN' and fc == 'INCOMPLETE':
         self.message = 'Transaction was cancelled'
         if not payments.finish_payment(transaction_id=transaction_id, status='cancelled'):
             logging.critical(f'payment not found, transaction_id is {transaction_id}')
             raise exceptions.SuspiciousOperation()
         return True
     return False
Example #6
0
    def get(self, request, *args, **kwargs):
        request_data = request.GET
        result = request_data.get('result')
        rc = request_data.get('rc')
        fc = request_data.get('fc')
        reference = request_data.get('ref')
        transaction_id = request_data.get('tid')
        amount = request_data.get('amt', '').replace(',', '')

        logging.info('verifying payment request: %r', request_data)

        if self._check_rc_usercan_is_incomplete(result, rc, fc, transaction_id):
            return shortcuts.render(request, 'billing/4csonline/failed_payment.html', {
                'message': self.message,  # TODO Use Django messages
            })

        payment_object = payments.by_transaction_id(transaction_id=transaction_id)
        self._check_payment(payment_object, transaction_id, amount)

        if not settings.ZENAIDA_BILLING_4CSONLINE_BYPASS_PAYMENT_VERIFICATION:
            if self._check_rc_ok_is_incomplete(result, rc, fc, transaction_id, reference):
                return shortcuts.render(request, 'billing/4csonline/failed_payment.html', {
                    'message': self.message,
                })

        payments.update_payment(payment_object, status='paid', merchant_reference=reference)

        if not settings.ZENAIDA_BILLING_4CSONLINE_BYPASS_PAYMENT_CONFIRMATION:
            result = self._is_payment_verified(transaction_id)
            if result == 'pending':
                return shortcuts.render(request, 'billing/4csonline/pending_payment.html', {
                    'message': self.message,
                })
            if result == 'failed':
                return shortcuts.render(request, 'billing/4csonline/failed_payment.html', {
                    'message': self.message,
                })

        if not payments.finish_payment(transaction_id=transaction_id, status='processed'):
            logging.critical(f'payment not found, transaction_id is {transaction_id}')  # TODO Use Django messages
            raise exceptions.SuspiciousOperation()

        redirect_url = '/billing/payments/'

        if not request.user.is_anonymous:
            started_orders = billing_orders.list_orders(
                owner=self.request.user,
                exclude_cancelled=True,
                include_statuses=['started']
            )
            if started_orders:
                messages.warning(self.request, 'You have an ongoing order. Please click the "Confirm" button '
                                               'to complete the order.')
                redirect_url = '/billing/order/' + str(started_orders[0].id)

        return shortcuts.render(request, 'billing/4csonline/success_payment.html', {'redirect_url': redirect_url})
Example #7
0
    def get(self, request, *args, **kwargs):
        request_data = request.GET
        result = request_data.get('result')
        rc = request_data.get('rc')
        fc = request_data.get('fc')
        reference = request_data.get('ref')
        transaction_id = request_data.get('tid')
        amount = request_data.get('amt')

        if self._check_rc_usercan_is_incomplete(result, rc, fc,
                                                transaction_id):
            return shortcuts.render(
                request,
                'billing/4csonline/failed_payment.html',
                {
                    'message': self.message,  # TODO Use Django messages
                })

        payment_object = payments.by_transaction_id(
            transaction_id=transaction_id)
        self._check_payment(payment_object, transaction_id, amount)

        if not settings.ZENAIDA_BILLING_4CSONLINE_BYPASS_PAYMENT_VERIFICATION:
            if self._check_rc_ok_is_incomplete(result, rc, fc, transaction_id,
                                               reference):
                return shortcuts.render(
                    request, 'billing/4csonline/failed_payment.html', {
                        'message': self.message,
                    })

        payments.update_payment(payment_object,
                                status='paid',
                                merchant_reference=reference)

        if not settings.ZENAIDA_BILLING_4CSONLINE_BYPASS_PAYMENT_CONFIRMATION:
            if not self._is_payment_verified(transaction_id):
                return shortcuts.render(
                    request, 'billing/4csonline/failed_payment.html', {
                        'message': self.message,
                    })

        if not payments.finish_payment(transaction_id=transaction_id,
                                       status='processed'):
            logging.critical(
                f'Payment not found, transaction_id is {transaction_id}'
            )  # TODO Use Django messages
            raise exceptions.SuspiciousOperation()

        return shortcuts.render(request,
                                'billing/4csonline/success_payment.html')
Example #8
0
 def _is_payment_verified(self, transaction_id):
     try:
         verified = requests.get(f'{settings.ZENAIDA_BILLING_4CSONLINE_MERCHANT_VERIFY_LINK}?m='
                                 f'{settings.ZENAIDA_BILLING_4CSONLINE_MERCHANT_ID}&t={transaction_id}')
     except Exception as exc:
         self.message = 'Payment verification is pending, your balance will be updated within few minutes.'
         logging.critical(f'payment confirmation failed, transaction_id is {transaction_id} : {exc}')
         return 'pending'
     if verified.text != 'YES':
         if not payments.finish_payment(transaction_id=transaction_id, status='unconfirmed'):
             logging.critical(f'payment not found, transaction_id is {transaction_id}')
             raise exceptions.SuspiciousOperation()
         self.message = 'Transaction verification failed'
         logging.critical(f'payment confirmation failed, transaction_id is {transaction_id}')
         return 'failed'
     return 'verified'
Example #9
0
    def _is_payment_verified(self, transaction_id):
        verified = requests.get(
            f'{settings.ZENAIDA_BILLING_4CSONLINE_MERCHANT_VERIFY_LINK}?m='
            f'{settings.ZENAIDA_BILLING_4CSONLINE_MERCHANT_ID}&t={transaction_id}'
        )

        if verified.text != 'YES':
            if not payments.finish_payment(transaction_id=transaction_id,
                                           status='unconfirmed'):
                logging.critical(
                    f'Payment not found, transaction_id is {transaction_id}')
                raise exceptions.SuspiciousOperation()
            self.message = 'Transaction verification failed, please contact site administrator'
            logging.critical(
                f'Payment confirmation failed, transaction_id is {transaction_id}'
            )
            return False
        return True
Example #10
0
    def handle(self, *args, **options):
        client = BTCPayClient(
            host=settings.ZENAIDA_BTCPAY_HOST,
            pem=settings.ZENAIDA_BTCPAY_CLIENT_PRIVATE_KEY,
            tokens={"merchant": settings.ZENAIDA_BTCPAY_MERCHANT})

        while True:
            # Check if BTCPay server is up and running.
            try:
                client.get_rate("USD")
            except:
                logger.exception(
                    "BTCPay server connection problem while getting rates.")
                time.sleep(60)
                continue

            logger.debug('check payments at %r',
                         timezone.now().strftime("%Y-%m-%d %H:%M:%S"))

            # Check status of all incomplete invoices.
            incomplete_invoices = BTCPayInvoice.invoices.filter(
                finished_at=None)

            for invoice in incomplete_invoices:
                try:
                    btcpay_resp = client.get_invoice(invoice.invoice_id)
                except:
                    logger.exception(
                        "BTCPay server connection problem while checking invoice payment status."
                    )
                    break

                # If status is new, there is not any update yet on BTCPay server, so move to the next invoice.
                if btcpay_resp.get('status') == 'new':
                    logger.debug(f'active btcpay invoice: {invoice}')
                    continue

                # If invoice is paid, process the payment in the database as paid.
                # Else, payment is not done, so decline the payment in the database.
                if btcpay_resp['btcPaid'] >= btcpay_resp['btcPrice']:
                    logger.debug(f'paid btcpay invoice: {invoice}')
                    payment_status = 'processed'
                    btcpay_invoice_status = 'paid'
                else:
                    logger.debug(f'expired btcpay invoice: {invoice}')
                    payment_status = 'declined'
                    btcpay_invoice_status = 'expired'

                if not payments.finish_payment(
                        transaction_id=invoice.transaction_id,
                        status=payment_status):
                    logger.critical(
                        f'payment failed to be completed, transaction_id={invoice.transaction_id}'
                    )
                    continue

                invoice.status = btcpay_invoice_status
                invoice.finished_at = timezone.now()
                invoice.save()
                logger.info(
                    f'payment is {payment_status} because it is {btcpay_invoice_status}, '
                    f'transaction_id={invoice.transaction_id}')

            time.sleep(60)
Example #11
0
    def handle(self, past_days, offset_minutes, dry_run, *args, **options):
        moment_recently = timezone.now() - datetime.timedelta(minutes=offset_minutes)
        moment_2months_ago = timezone.now() - datetime.timedelta(days=past_days)
        total_count = 0
        cancelled_count = 0
        declined_count = 0
        modified_count = 0
        verified_count = 0
        fraud_count = 0
        failed_count = 0
        missed_count = 0
        suspicious_records = []

        for payment in payments.iterate_payments(
            method='pay_4csonline',
            started_at__gte=moment_2months_ago,
            started_at__lte=moment_recently,
        ):
            total_count += 1
            if payment.status == 'declined':
                declined_count += 1
                logger.debug('%r is declined', payment)
                continue

            try:
                verified = requests.get(f'{settings.ZENAIDA_BILLING_4CSONLINE_MERCHANT_VERIFY_LINK}?m='
                                        f'{settings.ZENAIDA_BILLING_4CSONLINE_MERCHANT_ID}&t={payment.transaction_id}')
            except Exception as exc:
                failed_count += 1
                logger.critical(f'payment confirmation failed {payment.transaction_id} : {exc}')
                continue

            if verified.text.count('Runtime Error'):
                failed_count += 1
                logger.critical(f'payment confirmation failed {payment.transaction_id} : Runtime Error response from the Bank')
                continue

            if verified.text != 'YES':
                if payment.status in ['paid', 'processed', ]:
                    fraud_count += 1
                    suspicious_records.append(payment)
                    logger.critical('FRAUD! %r known as paid, but Bank result is %r', payment, verified.text)
                    continue

                if verified.text == 'NO':
                    if payment.status not in ['unconfirmed', 'started', ]:
                        suspicious_records.append(payment)
                        logger.warn('%r has unexpected status, but Bank status is %r', payment, verified.text)
                    if dry_run:
                        declined_count += 1
                        logger.debug('%r must be declined, Bank status is %r', payment, verified.text)
                        continue
                    if not payments.finish_payment(transaction_id=payment.transaction_id, status='declined'):
                        failed_count += 1
                        logger.critical(f'payment not found, transaction_id is {payment.transaction_id}')
                        continue
                    modified_count += 1
                    declined_count += 1
                    continue

                if verified.text == 'NOTFOUND':
                    if payment.status in ['started', ]:
                        if dry_run:
                            cancelled_count += 1
                            logger.debug('%r started, but not known to the Bank and must be cancelled', payment)
                            continue
                        if not payments.finish_payment(transaction_id=payment.transaction_id, status='cancelled'):
                            failed_count += 1
                            logger.critical(f'payment not found, transaction_id is {payment.transaction_id}')
                            continue
                        logger.info('%r was started while ago but not known to the Bank, cancelled', payment)
                        cancelled_count += 1
                        modified_count += 1
                        continue
                    logger.warn('%r is still pending, Bank status is %r', payment, verified.text)
                    continue

                failed_count += 1
                logger.critical('%r unexpected status from Bank: %r', payment, verified.text)
                continue

            if payment.status in ['unconfirmed', 'started', 'paid', ]:
                missed_count += 1
                verified_count += 1
                if dry_run:
                    logger.warn('%r must be accepted, Bank status is %r', payment, verified.text)
                    continue
                if not payments.finish_payment(transaction_id=payment.transaction_id, status='processed'):
                    failed_count += 1
                    logging.critical(f'payment not found, transaction_id is {payment.transaction_id}')
                    continue
                modified_count += 1
                logger.info('%r CONFIRMED and PROCESSED', payment)
                continue

            if payment.status not in ['processed', ]:
                suspicious_records.append(payment)
                failed_count += 1
                logger.critical('%r has unexpected status, Bank status is %r', payment, verified.text)
                continue

            verified_count += 1
            logger.debug('%r OK', payment)

        r = dict(
            total=total_count,
            missed=missed_count,
            cancelled=cancelled_count,
            declined=declined_count,
            modified=modified_count,
            verified=verified_count,
            fraud=fraud_count,
            failed=failed_count,
        )
        if failed_count + fraud_count + len(suspicious_records):
            self.stdout.write(self.style.ERROR(json.dumps(r, indent=4)))
        else:
            self.stdout.write(self.style.SUCCESS(json.dumps(r, indent=4)))