Example #1
0
 def test_invalid_freq_type(self):
     with self.assertRaises(ValueError):
         next_transaction_datetime(
             started_at=datetime.datetime.utcnow(),
             frequency=999,
             period=0,
             interval=1,
         )
Example #2
0
 def test_invalid_freq_type(self):
     from billy.models.schedule import next_transaction_datetime
     with self.assertRaises(ValueError):
         next_transaction_datetime(
             started_at=datetime.datetime.utcnow(),
             frequency=999,
             period=0,
             interval=1,
         )
Example #3
0
 def test_invalid_freq_type(self):
     from billy.models.schedule import next_transaction_datetime
     with self.assertRaises(ValueError):
         next_transaction_datetime(
             started_at=datetime.datetime.utcnow(), 
             frequency=999, 
             period=0,
             interval=1, 
         )
 def test_invalid_interval(self):
     with self.assertRaises(ValueError):
         next_transaction_datetime(
             started_at=utc_now(),
             frequency=self.plan_model.frequencies.DAILY,
             period=0,
             interval=0,
         )
     with self.assertRaises(ValueError):
         next_transaction_datetime(
             started_at=utc_now(),
             frequency=self.plan_model.frequencies.DAILY,
             period=0,
             interval=-1,
         )
Example #5
0
 def test_invalid_interval(self):
     with self.assertRaises(ValueError):
         next_transaction_datetime(
             started_at=datetime.datetime.utcnow(),
             frequency=self.plan_model.FREQ_DAILY,
             period=0,
             interval=0,
         )
     with self.assertRaises(ValueError):
         next_transaction_datetime(
             started_at=datetime.datetime.utcnow(),
             frequency=self.plan_model.FREQ_DAILY,
             period=0,
             interval=-1,
         )
Example #6
0
 def test_invalid_interval(self):
     from billy.models.plan import PlanModel
     from billy.models.schedule import next_transaction_datetime
     with self.assertRaises(ValueError):
         next_transaction_datetime(
             started_at=datetime.datetime.utcnow(),
             frequency=PlanModel.FREQ_DAILY,
             period=0,
             interval=0,
         )
     with self.assertRaises(ValueError):
         next_transaction_datetime(
             started_at=datetime.datetime.utcnow(),
             frequency=PlanModel.FREQ_DAILY,
             period=0,
             interval=-1,
         )
Example #7
0
 def test_invalid_interval(self):
     from billy.models.plan import PlanModel
     from billy.models.schedule import next_transaction_datetime
     with self.assertRaises(ValueError):
         next_transaction_datetime(
             started_at=datetime.datetime.utcnow(), 
             frequency=PlanModel.FREQ_DAILY, 
             period=0,
             interval=0, 
         )
     with self.assertRaises(ValueError):
         next_transaction_datetime(
             started_at=datetime.datetime.utcnow(), 
             frequency=PlanModel.FREQ_DAILY, 
             period=0,
             interval=-1, 
         )
 def assert_next_day(now_dt, expected):
     with freeze_time(now_dt):
         now = utc_now()
         next_dt = next_transaction_datetime(
             started_at=now,
             frequency=self.plan_model.frequencies.DAILY,
             period=1,
         )
         self.assertEqual(next_dt, expected)
Example #9
0
 def assert_next_day(now_dt, expected):
     with freeze_time(now_dt):
         now = datetime.datetime.utcnow()
         next_dt = next_transaction_datetime(
             started_at=now,
             frequency=self.plan_model.FREQ_DAILY,
             period=1,
         )
         self.assertEqual(next_dt, expected)
Example #10
0
 def assert_next_day(now_dt, expected):
     with freeze_time(now_dt):
         now = datetime.datetime.utcnow()
         next_dt = next_transaction_datetime(
             started_at=now,
             frequency=PlanModel.FREQ_DAILY,
             period=1,
         )
         self.assertEqual(next_dt, expected)
Example #11
0
 def assert_schedule(self, started_at, frequency, interval, length, expected):
     result = []
     for period in range(length):
         dt = next_transaction_datetime(
             started_at=started_at,
             frequency=frequency,
             period=period,
             interval=interval,
         )
         result.append(dt)
     self.assertEqual(result, expected)
 def assert_schedule(self, started_at, frequency, interval, length,
                     expected):
     result = []
     for period in range(length):
         dt = next_transaction_datetime(
             started_at=started_at,
             frequency=frequency,
             period=period,
             interval=interval,
         )
         result.append(dt)
     self.assertEqual(result, expected)
Example #13
0
    def yield_transactions(self, subscription_guids=None, now=None):
        """Generate new necessary transactions according to subscriptions we 
        had return guid list

        :param subscription_guids: A list subscription guid to yield 
            transaction_type from, if None is given, all subscriptions
            in the database will be the yielding source
        :param now: the current date time to use, now_func() will be used by 
            default
        :return: a generated transaction guid list
        """
        from sqlalchemy.sql.expression import not_

        if now is None:
            now = tables.now_func()

        tx_model = TransactionModel(self.session)
        Subscription = tables.Subscription

        transaction_guids = []

        # as we may have multiple new transactions for one subscription to
        # process, for example, we didn't run this method for a long while,
        # in this case, we need to make sure all transactions are yielded
        while True:
            # find subscriptions which should yield new transactions
            query = (
                self.session.query(Subscription)
                .filter(Subscription.next_transaction_at <= now)
                .filter(not_(Subscription.canceled))
            )
            if subscription_guids is not None:
                query = query.filter(Subscription.guid.in_(subscription_guids))
            subscriptions = query.all()

            # okay, we have no more transaction to process, just break
            if not subscriptions:
                self.logger.info('No more subscriptions to process')
                break

            for subscription in subscriptions:
                if subscription.plan.plan_type == PlanModel.TYPE_CHARGE:
                    transaction_type = tx_model.TYPE_CHARGE
                elif subscription.plan.plan_type == PlanModel.TYPE_PAYOUT:
                    transaction_type = tx_model.TYPE_PAYOUT
                else:
                    raise ValueError('Unknown plan type {} to process'
                                     .format(subscription.plan.plan_type))
                # when amount of subscription is given, we should use it
                # instead the one from plan
                if subscription.amount is None:
                    amount = subscription.plan.amount 
                else:
                    amount = subscription.amount
                type_map = {
                    tx_model.TYPE_CHARGE: 'charge',
                    tx_model.TYPE_PAYOUT: 'payout',
                }
                self.logger.debug(
                    'Creating transaction for %s, transaction_type=%s, '
                    'payment_uri=%s, amount=%s, scheduled_at=%s, period=%s', 
                    subscription.guid, 
                    type_map[transaction_type],
                    subscription.payment_uri,
                    amount,
                    subscription.next_transaction_at, 
                    subscription.period, 
                )
                # create the new transaction for this subscription
                guid = tx_model.create(
                    subscription_guid=subscription.guid, 
                    payment_uri=subscription.payment_uri, 
                    amount=amount, 
                    transaction_type=transaction_type, 
                    scheduled_at=subscription.next_transaction_at, 
                )
                self.logger.info(
                    'Created transaction for %s, guid=%s, transaction_type=%s, '
                    'payment_uri=%s, amount=%s, scheduled_at=%s, period=%s', 
                    subscription.guid, 
                    guid,
                    type_map[transaction_type],
                    subscription.payment_uri,
                    amount,
                    subscription.next_transaction_at, 
                    subscription.period, 
                )
                # advance the next transaction time
                subscription.period += 1
                subscription.next_transaction_at = next_transaction_datetime(
                    started_at=subscription.started_at,
                    frequency=subscription.plan.frequency, 
                    period=subscription.period,
                    interval=subscription.plan.interval, 
                )
                self.session.add(subscription)
                self.session.flush()
                transaction_guids.append(guid)

        self.session.flush()
        return transaction_guids
Example #14
0
    def yield_invoices(self, subscriptions=None, now=None):
        """Generate new scheduled invoices from given subscriptions

        :param subscriptions: A list subscription to yield invoices
            from, if None is given, all subscriptions in the database will be
            the yielding source
        :param now: the current date time to use, now_func() will be used by
            default
        :return: a generated transaction guid list
        """
        if now is None:
            now = tables.now_func()

        invoice_model = self.factory.create_invoice_model()
        Subscription = tables.Subscription

        subscription_guids = []
        if subscriptions is not None:
            subscription_guids = [subscription.guid for subscription in subscriptions]
        invoices = []

        # as we may have multiple new invoices for one subscription to
        # yield now, for example, we didn't run this method for a long while,
        # in this case, we need to make sure all transactions are yielded
        while True:
            # find subscriptions which should yield new invoices
            query = (
                self.session.query(Subscription)
                .filter(Subscription.next_invoice_at <= now)
                .filter(not_(Subscription.canceled))
            )
            if subscription_guids:
                query = query.filter(Subscription.guid.in_(subscription_guids))
            query = list(query)

            # okay, we have no more subscription to process, just break
            if not query:
                self.logger.info("No more subscriptions to process")
                break

            for subscription in query:
                if subscription.plan.plan_type == PlanModel.TYPE_CHARGE:
                    transaction_type = "charge"
                elif subscription.plan.plan_type == PlanModel.TYPE_PAYOUT:
                    transaction_type = "payout"
                else:
                    raise ValueError("Unknown plan type {} to process".format(subscription.plan.plan_type))

                amount = subscription.effective_amount
                # create the new transaction for this subscription
                invoice = invoice_model.create(
                    subscription=subscription,
                    funding_instrument_uri=subscription.funding_instrument_uri,
                    amount=amount,
                    scheduled_at=subscription.next_invoice_at,
                    appears_on_statement_as=subscription.appears_on_statement_as,
                )
                self.logger.info(
                    "Created subscription invoice for %s, guid=%s, "
                    "transaction_type=%s, funding_instrument_uri=%s, "
                    "amount=%s, scheduled_at=%s, period=%s",
                    subscription.guid,
                    invoice.guid,
                    transaction_type,
                    invoice.funding_instrument_uri,
                    invoice.amount,
                    invoice.scheduled_at,
                    subscription.invoice_count - 1,
                )
                # advance the next invoice time
                subscription.next_invoice_at = next_transaction_datetime(
                    started_at=subscription.started_at,
                    frequency=subscription.plan.frequency,
                    period=subscription.invoice_count,
                    interval=subscription.plan.interval,
                )
                self.logger.info(
                    "Schedule next invoice of %s at %s (period=%s)",
                    subscription.guid,
                    subscription.next_invoice_at,
                    subscription.invoice_count,
                )
                self.session.flush()
                invoices.append(invoice)

        self.session.flush()
        return invoices
    def yield_invoices(self, subscriptions=None, now=None):
        """Generate new scheduled invoices from given subscriptions

        :param subscriptions: A list subscription to yield invoices
            from, if None is given, all subscriptions in the database will be
            the yielding source
        :param now: the current date time to use, now_func() will be used by
            default
        :return: a generated transaction guid list
        """
        if now is None:
            now = tables.now_func()

        invoice_model = self.factory.create_invoice_model()
        Subscription = tables.Subscription

        subscription_guids = []
        if subscriptions is not None:
            subscription_guids = [
                subscription.guid for subscription in subscriptions
            ]
        invoices = []

        # as we may have multiple new invoices for one subscription to
        # yield now, for example, we didn't run this method for a long while,
        # in this case, we need to make sure all transactions are yielded
        while True:
            # find subscriptions which should yield new invoices
            query = (
                self.session.query(Subscription)
                .filter(Subscription.next_invoice_at <= now)
                .filter(not_(Subscription.canceled))
            )
            if subscription_guids:
                query = query.filter(Subscription.guid.in_(subscription_guids))
            query = list(query)

            # okay, we have no more subscription to process, just break
            if not query:
                self.logger.info('No more subscriptions to process')
                break

            for subscription in query:
                amount = subscription.effective_amount
                # create the new transaction for this subscription
                invoice = invoice_model.create(
                    subscription=subscription,
                    funding_instrument_uri=subscription.funding_instrument_uri,
                    amount=amount,
                    scheduled_at=subscription.next_invoice_at,
                    appears_on_statement_as=subscription.appears_on_statement_as,
                )
                self.logger.info(
                    'Created subscription invoice for %s, guid=%s, '
                    'plan_type=%s, funding_instrument_uri=%s, '
                    'amount=%s, scheduled_at=%s, period=%s',
                    subscription.guid,
                    invoice.guid,
                    subscription.plan.plan_type,
                    invoice.funding_instrument_uri,
                    invoice.amount,
                    invoice.scheduled_at,
                    subscription.invoice_count - 1,
                )
                # advance the next invoice time
                subscription.next_invoice_at = next_transaction_datetime(
                    started_at=subscription.started_at,
                    frequency=subscription.plan.frequency,
                    period=subscription.invoice_count,
                    interval=subscription.plan.interval,
                )
                self.logger.info(
                    'Schedule next invoice of %s at %s (period=%s)',
                    subscription.guid,
                    subscription.next_invoice_at,
                    subscription.invoice_count,
                )
                self.session.flush()
                invoices.append(invoice)

        self.session.flush()
        return invoices