def deactivate_hardcoded_twilio_gateway_fees(apps, schema_editor):
    SmsGatewayFeeCriteria = apps.get_model('smsbillables', 'SmsGatewayFeeCriteria')

    to_deactivate = SmsGatewayFeeCriteria.objects.filter(
        backend_api_id=SQLTwilioBackend.get_api_id(),
        country_code__isnull=False,
        is_active=True,
    )
    remaining = SmsGatewayFeeCriteria.objects.filter(
        backend_api_id=SQLTwilioBackend.get_api_id(),
    ).exclude(
        id__in=to_deactivate.values('id')
    )
    assert remaining.count() == 2
    assert remaining.filter(country_code=None).count() == 2
    to_deactivate.update(is_active=False)

    assert SmsGatewayFeeCriteria.objects.filter(
        backend_api_id=SQLTwilioBackend.get_api_id(),
        is_active=True,
    ).count() == 2
    assert SmsGatewayFeeCriteria.objects.filter(
        backend_api_id=SQLTwilioBackend.get_api_id(),
        country_code__isnull=True,
        is_active=True,
    ).count() == 2
예제 #2
0
def deactivate_hardcoded_twilio_gateway_fees(apps, schema_editor):
    SmsGatewayFeeCriteria = apps.get_model('smsbillables',
                                           'SmsGatewayFeeCriteria')

    to_deactivate = SmsGatewayFeeCriteria.objects.filter(
        backend_api_id=SQLTwilioBackend.get_api_id(),
        country_code__isnull=False,
        is_active=True,
    )
    remaining = SmsGatewayFeeCriteria.objects.filter(
        backend_api_id=SQLTwilioBackend.get_api_id(), ).exclude(
            id__in=to_deactivate.values('id'))
    assert remaining.count() == 2
    assert remaining.filter(country_code=None).count() == 2
    to_deactivate.update(is_active=False)

    assert SmsGatewayFeeCriteria.objects.filter(
        backend_api_id=SQLTwilioBackend.get_api_id(),
        is_active=True,
    ).count() == 2
    assert SmsGatewayFeeCriteria.objects.filter(
        backend_api_id=SQLTwilioBackend.get_api_id(),
        country_code__isnull=True,
        is_active=True,
    ).count() == 2
예제 #3
0
    def setUpClass(cls):
        super(TestGatewayChargeWithAPISupport, cls).setUpClass()
        cls.domain = 'sms_test_api_domain'

        cls.backend = SQLTwilioBackend(name="TEST API BACKEND",
                                       is_global=True,
                                       domain=cls.domain,
                                       hq_api_id=SQLTwilioBackend.get_api_id())
        cls.backend.save()
예제 #4
0
 def post(self, request, api_key, *args, **kwargs):
     message_sid = request.POST.get('MessageSid')
     from_ = SQLTwilioBackend.convert_from_whatsapp(
         request.POST.get('From'))
     body = request.POST.get('Body')
     incoming_sms(from_,
                  body,
                  SQLTwilioBackend.get_api_id(),
                  backend_message_id=message_sid,
                  domain_scope=self.domain,
                  backend_id=self.backend_couch_id)
     return HttpResponse(EMPTY_RESPONSE)
예제 #5
0
    def test_twilio_multipart_usage_charge(self):
        self.apply_direction_fee()
        twilio_backend = SQLTwilioBackend.objects.create(
            name='TWILIO',
            is_global=True,
            hq_api_id=SQLTwilioBackend.get_api_id(),
            couch_id='global_backend',
        )
        twilio_backend.set_extra_fields(
            account_sid='sid',
            auth_token='token',
        )
        twilio_backend.save()

        messages = generator.arbitrary_messages_by_backend_and_direction(
            {twilio_backend.hq_api_id: twilio_backend.couch_id}
        )
        for message in messages:
            FakeTwilioMessageFactory.add_num_segments_for_message(message.backend_message_id, randint(1, 10))
            FakeTwilioMessageFactory.add_price_for_message(message.backend_message_id, generator.arbitrary_fee())

        for message in messages:
            multipart_count = randint(1, 10)  # Should be ignored
            billable = SmsBillable.create(message, multipart_count=multipart_count)
            self.assertIsNotNone(billable)
            self.assertEqual(
                billable.usage_charge,
                (
                    self.least_specific_fees[message.direction]
                    * FakeTwilioMessageFactory.get_num_segments_for_message(
                        message.backend_message_id
                    )
                )
            )
예제 #6
0
 def setUp(self):
     super(TwilioLogCallTestCase, self).setUp()
     self.backend = SQLTwilioBackend.objects.create(
         name='TWILIO',
         is_global=True,
         hq_api_id=SQLTwilioBackend.get_api_id()
     )
예제 #7
0
    def test_twilio_domain_level_backend(self, mock_log_smsbillables_error):
        add_twilio_gateway_fee(apps)
        bootstrap_usage_fees(apps)
        twilio_backend = SQLTwilioBackend.objects.create(
            name='TWILIO',
            is_global=False,
            hq_api_id=SQLTwilioBackend.get_api_id(),
            couch_id='domain_backend',
        )
        twilio_backend.set_extra_fields(
            account_sid='sid',
            auth_token='token',
        )
        twilio_backend.save()

        messages = [
            message for phone_number in
            [generator.arbitrary_phone_number() for _ in range(10)] for message
            in generator.arbitrary_messages_by_backend_and_direction(
                {twilio_backend.hq_api_id: twilio_backend.couch_id},
                phone_number=phone_number)
        ]
        for msg_log in messages:
            FakeTwilioMessageFactory.add_price_for_message(
                msg_log.backend_message_id, generator.arbitrary_fee())

        for msg_log in messages:
            multipart_count = randint(1, 10)  # Should be ignored
            billable = SmsBillable.create(msg_log,
                                          multipart_count=multipart_count)
            self.assertIsNotNone(billable)
            self.assertIsNone(billable.gateway_fee)
            self.assertEqual(billable.gateway_charge, 0)

        self.assertEqual(mock_log_smsbillables_error.call_count, 0)
예제 #8
0
 def _get_provider_charges(cls, backend_message_id, backend_instance, direction, couch_id, backend_api_id):
     if backend_message_id:
         if backend_api_id == SQLTwilioBackend.get_api_id():
             message = get_twilio_message(backend_instance, backend_message_id)
             status = message.status
             price = message.price
         elif backend_api_id == InfobipBackend.get_api_id():
             message = get_infobip_message(backend_instance, backend_message_id)
             status = message['status']['name']
             price = message['price']['pricePerMessage']
         else:
             raise ProviderFeeNotSupportedException("backend_message_id=%s" % backend_message_id)
         if status is None or status.lower() in [
             'accepted',
             'queued',
             'sending',
             'receiving',
         ] or price is None:
             raise RetryBillableTaskException("backend_message_id=%s" % backend_message_id)
         return _ProviderChargeInfo(
             abs(Decimal(price)),
             SmsGatewayFee.get_by_criteria(
                 backend_api_id,
                 direction,
             )
         )
     else:
         log_smsbillables_error(
             "Could not create gateway fee for message %s: no backend_message_id" % couch_id
         )
         return _ProviderChargeInfo(None, None)
예제 #9
0
 def setUp(self):
     super(TwilioLogCallTestCase, self).setUp()
     self.backend = SQLTwilioBackend.objects.create(
         name='TWILIO',
         is_global=True,
         hq_api_id=SQLTwilioBackend.get_api_id()
     )
예제 #10
0
    def test_twilio_multipart_usage_charge(self):
        self.apply_direction_fee()
        twilio_backend = SQLTwilioBackend.objects.create(
            name='TWILIO',
            is_global=True,
            hq_api_id=SQLTwilioBackend.get_api_id(),
            couch_id='global_backend',
        )
        twilio_backend.set_extra_fields(
            account_sid='sid',
            auth_token='token',
        )
        twilio_backend.save()

        messages = generator.arbitrary_messages_by_backend_and_direction(
            {twilio_backend.hq_api_id: twilio_backend.couch_id}
        )
        for message in messages:
            FakeTwilioMessageFactory.add_num_segments_for_message(message.backend_message_id, randint(1, 10))
            FakeTwilioMessageFactory.add_price_for_message(message.backend_message_id, generator.arbitrary_fee())

        for message in messages:
            multipart_count = randint(1, 10)  # Should be ignored
            billable = SmsBillable.create(message, multipart_count=multipart_count)
            self.assertIsNotNone(billable)
            self.assertEqual(
                billable.usage_charge,
                (
                    self.least_specific_fees[message.direction]
                    * FakeTwilioMessageFactory.get_num_segments_for_message(
                        message.backend_message_id
                    )
                )
            )
예제 #11
0
def _available_gateway_fee_backends():
    return [
        backend for backend in get_sms_backend_classes().values()
        if backend.get_api_id() not in
        [SQLTwilioBackend.get_api_id(),
         InfobipBackend.get_api_id()]
    ]
예제 #12
0
    def test_twilio_domain_level_backend(self, mock_log_smsbillables_error):
        add_twilio_gateway_fee(apps)
        bootstrap_usage_fees(apps)
        twilio_backend = SQLTwilioBackend.objects.create(
            name='TWILIO',
            is_global=False,
            hq_api_id=SQLTwilioBackend.get_api_id(),
            couch_id='domain_backend',
        )
        twilio_backend.set_extra_fields(
            account_sid='sid',
            auth_token='token',
        )
        twilio_backend.save()

        messages = [
            message
            for phone_number in [generator.arbitrary_phone_number() for _ in range(10)]
            for message in generator.arbitrary_messages_by_backend_and_direction(
                {twilio_backend.hq_api_id: twilio_backend.couch_id}, phone_number=phone_number
            )
        ]
        for msg_log in messages:
            FakeTwilioMessageFactory.add_price_for_message(msg_log.backend_message_id, generator.arbitrary_fee())

        for msg_log in messages:
            multipart_count = randint(1, 10)  # Should be ignored
            billable = SmsBillable.create(msg_log, multipart_count=multipart_count)
            self.assertIsNotNone(billable)
            self.assertIsNone(billable.gateway_fee)
            self.assertEqual(billable.gateway_charge, 0)

        self.assertEqual(mock_log_smsbillables_error.call_count, 0)
예제 #13
0
파일: views.py 프로젝트: bazuzi/commcare-hq
def ivr_in(request):
    if request.method == 'POST':
        from_number = request.POST.get('From')
        call_sid = request.POST.get('CallSid')
        log_call(from_number, '%s-%s' % (SQLTwilioBackend.get_api_id(), call_sid))
        return HttpResponse(IVR_RESPONSE)
    else:
        return HttpResponseBadRequest("POST Expected")
예제 #14
0
파일: utils.py 프로젝트: dimagi/commcare-hq
def _get_twilio_client(backend_instance):
    from corehq.messaging.smsbackends.twilio.models import SQLTwilioBackend

    twilio_backend = SQLMobileBackend.load(
        backend_instance, api_id=SQLTwilioBackend.get_api_id(), is_couch_id=True, include_deleted=True
    )
    config = twilio_backend.config
    return TwilioRestClient(config.account_sid, config.auth_token)
예제 #15
0
 def _get_multipart_count(cls, backend_api_id, backend_instance, backend_message_id, multipart_count):
     if backend_api_id == SQLTwilioBackend.get_api_id():
         twilio_message = get_twilio_message(backend_instance, backend_message_id)
         if twilio_message.num_segments is not None:
             return int(twilio_message.num_segments)
         else:
             raise RetryBillableTaskException("backend_message_id=%s" % backend_message_id)
     else:
         return multipart_count
예제 #16
0
 def _get_multipart_count(cls, backend_api_id, backend_instance, backend_message_id, multipart_count):
     if backend_api_id == SQLTwilioBackend.get_api_id():
         twilio_message = get_twilio_message(backend_instance, backend_message_id)
         if twilio_message.num_segments is not None:
             return int(twilio_message.num_segments)
         else:
             raise RetryBillableTaskException("backend_message_id=%s" % backend_message_id)
     else:
         return multipart_count
예제 #17
0
def _get_twilio_client(backend_instance):
    from corehq.messaging.smsbackends.twilio.models import SQLTwilioBackend
    twilio_backend = SQLMobileBackend.load(
        backend_instance,
        api_id=SQLTwilioBackend.get_api_id(),
        is_couch_id=True,
        include_deleted=True,
    )
    config = twilio_backend.config
    return Client(config.account_sid, config.auth_token)
예제 #18
0
 def post(self, request, api_key, *args, **kwargs):
     message_sid = request.POST.get('MessageSid')
     account_sid = request.POST.get('AccountSid')
     from_ = request.POST.get('From')
     to = request.POST.get('To')
     body = request.POST.get('Body')
     incoming_sms(from_,
                  body,
                  SQLTwilioBackend.get_api_id(),
                  backend_message_id=message_sid,
                  backend_id=self.backend_couch_id)
     return HttpResponse(EMPTY_RESPONSE)
예제 #19
0
 def post(self, request, api_key, *args, **kwargs):
     message_sid = request.POST.get('MessageSid')
     account_sid = request.POST.get('AccountSid')
     from_ = request.POST.get('From')
     to = request.POST.get('To')
     body = request.POST.get('Body')
     incoming_sms(
         from_,
         body,
         SQLTwilioBackend.get_api_id(),
         backend_message_id=message_sid,
         backend_id=self.backend_couch_id
     )
     return HttpResponse(EMPTY_RESPONSE)
예제 #20
0
 def setUp(self):
     self.client = Client()
     self.twilio_url = reverse(AddDomainGatewayView.urlname,
                               kwargs={
                                   'domain': DOMAIN_NAME,
                                   'hq_api_id':
                                   SQLTwilioBackend.get_api_id()
                               })
     self.telerivet_url = reverse(AddDomainGatewayView.urlname,
                                  kwargs={
                                      'domain':
                                      DOMAIN_NAME,
                                      'hq_api_id':
                                      SQLTelerivetBackend.get_api_id()
                                  })
예제 #21
0
def add_twilio_gateway_fee(apps):
    default_currency, _ = apps.get_model(
        'accounting',
        'Currency').objects.get_or_create(code=settings.DEFAULT_CURRENCY)

    for direction in [INCOMING, OUTGOING]:
        SmsGatewayFee.create_new(
            SQLTwilioBackend.get_api_id(),
            direction,
            None,
            fee_class=apps.get_model('smsbillables', 'SmsGatewayFee'),
            criteria_class=apps.get_model('smsbillables',
                                          'SmsGatewayFeeCriteria'),
            currency=default_currency,
        )
예제 #22
0
파일: views.py 프로젝트: bazuzi/commcare-hq
def sms_in(request):
    if request.method == "POST":
        message_sid = request.POST.get("MessageSid")
        account_sid = request.POST.get("AccountSid")
        from_ = request.POST.get("From")
        to = request.POST.get("To")
        body = request.POST.get("Body")
        incoming_sms(
            from_,
            body,
            SQLTwilioBackend.get_api_id(),
            backend_message_id=message_sid
        )
        return HttpResponse(EMPTY_RESPONSE)
    else:
        return HttpResponseBadRequest("POST Expected")
예제 #23
0
def add_twilio_gateway_fee(apps):
    default_currency, _ = apps.get_model(
        'accounting', 'Currency'
    ).objects.get_or_create(
        code=settings.DEFAULT_CURRENCY
    )

    for direction in [INCOMING, OUTGOING]:
        SmsGatewayFee.create_new(
            SQLTwilioBackend.get_api_id(),
            direction,
            None,
            fee_class=apps.get_model('smsbillables', 'SmsGatewayFee'),
            criteria_class=apps.get_model('smsbillables', 'SmsGatewayFeeCriteria'),
            currency=default_currency,
        )
예제 #24
0
 def _get_multipart_count(cls, backend_api_id, backend_instance, backend_message_id, multipart_count):
     if backend_api_id == SQLTwilioBackend.get_api_id():
         twilio_message = get_twilio_message(backend_instance, backend_message_id)
         if twilio_message.num_segments is not None:
             return int(twilio_message.num_segments)
         else:
             raise RetryBillableTaskException("backend_message_id=%s" % backend_message_id)
     elif backend_api_id == InfobipBackend.get_api_id():
         infobip_message = get_infobip_message(backend_instance, backend_message_id)
         segments = infobip_message['messageCount'] \
             if 'messageCount' in infobip_message else infobip_message['smsCount']
         if segments is not None:
             return int(segments)
         else:
             raise RetryBillableTaskException("backend_message_id=%s" % backend_message_id)
     else:
         return multipart_count
def bootstrap_twilio_gateway_incoming(apps):
    currency_class = apps.get_model('accounting', 'Currency') if apps else Currency
    sms_gateway_fee_class = apps.get_model('smsbillables', 'SmsGatewayFee') if apps else SmsGatewayFee
    sms_gateway_fee_criteria_class = apps.get_model('smsbillables', 'SmsGatewayFeeCriteria') if apps else SmsGatewayFeeCriteria

    # https://www.twilio.com/sms/pricing/us
    SmsGatewayFee.create_new(
        SQLTwilioBackend.get_api_id(),
        INCOMING,
        0.0075,
        country_code=None,
        currency=currency_class.objects.get(code="USD"),
        fee_class=sms_gateway_fee_class,
        criteria_class=sms_gateway_fee_criteria_class,
    )

    log_smsbillables_info("Updated INCOMING Twilio gateway fees.")
def bootstrap_twilio_gateway_incoming(apps):
    currency_class = apps.get_model('accounting',
                                    'Currency') if apps else Currency
    sms_gateway_fee_class = apps.get_model(
        'smsbillables', 'SmsGatewayFee') if apps else SmsGatewayFee
    sms_gateway_fee_criteria_class = apps.get_model(
        'smsbillables',
        'SmsGatewayFeeCriteria') if apps else SmsGatewayFeeCriteria

    # https://www.twilio.com/sms/pricing/us
    SmsGatewayFee.create_new(
        SQLTwilioBackend.get_api_id(),
        INCOMING,
        0.0075,
        country_code=None,
        currency=currency_class.objects.get(code="USD"),
        fee_class=sms_gateway_fee_class,
        criteria_class=sms_gateway_fee_criteria_class,
    )

    log_smsbillables_info("Updated INCOMING Twilio gateway fees.")
예제 #27
0
 def _get_twilio_charges(cls, backend_message_id, backend_instance, direction, couch_id):
     if backend_message_id:
         twilio_message = get_twilio_message(backend_instance, backend_message_id)
         if twilio_message.status in [
             'accepted',
             'queued',
             'sending',
             'receiving',
         ] or twilio_message.price is None:
             raise RetryBillableTaskException("backend_message_id=%s" % backend_message_id)
         return _TwilioChargeInfo(
             Decimal(twilio_message.price) * -1,
             SmsGatewayFee.get_by_criteria(
                 SQLTwilioBackend.get_api_id(),
                 direction,
             )
         )
     else:
         log_smsbillables_error(
             "Could not create gateway fee for Twilio message %s: no backend_message_id" % couch_id
         )
         return _TwilioChargeInfo(None, None)
예제 #28
0
    def _get_gateway_fee(cls, backend_api_id, backend_instance, phone_number,
                         direction, couch_id, backend_message_id, domain):
        country_code, national_number = get_country_code_and_national_number(
            phone_number)
        is_gateway_billable = backend_instance is None or _sms_backend_is_global(
            backend_instance
        ) or toggles.ENABLE_INCLUDE_SMS_GATEWAY_CHARGING.enabled(domain)

        if is_gateway_billable:
            is_twilio_message = backend_api_id == SQLTwilioBackend.get_api_id()
            if is_twilio_message:
                twilio_charges = cls._get_twilio_charges(
                    backend_message_id, backend_instance, direction, couch_id)
                gateway_fee = twilio_charges.gateway_fee
                direct_gateway_fee = twilio_charges.twilio_gateway_fee
            else:
                gateway_fee = SmsGatewayFee.get_by_criteria(
                    backend_api_id,
                    direction,
                    backend_instance=backend_instance,
                    country_code=country_code,
                    national_number=national_number,
                )
                direct_gateway_fee = None
            if gateway_fee:
                conversion_rate = gateway_fee.currency.rate_to_default
                if conversion_rate != 0:
                    return _GatewayChargeInfo(gateway_fee, conversion_rate,
                                              direct_gateway_fee)
                else:
                    log_smsbillables_error(
                        "Gateway fee conversion rate for currency %s is 0" %
                        gateway_fee.currency.code)
                    return _GatewayChargeInfo(gateway_fee, None,
                                              direct_gateway_fee)
            else:
                log_smsbillables_error(
                    "No matching gateway fee criteria for SMS %s" % couch_id)
        return _GatewayChargeInfo(None, None, None)
예제 #29
0
 def _get_twilio_charges(cls, backend_message_id, backend_instance, direction, couch_id):
     if backend_message_id:
         twilio_message = get_twilio_message(backend_instance, backend_message_id)
         if twilio_message.status in [
             'accepted',
             'queued',
             'sending',
             'receiving',
         ] or twilio_message.price is None:
             raise RetryBillableTaskException("backend_message_id=%s" % backend_message_id)
         return _TwilioChargeInfo(
             Decimal(twilio_message.price) * -1,
             SmsGatewayFee.get_by_criteria(
                 SQLTwilioBackend.get_api_id(),
                 direction,
             )
         )
     else:
         log_smsbillables_error(
             "Could not create gateway fee for Twilio message %s: no backend_message_id" % couch_id
         )
         return _TwilioChargeInfo(None, None)
예제 #30
0
    def _get_gateway_fee(cls, backend_api_id, backend_instance,
                         phone_number, direction, couch_id, backend_message_id, domain):
        country_code, national_number = get_country_code_and_national_number(phone_number)
        is_gateway_billable = backend_instance is None or _sms_backend_is_global(
            backend_instance) or toggles.ENABLE_INCLUDE_SMS_GATEWAY_CHARGING.enabled(domain)

        if is_gateway_billable:
            is_twilio_message = backend_api_id == SQLTwilioBackend.get_api_id()
            if is_twilio_message:
                twilio_charges = cls._get_twilio_charges(
                    backend_message_id, backend_instance, direction, couch_id
                )
                gateway_fee = twilio_charges.gateway_fee
                direct_gateway_fee = twilio_charges.twilio_gateway_fee
            else:
                gateway_fee = SmsGatewayFee.get_by_criteria(
                    backend_api_id,
                    direction,
                    backend_instance=backend_instance,
                    country_code=country_code,
                    national_number=national_number,
                )
                direct_gateway_fee = None
            if gateway_fee:
                conversion_rate = gateway_fee.currency.rate_to_default
                if conversion_rate != 0:
                    return _GatewayChargeInfo(gateway_fee, conversion_rate, direct_gateway_fee)
                else:
                    log_smsbillables_error(
                        "Gateway fee conversion rate for currency %s is 0"
                        % gateway_fee.currency.code
                    )
                    return _GatewayChargeInfo(gateway_fee, None, direct_gateway_fee)
            else:
                log_smsbillables_error(
                    "No matching gateway fee criteria for SMS %s" % couch_id
                )
        return _GatewayChargeInfo(None, None, None)
예제 #31
0
 def post(self, request, api_key, *args, **kwargs):
     from_number = request.POST.get('From')
     call_sid = request.POST.get('CallSid')
     log_call(from_number, '%s-%s' % (SQLTwilioBackend.get_api_id(), call_sid))
     return HttpResponse(IVR_RESPONSE)
예제 #32
0
def _available_gateway_fee_backends():
    return filter(
        lambda backend: backend.get_api_id() != SQLTwilioBackend.get_api_id(),
        get_sms_backend_classes().values())
예제 #33
0
class AllBackendTest(BaseSMSTest):
    def setUp(self):
        super(AllBackendTest, self).setUp()

        self.domain_obj = Domain(name='all-backend-test')
        self.domain_obj.save()
        self.create_account_and_subscription(self.domain_obj.name)
        self.domain_obj = Domain.get(self.domain_obj._id)

        self.test_phone_number = '99912345'
        self.contact1 = CommCareCase(domain=self.domain_obj.name)
        self.contact1.set_case_property('contact_phone_number', self.test_phone_number)
        self.contact1.set_case_property('contact_phone_number_is_verified', '1')
        self.contact1.save()
        self.contact1 = CommConnectCase.wrap(self.contact1.to_json())

        # For use with megamobile only
        self.contact2 = CommCareCase(domain=self.domain_obj.name)
        self.contact2.set_case_property('contact_phone_number', '63%s' % self.test_phone_number)
        self.contact2.set_case_property('contact_phone_number_is_verified', '1')
        self.contact2.save()
        self.contact2 = CommConnectCase.wrap(self.contact2.to_json())

        self.unicel_backend = SQLUnicelBackend(
            name='UNICEL',
            is_global=True,
            hq_api_id=SQLUnicelBackend.get_api_id()
        )
        self.unicel_backend.save()

        self.mach_backend = SQLMachBackend(
            name='MACH',
            is_global=True,
            hq_api_id=SQLMachBackend.get_api_id()
        )
        self.mach_backend.save()

        self.tropo_backend = SQLTropoBackend(
            name='TROPO',
            is_global=True,
            hq_api_id=SQLTropoBackend.get_api_id()
        )
        self.tropo_backend.save()

        self.http_backend = SQLHttpBackend(
            name='HTTP',
            is_global=True,
            hq_api_id=SQLHttpBackend.get_api_id()
        )
        self.http_backend.save()

        self.telerivet_backend = SQLTelerivetBackend(
            name='TELERIVET',
            is_global=True,
            hq_api_id=SQLTelerivetBackend.get_api_id()
        )
        self.telerivet_backend.set_extra_fields(
            **dict(
                webhook_secret='telerivet-webhook-secret'
            )
        )
        self.telerivet_backend.save()

        self.test_backend = SQLTestSMSBackend(
            name='TEST',
            is_global=True,
            hq_api_id=SQLTestSMSBackend.get_api_id()
        )
        self.test_backend.save()

        self.grapevine_backend = SQLGrapevineBackend(
            name='GRAPEVINE',
            is_global=True,
            hq_api_id=SQLGrapevineBackend.get_api_id()
        )
        self.grapevine_backend.save()

        self.twilio_backend = SQLTwilioBackend(
            name='TWILIO',
            is_global=True,
            hq_api_id=SQLTwilioBackend.get_api_id()
        )
        self.twilio_backend.save()

        self.megamobile_backend = SQLMegamobileBackend(
            name='MEGAMOBILE',
            is_global=True,
            hq_api_id=SQLMegamobileBackend.get_api_id()
        )
        self.megamobile_backend.save()

        self.smsgh_backend = SQLSMSGHBackend(
            name='SMSGH',
            is_global=True,
            hq_api_id=SQLSMSGHBackend.get_api_id()
        )
        self.smsgh_backend.save()

        self.apposit_backend = SQLAppositBackend(
            name='APPOSIT',
            is_global=True,
            hq_api_id=SQLAppositBackend.get_api_id()
        )
        self.apposit_backend.save()

        self.sislog_backend = SQLSislogBackend(
            name='SISLOG',
            is_global=True,
            hq_api_id=SQLSislogBackend.get_api_id()
        )
        self.sislog_backend.save()

        self.yo_backend = SQLYoBackend(
            name='YO',
            is_global=True,
            hq_api_id=SQLYoBackend.get_api_id()
        )
        self.yo_backend.save()

    def _test_outbound_backend(self, backend, msg_text, mock_send):
        SQLMobileBackendMapping.set_default_domain_backend(self.domain_obj.name, backend)

        send_sms(self.domain_obj.name, None, self.test_phone_number, msg_text)
        sms = SMS.objects.get(
            domain=self.domain_obj.name,
            direction='O',
            text=msg_text
        )

        self.assertTrue(mock_send.called)
        msg_arg = mock_send.call_args[0][0]
        self.assertEqual(msg_arg.date, sms.date)
        self.assertEqual(sms.backend_api, backend.hq_api_id)
        self.assertEqual(sms.backend_id, backend.couch_id)

    def _verify_inbound_request(self, backend_api_id, msg_text, backend_couch_id=None):
        sms = SMS.objects.get(
            domain=self.domain_obj.name,
            direction='I',
            text=msg_text
        )
        self.assertEqual(sms.backend_api, backend_api_id)
        if backend_couch_id:
            self.assertEqual(sms.backend_id, backend_couch_id)

    def _simulate_inbound_request_with_payload(self, url,
            content_type, payload):
        response = Client().post(url, payload, content_type=content_type)
        self.assertEqual(response.status_code, 200)

    def _simulate_inbound_request(self, url, phone_param,
            msg_param, msg_text, post=False, additional_params=None,
            expected_response_code=200):
        fcn = Client().post if post else Client().get

        payload = {
            phone_param: self.test_phone_number,
            msg_param: msg_text,
        }

        if additional_params:
            payload.update(additional_params)

        response = fcn(url, payload)
        self.assertEqual(response.status_code, expected_response_code)

    @patch('corehq.messaging.smsbackends.unicel.models.SQLUnicelBackend.send')
    @patch('corehq.messaging.smsbackends.mach.models.SQLMachBackend.send')
    @patch('corehq.messaging.smsbackends.tropo.models.SQLTropoBackend.send')
    @patch('corehq.messaging.smsbackends.http.models.SQLHttpBackend.send')
    @patch('corehq.messaging.smsbackends.telerivet.models.SQLTelerivetBackend.send')
    @patch('corehq.messaging.smsbackends.test.models.SQLTestSMSBackend.send')
    @patch('corehq.messaging.smsbackends.grapevine.models.SQLGrapevineBackend.send')
    @patch('corehq.messaging.smsbackends.twilio.models.SQLTwilioBackend.send')
    @patch('corehq.messaging.smsbackends.megamobile.models.SQLMegamobileBackend.send')
    @patch('corehq.messaging.smsbackends.smsgh.models.SQLSMSGHBackend.send')
    @patch('corehq.messaging.smsbackends.apposit.models.SQLAppositBackend.send')
    @patch('corehq.messaging.smsbackends.sislog.models.SQLSislogBackend.send')
    @patch('corehq.messaging.smsbackends.yo.models.SQLYoBackend.send')
    def test_outbound_sms(
            self,
            yo_send,
            sislog_send,
            apposit_send,
            smsgh_send,
            megamobile_send,
            twilio_send,
            grapevine_send,
            test_send,
            telerivet_send,
            http_send,
            tropo_send,
            mach_send,
            unicel_send):
        self._test_outbound_backend(self.unicel_backend, 'unicel test', unicel_send)
        self._test_outbound_backend(self.mach_backend, 'mach test', mach_send)
        self._test_outbound_backend(self.tropo_backend, 'tropo test', tropo_send)
        self._test_outbound_backend(self.http_backend, 'http test', http_send)
        self._test_outbound_backend(self.telerivet_backend, 'telerivet test', telerivet_send)
        self._test_outbound_backend(self.test_backend, 'test test', test_send)
        self._test_outbound_backend(self.grapevine_backend, 'grapevine test', grapevine_send)
        self._test_outbound_backend(self.twilio_backend, 'twilio test', twilio_send)
        self._test_outbound_backend(self.megamobile_backend, 'megamobile test', megamobile_send)
        self._test_outbound_backend(self.smsgh_backend, 'smsgh test', smsgh_send)
        self._test_outbound_backend(self.apposit_backend, 'apposit test', apposit_send)
        self._test_outbound_backend(self.sislog_backend, 'sislog test', sislog_send)
        self._test_outbound_backend(self.yo_backend, 'yo test', yo_send)

    def test_unicel_inbound_sms(self):
        self._simulate_inbound_request('/unicel/in/', phone_param=InboundParams.SENDER,
            msg_param=InboundParams.MESSAGE, msg_text='unicel test')

        self._verify_inbound_request(self.unicel_backend.get_api_id(), 'unicel test')

    def test_tropo_inbound_sms(self):
        tropo_data = {'session': {'from': {'id': self.test_phone_number}, 'initialText': 'tropo test'}}
        self._simulate_inbound_request_with_payload('/tropo/sms/',
            content_type='text/json', payload=json.dumps(tropo_data))

        self._verify_inbound_request(self.tropo_backend.get_api_id(), 'tropo test')

    def test_telerivet_inbound_sms(self):
        additional_params = {
            'event': 'incoming_message',
            'message_type': 'sms',
            'secret': self.telerivet_backend.config.webhook_secret
        }
        self._simulate_inbound_request('/telerivet/in/', phone_param='from_number_e164',
            msg_param='content', msg_text='telerivet test', post=True,
            additional_params=additional_params)

        self._verify_inbound_request(self.telerivet_backend.get_api_id(), 'telerivet test')

    @override_settings(SIMPLE_API_KEYS={'grapevine-test': 'grapevine-api-key'})
    def test_grapevine_inbound_sms(self):
        xml = """
        <gviSms>
            <smsDateTime>2015-10-12T12:00:00</smsDateTime>
            <cellNumber>99912345</cellNumber>
            <content>grapevine test</content>
        </gviSms>
        """
        payload = urlencode({'XML': xml})
        self._simulate_inbound_request_with_payload(
            '/gvi/api/sms/?apiuser=grapevine-test&apikey=grapevine-api-key',
            content_type='application/x-www-form-urlencoded', payload=payload)

        self._verify_inbound_request(self.grapevine_backend.get_api_id(), 'grapevine test')

    def test_twilio_inbound_sms(self):
        url = '/twilio/sms/%s' % self.twilio_backend.inbound_api_key
        self._simulate_inbound_request(url, phone_param='From',
            msg_param='Body', msg_text='twilio test', post=True)

        self._verify_inbound_request(self.twilio_backend.get_api_id(), 'twilio test',
            backend_couch_id=self.twilio_backend.couch_id)

    def test_twilio_401_response(self):
        start_count = SMS.objects.count()

        self._simulate_inbound_request('/twilio/sms/xxxxx', phone_param='From',
            msg_param='Body', msg_text='twilio test', post=True,
            expected_response_code=401)

        end_count = SMS.objects.count()

        self.assertEqual(start_count, end_count)

    def test_megamobile_inbound_sms(self):
        self._simulate_inbound_request('/megamobile/sms/', phone_param='cel',
            msg_param='msg', msg_text='megamobile test')

        self._verify_inbound_request(self.megamobile_backend.get_api_id(), 'megamobile test')

    def test_sislog_inbound_sms(self):
        self._simulate_inbound_request('/sislog/in/', phone_param='sender',
            msg_param='msgdata', msg_text='sislog test')

        self._verify_inbound_request(self.sislog_backend.get_api_id(), 'sislog test')

    def test_yo_inbound_sms(self):
        self._simulate_inbound_request('/yo/sms/', phone_param='sender',
            msg_param='message', msg_text='yo test')

        self._verify_inbound_request(self.yo_backend.get_api_id(), 'yo test')

    def test_smsgh_inbound_sms(self):
        user = ApiUser.create('smsgh-api-key', 'smsgh-api-key', permissions=[PERMISSION_POST_SMS])
        user.save()

        self._simulate_inbound_request('/smsgh/sms/smsgh-api-key/', phone_param='snr',
            msg_param='msg', msg_text='smsgh test')

        self._verify_inbound_request('SMSGH', 'smsgh test')

        user.delete()

    def test_apposit_inbound_sms(self):
        user = ApiUser.create('apposit-api-key', 'apposit-api-key', permissions=[PERMISSION_POST_SMS])
        user.save()

        self._simulate_inbound_request(
            '/apposit/in/apposit-api-key/',
            phone_param='fromAddress',
            msg_param='content',
            msg_text='apposit test',
            post=True,
            additional_params={'channel': 'SMS'}
        )
        self._verify_inbound_request('APPOSIT', 'apposit test')

        user.delete()

    def tearDown(self):
        delete_domain_phone_numbers(self.domain_obj.name)
        self.contact1.delete()
        self.contact2.delete()
        self.domain_obj.delete()
        self.unicel_backend.delete()
        self.mach_backend.delete()
        self.tropo_backend.delete()
        self.http_backend.delete()
        self.telerivet_backend.delete()
        self.test_backend.delete()
        self.grapevine_backend.delete()
        self.twilio_backend.delete()
        self.megamobile_backend.delete()
        self.smsgh_backend.delete()
        self.apposit_backend.delete()
        self.sislog_backend.delete()
        self.yo_backend.delete()
        super(AllBackendTest, self).tearDown()
예제 #34
0
    def setUp(self):
        super(AllBackendTest, self).setUp()

        self.domain_obj = Domain(name='all-backend-test')
        self.domain_obj.save()
        self.create_account_and_subscription(self.domain_obj.name)
        self.domain_obj = Domain.get(self.domain_obj._id)

        self.test_phone_number = '99912345'
        self.contact1 = CommCareCase(domain=self.domain_obj.name)
        self.contact1.set_case_property('contact_phone_number', self.test_phone_number)
        self.contact1.set_case_property('contact_phone_number_is_verified', '1')
        self.contact1.save()
        self.contact1 = CommConnectCase.wrap(self.contact1.to_json())

        # For use with megamobile only
        self.contact2 = CommCareCase(domain=self.domain_obj.name)
        self.contact2.set_case_property('contact_phone_number', '63%s' % self.test_phone_number)
        self.contact2.set_case_property('contact_phone_number_is_verified', '1')
        self.contact2.save()
        self.contact2 = CommConnectCase.wrap(self.contact2.to_json())

        self.unicel_backend = SQLUnicelBackend(
            name='UNICEL',
            is_global=True,
            hq_api_id=SQLUnicelBackend.get_api_id()
        )
        self.unicel_backend.save()

        self.mach_backend = SQLMachBackend(
            name='MACH',
            is_global=True,
            hq_api_id=SQLMachBackend.get_api_id()
        )
        self.mach_backend.save()

        self.tropo_backend = SQLTropoBackend(
            name='TROPO',
            is_global=True,
            hq_api_id=SQLTropoBackend.get_api_id()
        )
        self.tropo_backend.save()

        self.http_backend = SQLHttpBackend(
            name='HTTP',
            is_global=True,
            hq_api_id=SQLHttpBackend.get_api_id()
        )
        self.http_backend.save()

        self.telerivet_backend = SQLTelerivetBackend(
            name='TELERIVET',
            is_global=True,
            hq_api_id=SQLTelerivetBackend.get_api_id()
        )
        self.telerivet_backend.set_extra_fields(
            **dict(
                webhook_secret='telerivet-webhook-secret'
            )
        )
        self.telerivet_backend.save()

        self.test_backend = SQLTestSMSBackend(
            name='TEST',
            is_global=True,
            hq_api_id=SQLTestSMSBackend.get_api_id()
        )
        self.test_backend.save()

        self.grapevine_backend = SQLGrapevineBackend(
            name='GRAPEVINE',
            is_global=True,
            hq_api_id=SQLGrapevineBackend.get_api_id()
        )
        self.grapevine_backend.save()

        self.twilio_backend = SQLTwilioBackend(
            name='TWILIO',
            is_global=True,
            hq_api_id=SQLTwilioBackend.get_api_id()
        )
        self.twilio_backend.save()

        self.megamobile_backend = SQLMegamobileBackend(
            name='MEGAMOBILE',
            is_global=True,
            hq_api_id=SQLMegamobileBackend.get_api_id()
        )
        self.megamobile_backend.save()

        self.smsgh_backend = SQLSMSGHBackend(
            name='SMSGH',
            is_global=True,
            hq_api_id=SQLSMSGHBackend.get_api_id()
        )
        self.smsgh_backend.save()

        self.apposit_backend = SQLAppositBackend(
            name='APPOSIT',
            is_global=True,
            hq_api_id=SQLAppositBackend.get_api_id()
        )
        self.apposit_backend.save()

        self.sislog_backend = SQLSislogBackend(
            name='SISLOG',
            is_global=True,
            hq_api_id=SQLSislogBackend.get_api_id()
        )
        self.sislog_backend.save()

        self.yo_backend = SQLYoBackend(
            name='YO',
            is_global=True,
            hq_api_id=SQLYoBackend.get_api_id()
        )
        self.yo_backend.save()
예제 #35
0
def _available_gateway_fee_backends():
    return filter(
        lambda backend: backend.get_api_id() != SQLTwilioBackend.get_api_id(),
        get_sms_backend_classes().values()
    )
예제 #36
0
 def post(self, request, api_key, *args, **kwargs):
     from_number = request.POST.get('From')
     call_sid = request.POST.get('CallSid')
     log_call(from_number,
              '%s-%s' % (SQLTwilioBackend.get_api_id(), call_sid))
     return HttpResponse(IVR_RESPONSE)
예제 #37
0
 def validate_phone_number(self, phone_number: str) -> None:
     from corehq.messaging.smsbackends.twilio.models import SQLTwilioBackend
     if not SQLTwilioBackend.phone_number_is_messaging_service_sid(
             phone_number):
         super().validate_phone_number(phone_number)
예제 #38
0
def bootstrap_twilio_gateway(apps, twilio_rates_filename):
    currency_class = apps.get_model('accounting', 'Currency') if apps else Currency
    sms_gateway_fee_class = apps.get_model('smsbillables', 'SmsGatewayFee') if apps else SmsGatewayFee
    sms_gateway_fee_criteria_class = apps.get_model('smsbillables', 'SmsGatewayFeeCriteria') if apps else SmsGatewayFeeCriteria

    # iso -> provider -> rate
    def get_twilio_data():
        twilio_file = open(twilio_rates_filename)
        twilio_csv = csv.reader(twilio_file.read().splitlines())
        twilio_data = {}
        skip = 0
        for row in twilio_csv:
            if skip < 4:
                skip += 1
                continue
            else:
                try:
                    iso = row[0].lower()
                    provider = row[2].split('-')[1].lower().replace(' ', '')
                    rate = float(row[3])
                    if not(iso in twilio_data):
                        twilio_data[iso] = {}
                    twilio_data[iso][provider] = rate
                except IndexError:
                    log_smsbillables_info("Twilio index error %s:" % row)
        twilio_file.close()
        return twilio_data

    # iso -> provider -> (country code, number of subscribers)
    def get_mach_data():
        mach_workbook = xlrd.open_workbook('corehq/apps/smsbillables/management/'
                                           'commands/pricing_data/Syniverse_coverage_list_DIAMONDplus.xls')
        mach_table = mach_workbook.sheet_by_index(0)
        mach_data = {}
        try:
            row = 7
            while True:
                country_code = int(mach_table.cell_value(row, 0))
                iso = mach_table.cell_value(row, 1)
                network = mach_table.cell_value(row, 5).lower().replace(' ', '')
                subscribers = 0
                try:
                    subscribers = int(mach_table.cell_value(row, 10).replace('.', ''))
                except ValueError:
                    log_smsbillables_info("Incomplete subscriber data for country code %d" % country_code)
                if not(iso in mach_data):
                    mach_data[iso] = {}
                mach_data[iso][network] = (country_code, subscribers)
                row += 1
        except IndexError:
            pass
        return mach_data

    twilio_data = get_twilio_data()
    mach_data = get_mach_data()

    for iso in twilio_data:
        if iso in mach_data:
            weighted_price = 0
            total_subscriptions = 0
            country_code = None
            calculate_other = False
            for twilio_provider in twilio_data[iso]:
                if twilio_provider == 'other':
                    calculate_other = True
                else:
                    for mach_provider in mach_data[iso]:
                        try:
                            if twilio_provider in mach_provider:
                                country_code, subscriptions = mach_data[iso][mach_provider]
                                weighted_price += twilio_data[iso][twilio_provider] * subscriptions
                                total_subscriptions += subscriptions
                                mach_data[iso][mach_provider] = country_code, 0
                                break
                        except UnicodeDecodeError:
                            pass
            if calculate_other:
                other_rate_twilio = twilio_data[iso]['other']
                for _, subscriptions in mach_data[iso].values():
                    weighted_price += other_rate_twilio * subscriptions
                    total_subscriptions += subscriptions
            if country_code is not None:
                weighted_price = weighted_price / total_subscriptions
                SmsGatewayFee.create_new(
                    SQLTwilioBackend.get_api_id(),
                    OUTGOING,
                    weighted_price,
                    country_code=country_code,
                    currency=currency_class.objects.get(code="USD"),
                    fee_class=sms_gateway_fee_class,
                    criteria_class=sms_gateway_fee_criteria_class,
                )
        else:
            log_smsbillables_info("%s not in mach_data" % iso)

    # https://www.twilio.com/help/faq/sms/will-i-be-charged-if-twilio-encounters-an-error-when-sending-an-sms
    SmsGatewayFee.create_new(
        SQLTwilioBackend.get_api_id(),
        OUTGOING,
        0.00,
        country_code=None,
        currency=currency_class.objects.get(code="USD"),
        fee_class=sms_gateway_fee_class,
        criteria_class=sms_gateway_fee_criteria_class,
    )

    log_smsbillables_info("Updated Twilio gateway fees.")
예제 #39
0
def _available_gateway_fee_backends():
    return [
        backend for backend in get_sms_backend_classes().values()
        if backend.get_api_id() != SQLTwilioBackend.get_api_id()
    ]