Exemplo n.º 1
0
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,
            TwilioBackend.get_api_id(),
            backend_message_id=message_sid
        )
        return HttpResponse(EMPTY_RESPONSE)
    else:
        return HttpResponseBadRequest("POST Expected")
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(
        TwilioBackend.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,
    )

    logger.info("Updated INCOMING Twilio gateway fees.")
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:
                    logger.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:
                    logger.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(
                    TwilioBackend.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:
            logger.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(
        TwilioBackend.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,
    )

    logger.info("Updated Twilio gateway fees.")
Exemplo n.º 4
0
from corehq.apps.ivr.api import log_call
from corehq.apps.sms.api import incoming as incoming_sms
from corehq.messaging.smsbackends.twilio.models import TwilioBackend
from django.http import HttpResponse, HttpResponseBadRequest
from django.views.decorators.csrf import csrf_exempt

EMPTY_RESPONSE = """<?xml version="1.0" encoding="UTF-8" ?>
<Response></Response>"""

IVR_RESPONSE = """<?xml version="1.0" encoding="UTF-8" ?>
<Response>
    <Pause length="30" />
    <Reject />
</Response>"""

API_ID = TwilioBackend.get_api_id()


@csrf_exempt
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,
            TwilioBackend.get_api_id(),
            backend_message_id=message_sid
Exemplo n.º 5
0
class AllBackendTest(BaseSMSTest):
    def setUp(self):
        super(AllBackendTest, self).setUp()
        backend_api.TEST = True

        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 = UnicelBackend(name='UNICEL', is_global=True)
        self.unicel_backend.save()

        self.mach_backend = MachBackend(name='MACH', is_global=True)
        self.mach_backend.save()

        self.tropo_backend = TropoBackend(name='TROPO', is_global=True)
        self.tropo_backend.save()

        self.http_backend = HttpBackend(name='HTTP', is_global=True)
        self.http_backend.save()

        self.telerivet_backend = TelerivetBackend(name='TELERIVET', is_global=True,
            webhook_secret='telerivet-webhook-secret')
        self.telerivet_backend.save()

        self.test_backend = TestSMSBackend(name='TEST', is_global=True)
        self.test_backend.save()

        self.grapevine_backend = GrapevineBackend(name='GRAPEVINE', is_global=True)
        self.grapevine_backend.save()

        self.twilio_backend = TwilioBackend(name='TWILIO', is_global=True)
        self.twilio_backend.save()

        self.megamobile_backend = MegamobileBackend(name='MEGAMOBILE', is_global=True)
        self.megamobile_backend.save()

        self.smsgh_backend = SMSGHBackend(name='SMSGH', is_global=True)
        self.smsgh_backend.save()

        self.apposit_backend = AppositBackend(name='APPOSIT', is_global=True)
        self.apposit_backend.save()

    def _test_outbound_backend(self, backend, msg_text, mock_send):
        self.domain_obj.default_sms_backend_id = backend._id
        self.domain_obj.save()

        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)

    def _verify_inbound_request(self, backend_api_id, msg_text):
        sms = SMS.objects.get(
            domain=self.domain_obj.name,
            direction='I',
            text=msg_text
        )
        self.assertEqual(sms.backend_api, backend_api_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):
        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, 200)

    @patch('corehq.messaging.smsbackends.unicel.api.UnicelBackend.send')
    @patch('corehq.messaging.smsbackends.mach.api.MachBackend.send')
    @patch('corehq.messaging.smsbackends.tropo.api.TropoBackend.send')
    @patch('corehq.messaging.smsbackends.http.api.HttpBackend.send')
    @patch('corehq.messaging.smsbackends.telerivet.models.TelerivetBackend.send')
    @patch('corehq.messaging.smsbackends.test.api.TestSMSBackend.send')
    @patch('corehq.messaging.smsbackends.grapevine.api.GrapevineBackend.send')
    @patch('corehq.messaging.smsbackends.twilio.models.TwilioBackend.send')
    @patch('corehq.messaging.smsbackends.megamobile.api.MegamobileBackend.send')
    @patch('corehq.messaging.smsbackends.smsgh.models.SMSGHBackend.send')
    @patch('corehq.messaging.smsbackends.apposit.models.AppositBackend.send')
    def test_outbound_sms(
            self,
            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)

    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.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):
        self._simulate_inbound_request('/twilio/sms/', phone_param='From',
            msg_param='Body', msg_text='twilio test', post=True)

        self._verify_inbound_request(self.twilio_backend.get_api_id(), 'twilio test')

    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('SISLOG', '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('YO', '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):
        backend_api.TEST = False
        self.contact1.get_verified_number().delete()
        self.contact1.delete()
        self.contact2.get_verified_number().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()
        super(AllBackendTest, self).tearDown()