Esempio n. 1
0
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
Esempio n. 2
0
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
Esempio n. 3
0
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
Esempio n. 4
0
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()
Esempio n. 5
0
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()