Пример #1
0
def notify_successful_loan_disbursal_transaction(loan_account,
                                                 transaction_amount,
                                                 transaction_charge,
                                                 mpesa_transaction_id):
    logger.debug('notify_successful_loan_disbursal_transaction')
    logger.debug({
        'mpesa_transaction_id': mpesa_transaction_id,
        'transaction_type': LoanTransactionType.LOAN_DISBURSAL,
        'transaction_amount': transaction_amount,
        'transaction_charge': transaction_charge,
        'loan_account': loan_account.account_id,
        'platform': 'mpesa'
    })

    response = queue.send_sqs_message(
        "loan_transactions_processor",
        json.dumps({
            'loan_account_id': loan_account.account_id,
            'transaction': {
                'transaction_type': LoanTransactionType.LOAN_DISBURSAL,
                'amount': transaction_amount,
                'payment_platform': {
                    'fee': transaction_charge,
                    'type': PaymentPlatform.MPESA,
                    'transaction_id': mpesa_transaction_id
                }
            }
        }))

    logger.debug(response)
Пример #2
0
def notify_successful_loan_repayment_transaction(loan_account,
                                                 repayment_amount,
                                                 mpesa_transaction_id):
    logger.debug('notify_successful_loan_repayment_transaction')
    logger.debug({
        'mpesa_transaction_id': mpesa_transaction_id,
        'transaction_type': LoanTransactionType.LOAN_REPAYMENT,
        'loan_account': loan_account.account_id,
        'repayment_amount': repayment_amount,
        'platform': 'mpesa'
    })

    response = queue.send_sqs_message(
        "loan_transactions_processor",
        json.dumps({
            'loan_account_id': loan_account.account_id,
            'transaction': {
                'transaction_type': LoanTransactionType.LOAN_REPAYMENT,
                'repayment_amount': repayment_amount,
                'payment_platform': {
                    'type': PaymentPlatform.MPESA,
                    'transaction_id': mpesa_transaction_id
                }
            }
        }))

    logger.debug(response)
Пример #3
0
def notify_loan_repayment_request(loan_account, repayment_amount):
    logger.info(
        f"notify_loan_repayment_request({loan_account.account_id}, {repayment_amount})"
    )

    time_now = timezone.now()
    loan_transaction = LoanTransaction.objects.create(
        transaction_type=LoanTransactionType.LOAN_REPAYMENT,
        loan_account=loan_account,
        initiated_at=time_now,
        status=LoanTransactionStatus.PENDING_PROCESSING,
        amount=repayment_amount)
    logger.info(
        dict(transaction_type="LOAN_REPAYMENT",
             loan_account=loan_account,
             initiated_at=time_now,
             status="PENDING_PROCESSING",
             amount=repayment_amount))

    response = queue.send_sqs_message(
        "loan_repayments",
        json.dumps({
            'loan_transaction_id': loan_transaction.transaction_id,
            'loan_account_id': loan_account.account_id,
            'repayment_amount': repayment_amount
        }))
    logger.debug(response)

    return loan_transaction
Пример #4
0
def create_dead_letter_queue(queue_url, dead_letter_queue_arn):
    # Create SQS client
    sqs = boto3.client('sqs')

    redrive_policy = {
        'deadLetterTargetArn': dead_letter_queue_arn,
        'maxReceiveCount': '10'
    }

    # Configure queue to send messages to dead letter queue
    sqs.set_queue_attributes(
        QueueUrl=queue_url,
        Attributes={'RedrivePolicy': json.dumps(redrive_policy)})
Пример #5
0
def extract_body(request, *args, **kwargs):

    try:
        data = json.loads(request.body)
    except Exception:
        data = {}
        logger.error(json.dumps(
            dict(message="Error decoding data submitted.",
                 args=args,
                 kwargs=kwargs)),
                     exc_info=True)

    return data
Пример #6
0
def process_balance_check_result(data, mpesa_transaction):
    logger.debug(f"process_balance_check_result({data}, {mpesa_transaction})")

    with db_transaction.atomic():
        cleaned = clean_balance_response(data)
        
        logger.debug(cleaned)
        logger.debug(mpesa_transaction.sender_account)

        if cleaned.get('result_code') != ResultCode.success:
            return ResultCode.internal_failure

        balance = MpesaAccountBalance.objects\
            .get(
                request_id=mpesa_transaction.transaction_id,
                mpesa_account=mpesa_transaction.sender_account)
                    
        mpesa_transaction.response_payload = balance.response_payload = json.dumps(cleaned)
        mpesa_transaction.save()

        balance.balance_updated_at = timezone.now()
        balance.status = 1
        balance.save()

        balance_items = cleaned.get('balances') or []

        if not balance_items:
            logger.debug("Balance items empty")
            return ResultCode.internal_failure

        logger.debug(f"balance_items={balance_items}")

        linked_bank_accounts = balance\
            .mpesa_account\
            .linked_bank_accounts\
            .all()
        
        for linked_bank_account in linked_bank_accounts:
            bank_account = linked_bank_account.bank_account
            logger.debug(f"bank_account={bank_account}")

            bank_account.current_balance = 0
            bank_account.balance_items = balance_items

            for _balance in balance_items:
                bank_account.current_balance += _balance['balance']

            bank_account.save()

    return ResultCode.success
Пример #7
0
    def test_get_user_is_accessible_via_token_successfully(self):
        response = self.client.get(reverse(
            'users:get_user',
            kwargs={'user_account_id': self.user.user_account.account_id}),
                                   HTTP_AUTHORIZATION=self.HTTP_AUTHORIZATION)

        self.assertTrue(response.status_code, HTTPStatus.OK.value)
        self.assertEqual(
            response.json(),
            json.loads(
                json.dumps({
                    'success': True,
                    'user_account': self.user_account.as_dict()
                })))
Пример #8
0
def notify_loan_application(loan_profile, loan_amount):
    logger.info(
        f"notify_loan_application({loan_profile.profile_id}, {loan_amount})")

    loan_application = LoanApplication.objects.create(
        loan_profile=loan_profile,
        applied_at=timezone.now(),
        payment_platform=PaymentPlatform.MPESA,
        amount=D(loan_amount))

    response = queue.send_sqs_message(
        "loan_requests",
        json.dumps({'loan_application_id': loan_application.application_id}))

    logger.debug(response)

    return loan_application
Пример #9
0
def notify_c2b_mpesa_express_response(mpesa_transaction,
                                      personal_mpesa_account,
                                      business_paybill_account, amount):
    logger.info("Notifying c2b mpesa express response")

    # -----------

    payload = {
        'business_mpesa_account_id': business_paybill_account.account_id,
        'personal_mpesa_account_id': personal_mpesa_account.account_id,
        'mpesa_transaction_id': mpesa_transaction.transaction_id,
        'transaction_type': 'c2b',
        'amount': amount
    }
    logger.debug(payload)

    # -----------

    return queue.send_sqs_message('mpesa_c2b_requests', json.dumps(payload))
Пример #10
0
def notify_b2c_mpesa_response(mpesa_transaction, personal_mpesa_account,
                              business_paybill_account, transaction_amount,
                              transaction_charge):
    logger.info("Notifying b2c response")

    # -----------

    payload = {
        'business_mpesa_account_id': business_paybill_account.account_id,
        'personal_mpesa_account_id': personal_mpesa_account.account_id,
        'mpesa_transaction_id': mpesa_transaction.transaction_id,
        'transaction_type': 'b2c',
        'transaction_amount': transaction_amount,
        'transaction_charge': transaction_charge
    }
    logger.debug(payload)

    # -----------

    return queue.send_sqs_message('mpesa_b2c_requests', json.dumps(payload))
Пример #11
0
def notify_verify_mpesa_account_request(loan_account):
    logger.info(
        f"notify_verify_mpesa_account_request({loan_account.account_id})")

    time_now = timezone.now()
    logger.info(
        dict(transaction_type="MPESA_VERIFICATION",
             initiated_at=time_now,
             status="PENDING_PROCESSING",
             amount=settings.MPESA_VERIFICATION_AMOUNT))

    response = queue.send_sqs_message(
        "mpesa_verification_request",
        json.dumps({
            'loan_transaction_id': loan_transaction.transaction_id,
            'loan_account_id': loan_account.account_id,
            'repayment_amount': repayment_amount
        }))
    logger.debug(response)

    return loan_transaction
Пример #12
0
def b2c_transact(env="sandbox",
                 live_url='https://api.safaricom.co.ke',
                 app_key=None,
                 app_secret=None,
                 initiator_name=None,
                 security_credential=None,
                 command_id=None,
                 amount=None,
                 party_a=None,
                 party_b=None,
                 remarks=None,
                 account_reference=None,
                 queue_timeout_url=None,
                 result_url=None,
                 occassion=None,
                 timeout=settings.MPESA_REQUESTS_TIMEOUT_SECONDS):
    logger.info("Sending cash for %s" % app_key)
    logger.info(
        json.dumps({
            "env": env,
            "amount": amount,
            "party_a": party_a,
            "party_b": party_b,
        }))

    return B2C(live_url=live_url,
               env=env,
               app_key=app_key,
               app_secret=app_secret,
               timeout=timeout).transact(
                   initiator_name=initiator_name,
                   security_credential=security_credential,
                   command_id=command_id,
                   amount=amount,
                   party_a=party_a,
                   party_b=party_b,
                   remarks=remarks,
                   queue_timeout_url=queue_timeout_url,
                   result_url=result_url,
                   occassion=occassion)
Пример #13
0
def get_encrypted_text(plain_text, function_name=None):
    c = boto3.client(
        'lambda',
        aws_access_key_id=settings.ENCRYPTION_INVOKER_ACCESS_KEY_ID,
        aws_secret_access_key=settings.ENCRYPTION_INVOKER_SECRET_ACCESS_KEY,
        region_name="us-east-1")

    logger.info("Boto client %s prepared" % c)

    try:
        logger.debug("Invoking lambda with access key id %s" %
                     settings.ENCRYPTION_INVOKER_ACCESS_KEY_ID)
        response = c.invoke(FunctionName=function_name,
                            InvocationType='RequestResponse',
                            Payload=json.dumps({"text": plain_text}))

        logger.info("Response returned %s" % response)
        payload = json.loads(response['Payload'].read())

        return payload['encrypted']
    except (TypeError, KeyError) as e:
        logger.exception(e)
        return
Пример #14
0
    def test_mpesa_b2c_works(self, b2c_transact, authenticate,
                             get_encrypted_text):
        expected_mpesa_token_response = {
            "access_token": "JZIAHHsd43435jjKSu238007K98SO",
            "expires_in": "3599"
        }
        b2c_transact.return_value = {
            "ConversationID": "AG_20180326_00005ca7f7c21d608166",
            "OriginatorConversationID": "12363-1328499-6",
            "ResponseCode": "0",
            "ResponseDescription": "Accept the service request successfully."
        }
        get_encrypted_text.return_value = {
            "encrypted": "%s==" % ("ahh2iza7823kasksa" * 7)
        }
        authenticate.return_value = expected_mpesa_token_response

        response = self.client.post(reverse_lazy(
            'mpesa:mpesa_api_authenticate',
            kwargs={
                'organization_id': self.organization.organization_id,
            }),
                                    data={'short_code': '263801'},
                                    HTTP_AUTHORIZATION=self.HTTP_AUTHORIZATION)

        auth_response = response.json()

        self.assertTrue(auth_response, expected_mpesa_token_response)
        self.assertTrue(response.status_code, 200)

        response = self.client.post(reverse_lazy(
            'mpesa:mpesa_b2c_transact',
            kwargs={
                'organization_id': self.organization.organization_id,
            }), {
                'short_code': '263801',
                'phone_number': '254703255755',
                'amount': '200.00',
                'notes': 'Loan to John'
            },
                                    HTTP_AUTHORIZATION=self.HTTP_AUTHORIZATION)

        b2c_response = response.json()
        transaction = MpesaTransaction.objects.last()

        self.assertEqual(
            {
                'success': True,
                'message': 'B2C Transaction successful',
                'transaction_id': transaction.third_party_transaction_id
            }, b2c_response)

        self.assertTrue(response.status_code, 200)

        request_payload = transaction.request_payload

        self.assertTrue('conversation_id' in request_payload)
        self.assertTrue('originator_conversation_id' in request_payload)
        self.assertTrue('response_code' in request_payload)
        self.assertTrue(request_payload['response_description'] ==
                        'Accept the service request successfully.')

        #simulate a webhook from M-Pesa; does not require token as its csrf token exempted
        response = self.client.post(reverse_lazy(
            'mpesa:mpesa_b2c_result_url',
            kwargs={
                'organization_id': self.organization.organization_id,
                'reference': transaction.third_party_transaction_id
            }),
                                    json.dumps(B2C_RESPONSE),
                                    content_type="application/json")

        #refresh the model from the db
        transaction.refresh_from_db()

        self.assertEquals(
            {
                'transaction_id':
                'LHL41AHJ6G',
                'transaction_recipient_full_name':
                'John Doe',
                'transaction_recipient_first_name':
                'John',
                'transaction_recipient_middle_name':
                '',
                'transaction_recipient_last_name':
                'Doe',
                'transaction_recipient_phone':
                '254703267860',
                'transaction_amount':
                100,
                'transaction_completed_at':
                '21.08.2017 12:01:59',
                'b2c_utility_balance':
                98834.0,
                'b2c_working_account_balance':
                100000.0,
                'kyc_is_registered_mpesa_user':
                '******',
                'conversation_id':
                'AG_20170821_000049448b24712383de',
                'originator_conversation_id':
                '14593-80515-2',
                'result_code':
                0,
                'result_description':
                'The service request has been accepted successfully.',
            }, transaction.response_payload)
Пример #15
0
    def test_mpesa_c2b_works(self, c2b_register_url, authenticate,
                             get_encrypted_text):
        c2b_register_url.return_value = {
            "ConversationID": "",
            "OriginatorCoversationID": "",
            "ResponseDescription": "success"
        }
        authenticate.return_value = {
            "access_token": "JZIAHHsd43435jjKSu238007K98SO",
            "expires_in": "3599"
        }
        get_encrypted_text.return_value = {
            "encrypted": "%s==" % ("ahh2iza7823kasksa" * 7)
        }

        response = self.client.post(reverse_lazy(
            'mpesa:mpesa_api_authenticate',
            kwargs={
                'organization_id': self.organization.organization_id,
            }), {'short_code': '263801'},
                                    HTTP_AUTHORIZATION=self.HTTP_AUTHORIZATION)

        auth_response = response.json()

        self.assertTrue('authentication_token' in auth_response)
        self.assertTrue(response.status_code, 200)

        response = self.client.post(
            reverse_lazy('mpesa:mpesa_c2b_register_urls',
                         kwargs={
                             'organization_id':
                             self.organization.organization_id,
                         }), {
                             "short_code": self.paybill_c2b_account.identifier,
                         },
            HTTP_AUTHORIZATION=self.HTTP_AUTHORIZATION)

        self.assertTrue(response.json()['success'])
        mpesa_registered_url_reference = response.json(
        )['mpesa_registered_url']

        mpesa_registered_url = MpesaAccountRegisteredURL.objects.get(
            reference=mpesa_registered_url_reference)

        self.assertEquals(
            mpesa_registered_url.validation_url,
            get_absolute_url("mpesa:mpesa_c2b_validation_url",
                             kwargs={
                                 "organization_id":
                                 self.organization.organization_id,
                                 "reference": mpesa_registered_url_reference
                             }))
        self.assertEquals(
            mpesa_registered_url.confirmation_url,
            get_absolute_url("mpesa:mpesa_c2b_confirmation_url",
                             kwargs={
                                 "organization_id":
                                 self.organization.organization_id,
                                 "reference": mpesa_registered_url_reference
                             }))

        #simulate a webhooks from M-Pesa
        response = self.client.post(reverse_lazy(
            'mpesa:mpesa_c2b_validation_url',
            kwargs={
                'organization_id': self.organization.organization_id,
                'reference': mpesa_registered_url.reference
            }),
                                    json.dumps({
                                        "TransactionType":
                                        "",
                                        "TransID":
                                        "LHG31AA5TX",
                                        "TransTime":
                                        "20190816190243",
                                        "TransAmount":
                                        "200.00",
                                        "BusinessShortCode":
                                        self.paybill_c2b_account.identifier,
                                        "BillRefNumber":
                                        "account",
                                        "InvoiceNumber":
                                        "",
                                        "OrgAccountBalance":
                                        "",
                                        "ThirdPartyTransID":
                                        "",
                                        "MSISDN":
                                        self.mpesa_account.identifier,
                                        "FirstName":
                                        self.mpesa_account.first_name,
                                        "MiddleName":
                                        "",
                                        "LastName":
                                        self.mpesa_account.last_name
                                    }),
                                    content_type="application/json")

        self.assertEqual(response.json(), {
            'ResultCode': ResultCode.success,
            'ResultDesc': 'Accepted'
        })

        transaction = MpesaTransaction.objects.last()

        transaction.refresh_from_db()
        self.assertEqual(transaction.result_code, None)

        response = self.client.post(reverse_lazy(
            'mpesa:mpesa_c2b_confirmation_url',
            kwargs={
                'organization_id': self.organization.organization_id,
                'reference': mpesa_registered_url_reference
            }),
                                    json.dumps({
                                        "TransID":
                                        "LHG31AA5TX",
                                        "TransactionType":
                                        "",
                                        "TransTime":
                                        "20190816190243",
                                        "TransAmount":
                                        "200.00",
                                        "BusinessShortCode":
                                        self.paybill_c2b_account.identifier,
                                        "BillRefNumber":
                                        "account",
                                        "InvoiceNumber":
                                        "",
                                        "OrgAccountBalance":
                                        "",
                                        "ThirdPartyTransID":
                                        "",
                                        "MSISDN":
                                        self.mpesa_account.identifier,
                                        "FirstName":
                                        self.mpesa_account.first_name,
                                        "MiddleName":
                                        "",
                                        "LastName":
                                        self.mpesa_account.last_name
                                    }),
                                    content_type="application/json")

        self.assertEqual(response.json(), {
            'ResultCode': ResultCode.success,
            'ResultDesc': 'Accepted'
        })

        transaction.refresh_from_db()
        self.assertEqual(transaction.result_code, ResultCode.success)
Пример #16
0
     }
 },
 'formatters': {
     'default': {
         # exact format is not important, this is the minimum information
         'format': '%(asctime)s %(name)-12s %(levelname)-8s %(message)s',
     },
     'django.server': DEFAULT_LOGGING['formatters']['django.server'],
     'json_formatter': {
         'format':
         json.dumps({
             "name": "%(name)-12s",
             "level": "%(levelname)-8s",
             "time": "%(asctime)s",
             "level_name": "%(levelname)s",
             "module": "%(module)s",
             "date": "%(asctime)s",
             "data": "%(message)s",
             "line_no": "%(lineno)d",
             "path": "%(pathname)s"
         }),
         'class':
         'pythonjsonlogger.jsonlogger.JsonFormatter',
         'datefmt':
         "%Y-%m-%d %H:%M:%S",
     },
     'main_formatter': {
         'format': '%(levelname)s:%(name)s: %(message)s '
         '(%(asctime)s; %(filename)s:%(lineno)d)',
         'datefmt': "%Y-%m-%d %H:%M:%S",
     },
Пример #17
0
    def test_mpesa_user_can_register_and_borrow(self, b2c_transact,
                                                authenticate,
                                                get_encrypted_text,
                                                mpesa_express_stk_push):

        b2c_transact.return_value = {
            "ConversationID": "AG_20180326_00005ca7f7c21d608166",
            "OriginatorConversationID": "12363-1328499-6",
            "ResponseCode": "0",
            "ResponseDescription": "Accept the service request successfully."
        }
        authenticate.return_value = {
            "access_token": "JZIAHHsd43435jjKSu238007K98SO",
            "expires_in": "3599"
        }
        get_encrypted_text.return_value = {
            "encrypted": "%s==" % ("ahh2iza7823kasksa" * 7)
        }
        mpesa_express_stk_push.return_value = {
            "MerchantRequestID": "21605-295434-4",
            "CheckoutRequestID": "ws_CO_04112017184930742",
            "ResponseCode": "0",
            "ResponseDescription": "Success. Request accepted for processing",
            "CustomerMessage": "Success. Request accepted for processing"
        }

        #------------- Setup

        # 3. Now, borrow the amount
        response = self.client.post(reverse_lazy(
            'mpesa:mpesa_express_c2b_push',
            kwargs={'organization_id': self.organization.organization_id}),
                                    data={
                                        'short_code': '263801',
                                        'phone_number': self.user.username,
                                        'amount': 320,
                                        'notes': 'My notes'
                                    },
                                    HTTP_AUTHORIZATION=self.HTTP_AUTHORIZATION)
        transaction = MpesaTransaction.objects.last()
        self.assertEqual(
            response.json(),
            json.loads(
                json.dumps({
                    'success': True,
                    'transaction': transaction.as_dict()
                })))

        # 3. Simulate callback from M-Pesa
        response = self.client.post(reverse_lazy(
            'mpesa:mpesa_c2b_stk_push_callback_url',
            kwargs={
                'organization_id': self.organization.organization_id,
                'reference': transaction.third_party_transaction_id
            }),
                                    json.dumps(C2B_STK_MPESA_EXPRESS_RESPONSE),
                                    content_type="application/json")

        self.assertEqual(response.json(), {
            'ResultCode': 0,
            'ResultDesc': 'Accepted'
        })