def pay(self, request, pk=None, provider=None): """ Task Payment Provider Endpoint --- omit_serializer: true omit_parameters: - query """ task = self.get_object() callback = '%s://%s/task/%s/rate/' % (request.scheme, request.get_host(), pk) next_url = callback if task and task.has_object_write_permission(request) and bitcoin_utils.is_valid_btc_address(task.btc_address): if provider == TASK_PAYMENT_METHOD_BITONIC: client = oauth1.Client( BITONIC_CONSUMER_KEY, BITONIC_CONSUMER_SECRET, BITONIC_ACCESS_TOKEN, BITONIC_TOKEN_SECRET, callback_uri=callback, signature_type=SIGNATURE_TYPE_QUERY ) amount = task.pay increase_factor = 1 + (Decimal(BITONIC_PAYMENT_COST_PERCENTAGE)*Decimal(0.01)) q_string = urlencode({ 'ext_data': task.summary.encode('utf-8'), 'bitcoinaddress': task.btc_address, 'ordertype': 'buy', 'euros': amount * increase_factor }) req_data = client.sign('%s/?%s' % (BITONIC_URL, q_string), http_method='GET') next_url = req_data[0] return redirect(next_url)
def update_multi_tasks(multi_task_key, distribute=False): multi_task_key = clean_instance(multi_task_key, MultiTaskPaymentKey) if multi_task_key.distribute_only: connected_tasks = multi_task_key.distribute_tasks connected_tasks.filter(paid=True).update( btc_price=multi_task_key.btc_price, withhold_tunga_fee_distribute=multi_task_key.withhold_tunga_fee, btc_paid=multi_task_key.paid, btc_paid_at=multi_task_key.paid_at ) else: connected_tasks = multi_task_key.tasks connected_tasks.filter(paid=False).update( payment_method=multi_task_key.payment_method, btc_price=multi_task_key.btc_price, withhold_tunga_fee=multi_task_key.withhold_tunga_fee, paid=multi_task_key.paid, paid_at=multi_task_key.paid_at, processing=multi_task_key.processing, processing_at=multi_task_key.processing_at ) # Generate invoices for all connected tasks for task in connected_tasks.all(): if multi_task_key.distribute_only: if task.paid and multi_task_key.paid: # Coinbase waits for 6 confirmations, so not safe to distribute yet # distribute_task_payment.delay(task.id) pass return # Save Invoice if not task.btc_address or not bitcoin_utils.is_valid_btc_address(task.btc_address): address = coinbase_utils.get_new_address(coinbase_utils.get_api_client()) task.btc_address = address task.save() TaskInvoice.objects.create( task=task, user=multi_task_key.user, title=task.title, fee=task.pay, client=task.owner or task.user, # developer=developer, payment_method=multi_task_key.payment_method, btc_price=multi_task_key.btc_price, btc_address=task.btc_address, withhold_tunga_fee=multi_task_key.withhold_tunga_fee ) if distribute and multi_task_key.paid: distribute_task_payment_payoneer.delay(task.id)
def btc_address(self): if not self.profile: return None if self.profile.payment_method == PAYMENT_METHOD_BTC_ADDRESS: if bitcoin_utils.is_valid_btc_address(self.profile.btc_address): return self.profile.btc_address elif self.profile.payment_method == PAYMENT_METHOD_BTC_WALLET: wallet = self.profile.btc_wallet if wallet.provider == BTC_WALLET_PROVIDER_COINBASE: client = coinbase_utils.get_oauth_client(wallet.token, wallet.token_secret, self) return coinbase_utils.get_new_address(client) return None
def btc_address(self): if not self.profile: return None if self.profile.payment_method == PAYMENT_METHOD_BTC_ADDRESS: if bitcoin_utils.is_valid_btc_address(self.profile.btc_address): return self.profile.btc_address elif self.profile.payment_method == PAYMENT_METHOD_BTC_WALLET: wallet = self.profile.btc_wallet if wallet.provider == BTC_WALLET_PROVIDER_COINBASE: client = coinbase_utils.get_oauth_client( wallet.token, wallet.token_secret, self) return coinbase_utils.get_new_address(client) return None
def process_invoices(pk, invoice_types=('client', ), user_id=None, developer_ids=None, is_admin=False, filepath=None): """ :param pk: id of the task :param invoice_types: tuple of invoice types to generate e.g 'client', 'developer', 'tunga' :param user_id: user viewing the invoice(s) :param developer_ids: participant invoices to generate. only applies to 'developer' and 'tunga' invoices :param is_admin: is requester an admin? :param filepath: file to store invoice in :return: """ all_invoices = list() if pk == 'all': tasks = Task.objects.filter(closed=True, taskinvoice__isnull=False) if user_id and not is_admin: tasks = tasks.filter( Q(user_id=user_id) | Q(owner_id=user_id) | Q(pm_id=user_id) | Q(participant__user_id=user_id)) tasks = tasks.distinct() else: tasks = Task.objects.filter(id=pk) for task in tasks: invoice = task.invoice if invoice: invoice = invoice.clean_invoice() if invoice.number: initial_invoice_data = TaskInvoiceSerializer(invoice).data initial_invoice_data[ 'date'] = task.invoice.created_at.strftime('%d %B %Y') task_owner = task.user if task.owner: task_owner = task.owner participation_shares = task.get_participation_shares() common_developer_info = list() for share_info in participation_shares: participant = share_info['participant'] developer, created = DeveloperNumber.objects.get_or_create( user=participant.user) amount_details = invoice.get_amount_details( share=share_info['share']) if (not developer_ids or participant.user.id in developer_ids) and \ not (participant.prepaid or (participant.prepaid is None and participant.user.is_internal)): common_developer_info.append({ 'developer': InvoiceUserSerializer(participant.user).data, 'amount': amount_details, 'dev_number': developer.number or '', 'participant': participant }) for invoice_type in invoice_types: if invoice_type == 'developer' and invoice.version > 1: continue task_developers = [] invoice_data = copy(initial_invoice_data) if invoice_type == 'client': invoice_data['number_client'] = invoice.invoice_id( invoice_type='client') task_developers = [dict()] else: for common_info in common_developer_info: final_dev_info = copy(common_info) final_dev_info['number'] = invoice.invoice_id( invoice_type=invoice_type, user=common_info['participant'] and common_info['participant'].user or None) participant_payment_method = None if common_info['participant']: try: participant_payment = common_info[ 'participant'].participantpayment_set.filter( ).latest('created_at') if participant_payment and bitcoin_utils.is_valid_btc_address( participant_payment.destination): participant_payment_method = PAYMENT_METHOD_BITCOIN except: pass final_dev_info[ 'payment_method'] = participant_payment_method task_developers.append(final_dev_info) invoice_data['developers'] = task_developers all_invoices.append( dict(invoice_type=invoice_type, invoice=invoice_data, location=invoice_type == 'client' and invoice.vat_location_client or VAT_LOCATION_WORLD)) ctx = dict(invoices=all_invoices) rendered_html = render_to_string("tunga/pdf/invoice.html", context=ctx).encode(encoding="UTF-8") if filepath: HTML(string=rendered_html, encoding='utf-8').write_pdf(filepath) return rendered_html
def validate_btc_address_or_none(value): error_msg = 'Invalid Bitcoin address.' if value is not None and re.match(r"[a-zA-Z1-9]{27,35}$", value) is None: raise ValidationError(error_msg) if value is not None and not is_valid_btc_address(value): raise ValidationError(error_msg)
def invoice(self, request, pk=None): """ Task Invoice Endpoint --- request_serializer: TaskPaymentSerializer response_serializer: TaskInvoiceSerializer omit_parameters: - query """ task = get_object_or_404(self.get_queryset(), pk=pk) self.check_object_permissions(request, task) invoice = task.invoice if request.method == 'POST': serializer = self.get_serializer(data=request.data) serializer.is_valid(raise_exception=True) fee = serializer.validated_data['fee'] payment_method = serializer.validated_data['payment_method'] if fee < task.pay: raise ValidationError({ 'fee': 'You cannot reduce the fee for the task, Please contact [email protected] for assistance' }) task.bid = fee task.payment_method = payment_method btc_price = coinbase_utils.get_btc_price(task.currency) task.btc_price = btc_price if not task.btc_address or not bitcoin_utils.is_valid_btc_address(task.btc_address): address = coinbase_utils.get_new_address(coinbase_utils.get_api_client()) task.btc_address = address task.full_clean() task.save() developer = None try: assignee = task.participation_set.filter(accepted=True).order_by('-assignee').earliest('created_at') developer = assignee.user except: pass if not developer: raise ValidationError({ 'fee': 'Please assign a developer to the task or contact [email protected] for assistance' }) # Save Invoice invoice = TaskInvoice.objects.create( task=task, title=task.title, fee=task.pay, client=task.user, developer=developer, payment_method=task.payment_method, btc_price=btc_price, btc_address=task.btc_address ) if task.payment_method == TASK_PAYMENT_METHOD_BANK: # Send notification for requested invoice send_task_invoice_request_email.delay(task.id) response_serializer = TaskInvoiceSerializer(invoice, context={'request': request}) return Response(response_serializer.data)
def distribute_task_payment(task, force_distribution=False, destination=None, target_payment=None): if not force_distribution: return task = clean_instance(task, Task) if not task.paid: return if task.pay_distributed and not target_payment: return pay_description = task.summary participation_shares = task.get_payment_shares() # Distribute all payments for this task payments = TaskPayment.objects.filter( Q(multi_pay_key__tasks=task) | Q(multi_pay_key__distribute_tasks=task) | (Q(task=task) & Q(processed=False)), received_at__isnull=False, payment_type=PAYMENT_METHOD_BITCOIN ) if target_payment: payments.filter(id=target_payment) task_distribution = [] for payment in payments: portion_distribution = [] for item in participation_shares: participant = item['participant'] share = item['share'] share_amount = Decimal(share) * payment.task_pay_share(task) portion_sent = False if not participant.user: continue participant_pay, created = ParticipantPayment.objects.get_or_create( source=payment, participant=participant ) payment_method = participant.user.payment_method if created or (participant_pay and participant_pay.status in [STATUS_PENDING, STATUS_RETRY]): if destination or payment_method in [PAYMENT_METHOD_BTC_ADDRESS, PAYMENT_METHOD_BTC_WALLET]: pay_destination = destination if not pay_destination: pay_destination = participant_pay.destination if not (pay_destination and bitcoin_utils.is_valid_btc_address(pay_destination)): pay_destination = participant.user.btc_address tunga_wallet_balance = coinbase_utils.get_account_balance() transaction = send_payment_share( destination=pay_destination, amount=share_amount, idem=str(participant_pay.idem_key), description='{} - {}'.format(pay_description, participant.user.display_name) ) if transaction.status not in [ coinbase_utils.TRANSACTION_STATUS_FAILED, coinbase_utils.TRANSACTION_STATUS_EXPIRED, coinbase_utils.TRANSACTION_STATUS_CANCELED ]: participant_pay.ref = transaction.id participant_pay.btc_sent = abs(Decimal(transaction.amount.amount)) participant_pay.status = STATUS_PROCESSING participant_pay.sent_at = datetime.datetime.utcnow() participant_pay.save() portion_sent = True elif payment_method == PAYMENT_METHOD_MOBILE_MONEY: tunga_wallet_balance = coinbase_utils.get_account_balance() if tunga_wallet_balance > share_amount: # Only attempt BitPesa payment if Wallet has enough balance recipients = [ { bitpesa.KEY_REQUESTED_AMOUNT: float( bitcoin_utils.get_valid_btc_amount(share_amount) ), bitpesa.KEY_REQUESTED_CURRENCY: CURRENCY_BTC, bitpesa.KEY_PAYOUT_METHOD: { bitpesa.KEY_TYPE: bitpesa.get_pay_out_method(participant.user.mobile_money_cc), bitpesa.KEY_DETAILS: { bitpesa.KEY_FIRST_NAME: participant.user.first_name, bitpesa.KEY_LAST_NAME: participant.user.last_name, bitpesa.KEY_PHONE_NUMBER: participant.user.mobile_money_number } } } ] bitpesa_nonce = str(uuid4()) transaction = bitpesa.create_transaction( BITPESA_SENDER, recipients, input_currency=CURRENCY_BTC, transaction_id=participant_pay.id, nonce=bitpesa_nonce ) if transaction: participant_pay.external_created_at = datetime.datetime.utcnow() participant_pay.ref = transaction.get(bitpesa.KEY_ID, None) participant_pay.status = STATUS_INITIATED participant_pay.extra = bitpesa_nonce participant_pay.save() if complete_bitpesa_payment(transaction): portion_sent = True else: # TODO: Notify via Slack of failed payment due to balance pass elif participant_pay and payment_method == PAYMENT_METHOD_MOBILE_MONEY and \ participant_pay.status == STATUS_INITIATED: tunga_wallet_balance = coinbase_utils.get_account_balance() if tunga_wallet_balance > share_amount: # Only attempt BitPesa payment if Wallet has enough balance transaction = bitpesa.get_transaction(participant_pay.ref) if transaction and complete_bitpesa_payment(transaction): portion_sent = True else: # TODO: Notify via Slack of failed payment due to balance pass portion_distribution.append(portion_sent) if portion_distribution and False not in portion_distribution: payment.processed = True payment.save() task_distribution.append(True) else: task_distribution.append(False) if task_distribution and not (False in task_distribution): task.pay_distributed = True task.save()
def distribute_task_payment(task): task = clean_instance(task, Task) if not task.paid: return if task.pay_distributed: return pay_description = task.summary participation_shares = task.get_payment_shares() # Distribute all payments for this task payments = TaskPayment.objects.filter( Q(multi_pay_key__tasks=task) | Q(multi_pay_key__distribute_tasks=task) | (Q(task=task) & Q(processed=False)), received_at__isnull=False, payment_type=TASK_PAYMENT_METHOD_BITCOIN) task_distribution = [] for payment in payments: portion_distribution = [] for item in participation_shares: participant = item['participant'] share = item['share'] portion_sent = False if not participant.user: continue participant_pay, created = ParticipantPayment.objects.get_or_create( source=payment, participant=participant) payment_method = participant.user.payment_method if created or (participant_pay and participant_pay.status == STATUS_PENDING): if payment_method in [ PAYMENT_METHOD_BTC_ADDRESS, PAYMENT_METHOD_BTC_WALLET ]: if not (participant_pay.destination and bitcoin_utils.is_valid_btc_address( participant_pay.destination)): participant_pay.destination = participant.user.btc_address transaction = send_payment_share( destination=participant_pay.destination, amount=Decimal(share) * payment.task_btc_share(task), idem=str(participant_pay.idem_key), description='%s - %s' % (pay_description, participant.user.display_name)) if transaction.status not in [ coinbase_utils.TRANSACTION_STATUS_FAILED, coinbase_utils.TRANSACTION_STATUS_EXPIRED, coinbase_utils.TRANSACTION_STATUS_CANCELED ]: participant_pay.ref = transaction.id participant_pay.btc_sent = abs( Decimal(transaction.amount.amount)) participant_pay.status = STATUS_PROCESSING participant_pay.save() portion_sent = True elif payment_method == PAYMENT_METHOD_MOBILE_MONEY: share_amount = Decimal(share) * payment.task_btc_share( task) recipients = [{ bitpesa.KEY_REQUESTED_AMOUNT: float( bitcoin_utils.get_valid_btc_amount(share_amount)), bitpesa.KEY_REQUESTED_CURRENCY: CURRENCY_BTC, bitpesa.KEY_PAYOUT_METHOD: { bitpesa.KEY_TYPE: bitpesa.get_pay_out_method( participant.user.mobile_money_cc), bitpesa.KEY_DETAILS: { bitpesa.KEY_FIRST_NAME: participant.user.first_name, bitpesa.KEY_LAST_NAME: participant.user.last_name, bitpesa.KEY_PHONE_NUMBER: participant.user.mobile_money_number } } }] bitpesa_nonce = str(uuid4()) transaction = bitpesa.create_transaction( BITPESA_SENDER, recipients, input_currency=CURRENCY_BTC, transaction_id=participant_pay.id, nonce=bitpesa_nonce) if transaction: participant_pay.ref = transaction.get( bitpesa.KEY_ID, None) participant_pay.status = STATUS_INITIATED participant_pay.extra = bitpesa_nonce participant_pay.save() if complete_bitpesa_payment(transaction): portion_sent = True elif participant_pay and payment_method == PAYMENT_METHOD_MOBILE_MONEY and \ participant_pay.status == STATUS_INITIATED: transaction_details = bitpesa.call_api( bitpesa.get_endpoint_url('transactions/%s' % participant_pay.ref), 'GET', str(uuid4()), data={}) transaction = transaction_details.json().get( bitpesa.KEY_OBJECT) if transaction and complete_bitpesa_payment(transaction): portion_sent = True portion_distribution.append(portion_sent) if portion_distribution and False not in portion_distribution: payment.processed = True payment.save() task_distribution.append(True) else: task_distribution.append(False) if task_distribution and not (False in task_distribution): task.pay_distributed = True task.save()
def process_invoices(pk, invoice_types=('client',), user_id=None, developer_ids=None, is_admin=False, filepath=None): """ :param pk: id of the task :param invoice_types: tuple of invoice types to generate e.g 'client', 'developer', 'tunga' :param user_id: user viewing the invoice(s) :param developer_ids: participant invoices to generate. only applies to 'developer' and 'tunga' invoices :param is_admin: is requester an admin? :param filepath: file to store invoice in :return: """ all_invoices = list() if pk == 'all': tasks = Task.objects.filter(closed=True, taskinvoice__isnull=False) if user_id and not is_admin: tasks = tasks.filter( Q(user_id=user_id) | Q(owner_id=user_id) | Q(pm_id=user_id) | Q(participant__user_id=user_id)) tasks = tasks.distinct() else: tasks = Task.objects.filter(id=pk) for task in tasks: invoice = task.invoice if invoice: invoice = invoice.clean_invoice() if invoice.number: initial_invoice_data = TaskInvoiceSerializer(invoice).data initial_invoice_data['date'] = task.invoice.created_at.strftime('%d %B %Y') task_owner = task.user if task.owner: task_owner = task.owner participation_shares = task.get_participation_shares() common_developer_info = list() for share_info in participation_shares: participant = share_info['participant'] developer, created = DeveloperNumber.objects.get_or_create(user=participant.user) amount_details = invoice.get_amount_details(share=share_info['share']) if (not developer_ids or participant.user.id in developer_ids) and \ not (participant.prepaid or (participant.prepaid is None and participant.user.is_internal)): common_developer_info.append({ 'developer': InvoiceUserSerializer(participant.user).data, 'amount': amount_details, 'dev_number': developer.number or '', 'participant': participant }) for invoice_type in invoice_types: if invoice_type == 'developer' and invoice.version > 1: continue task_developers = [] invoice_data = copy(initial_invoice_data) if invoice_type == 'client': invoice_data['number_client'] = invoice.invoice_id(invoice_type='client') task_developers = [dict()] else: for common_info in common_developer_info: final_dev_info = copy(common_info) final_dev_info['number'] = invoice.invoice_id( invoice_type=invoice_type, user=common_info['participant'] and common_info['participant'].user or None ) participant_payment_method = None if common_info['participant']: try: participant_payment = common_info['participant'].participantpayment_set.filter().latest('created_at') if participant_payment and bitcoin_utils.is_valid_btc_address(participant_payment.destination): participant_payment_method = PAYMENT_METHOD_BITCOIN except: pass final_dev_info['payment_method'] = participant_payment_method task_developers.append(final_dev_info) invoice_data['developers'] = task_developers all_invoices.append( dict( invoice_type=invoice_type, invoice=invoice_data, location=invoice_type == 'client' and invoice.vat_location_client or VAT_LOCATION_WORLD ) ) ctx = dict( invoices=all_invoices ) rendered_html = render_to_string("tunga/pdf/invoice.html", context=ctx).encode(encoding="UTF-8") if filepath: HTML(string=rendered_html, encoding='utf-8').write_pdf(filepath) return rendered_html