def send_payment_share(destination, amount, idem, description=None): client = coinbase_utils.get_api_client() account = client.get_primary_account() transaction = account.send_money( to=destination, amount=bitcoin_utils.get_valid_btc_amount(amount), currency=CURRENCY_BTC, idem=idem, description=description) return transaction
def send_payment_share(destination, amount, idem, description=None): client = coinbase_utils.get_api_client() account = client.get_primary_account() transaction = account.send_money( to=destination, amount=bitcoin_utils.get_valid_btc_amount(amount), currency=CURRENCY_BTC, idem=idem, description=description ) return transaction
def complete_bitpesa_payment(transaction): bp_transaction_id = transaction.get(bitpesa.KEY_ID, None) metadata = transaction.get(bitpesa.KEY_METADATA, None) reference = metadata.get(bitpesa.KEY_REFERENCE, None) bp_idem_key = metadata.get(bitpesa.KEY_IDEM_KEY, None) input_amount = Decimal('%s' % transaction.get(bitpesa.KEY_INPUT_AMOUNT, 0)) payin_methods = transaction.get(bitpesa.KEY_PAYIN_METHODS, None) destination_address = None if payin_methods: out_details = payin_methods[0][bitpesa.KEY_OUT_DETAILS] # Key was originally 'bitcoin_address' on first test but it appeared to have 'Address', # Check both for redundancy and inquire from BitPesa on this if bitpesa.KEY_BITCOIN_ADDRESS in out_details: destination_address = out_details.get(bitpesa.KEY_BITCOIN_ADDRESS, None) else: destination_address = out_details.get("Address", None) if not destination_address: destination_address = payin_methods[0][bitpesa.KEY_IN_DETAILS].get(bitpesa.KEY_ADDRESS, None) if destination_address: try: payment = ParticipantPayment.objects.get( id=reference, ref=bp_transaction_id, extra=bp_idem_key, status=STATUS_INITIATED ) except: payment = None if payment: transaction_state = transaction.get(bitpesa.KEY_STATE, None) if transaction_state == bitpesa.VALUE_APPROVED: share_amount = Decimal( bitcoin_utils.get_valid_btc_amount( payment.source.btc_received * Decimal(payment.participant.payment_share) ) ) if input_amount <= share_amount: cb_transaction = send_payment_share( destination=destination_address, amount=input_amount, idem='{}-bitpesa-{}'.format(str(payment.idem_key), bp_transaction_id)[:60], description='%s - %s' % ( payment.participant.task.summary, payment.participant.user.display_name ) ) if cb_transaction.status not in [ coinbase_utils.TRANSACTION_STATUS_FAILED, coinbase_utils.TRANSACTION_STATUS_EXPIRED, coinbase_utils.TRANSACTION_STATUS_CANCELED ]: payment.btc_sent = input_amount payment.destination = destination_address payment.ref = cb_transaction.id payment.status = STATUS_PROCESSING payment.extra = json.dumps(dict(bitpesa=bp_transaction_id)) payment.sent_at = datetime.datetime.utcnow() payment.save() # Save immediately incase next step fails payment.btc_price = coinbase_utils.get_btc_price( payment.source.task and payment.source.task.currency or payment.source.multi_pay_key.currency ) payment.save() return True elif transaction_state == bitpesa.VALUE_CANCELED: # Fail for canceled BitPesa transactions if payment.status == STATUS_INITIATED: # Switch status to pending if BTC hasn't already been sent payment.status = STATUS_PENDING payment.save() return False
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()