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)
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
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
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
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
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})
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')
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'
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
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)
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)))