def update_from_stripe_data(self,
                                stripe_coupon,
                                exclude_fields=None,
                                commit=True):
        """
        Update StripeCoupon object with data from stripe.Coupon without calling stripe.Coupon.retrieve.

        To only update the object, set the commit param to False.
        Returns the number of rows altered or None if commit is False.
        """
        fields_to_update = self.STRIPE_FIELDS - set(exclude_fields or [])
        update_data = {key: stripe_coupon[key] for key in fields_to_update}
        for field in ["created", "redeem_by"]:
            if update_data.get(field):
                update_data[field] = timestamp_to_timezone_aware_date(
                    update_data[field])

        if update_data.get("amount_off"):
            update_data["amount_off"] = Decimal(
                update_data["amount_off"]) / 100

        # also make sure the object is up to date (without the need to call database)
        for key, value in update_data.items():
            setattr(self, key, value)

        if commit:
            return StripeCoupon.objects.filter(pk=self.pk).update(
                **update_data)
 def _parse_coupon_notification(self, action):
     coupon_id = self.raw_data["data"]["object"]["id"]
     created = timestamp_to_timezone_aware_date(
         self.raw_data["data"]["object"]["created"])
     if action == "created":
         try:
             StripeCoupon.objects.all_with_deleted().get(
                 coupon_id=coupon_id, created=created)
         except StripeCoupon.DoesNotExist:
             try:
                 StripeCoupon(coupon_id=coupon_id).save(force_retrieve=True)
             except stripe.error.InvalidRequestError:
                 raise StripeWebhookParseError(
                     _("Coupon with this coupon_id does not exists at Stripe API"
                       ))
             except StripeCouponAlreadyExists as e:
                 raise StripeWebhookParseError(e.details)
         else:
             raise StripeWebhookParseError(
                 StripeCouponAlreadyExists.details)
     elif action == "updated":
         try:
             coupon = StripeCoupon.objects.get(coupon_id=coupon_id,
                                               created=created)
             coupon.metadata = self.raw_data["data"]["object"]["metadata"]
             super(StripeCoupon, coupon).save(
             )  # use the super method not to call Stripe API
         except StripeCoupon.DoesNotExist:
             pass  # do not update if does not exist
     elif action == "deleted":
         StripeCoupon.objects.filter(coupon_id=coupon_id,
                                     created=created).delete()
Exemplo n.º 3
0
def migrate_subcription(apps, schema_editor):
    StripeSubscription = apps.get_model("aa_stripe", "StripeSubscription")
    StripeCoupon = apps.get_model("aa_stripe", "StripeCoupon")
    stripe.api_key = stripe_settings.API_KEY

    for subscription in StripeSubscription.objects.exclude(coupon_code=""):
        if StripeCoupon.objects.filter(coupon_id=subscription.coupon_code,
                                       is_deleted=False).exists():
            # do not allow duplicates
            continue

        try:
            stripe_coupon = stripe.Coupon.retrieve(id=subscription.coupon_code)
        except stripe.error.InvalidRequestError:
            print("Coupon {} does not exist, cannot migrate".format(
                subscription.coupon_code))
            continue

        fields = {
            "amount_off", "currency", "duration", "duration_in_months",
            "livemode", "max_redemptions", "percent_off", "redeem_by",
            "times_redeemed", "valid", "metadata", "created"
        }
        update_data = {key: stripe_coupon[key] for key in fields}
        update_data["created"] = timestamp_to_timezone_aware_date(
            stripe_coupon.get("created"))
        update_data["amount_off"] = Decimal(update_data["amount_off"]) / 100
        coupon = StripeCoupon.objects.create(coupon_id=stripe_coupon.get("id"),
                                             is_created_at_stripe=True,
                                             **update_data)
        subscription.coupon = coupon
        subscription.save()
Exemplo n.º 4
0
    def test_update(self):
        with requests_mock.Mocker() as m:
            created = int(time.mktime(datetime.now().timetuple()))
            stripe_response = {
                "id": "25OFF",
                "object": "coupon",
                "amount_off": 1,
                "created": created,
                "currency": "usd",
                "duration": StripeCoupon.DURATION_FOREVER,
                "duration_in_months": None,
                "livemode": False,
                "max_redemptions": None,
                "metadata": {},
                "percent_off": 25,
                "redeem_by": created + 60,
                "times_redeemed": 0,
                "valid": True
            }
            coupon = self._create_coupon(
                coupon_id="25OFF",
                duration=StripeCoupon.DURATION_FOREVER,
                amount_off=1)
            self.assertFalse(coupon.is_deleted)

            # try accessing coupon that does not exist - should delete the coupon from our database
            m.register_uri("GET",
                           "https://api.stripe.com/v1/coupons/25OFF",
                           status_code=404,
                           text=json.dumps(
                               {"error": {
                                   "type": "invalid_request_error"
                               }}))
            coupon.metadata = {"yes": "no"}
            coupon.save()
            self.assertTrue(
                StripeCoupon.objects.deleted().filter(pk=coupon.pk).exists())

            # try changing other Stripe data than coupon's metadata
            m.register_uri("GET",
                           "https://api.stripe.com/v1/coupons/25OFF",
                           text=json.dumps(stripe_response))
            m.register_uri("POST",
                           "https://api.stripe.com/v1/coupons/25OFF",
                           text=json.dumps(stripe_response))
            coupon = self._create_coupon(
                coupon_id="25OFF",
                duration=StripeCoupon.DURATION_FOREVER,
                amount_off=1)
            coupon.duration = StripeCoupon.DURATION_ONCE
            coupon.save()
            coupon.refresh_from_db()
            self.assertNotEqual(coupon.duration, StripeCoupon.DURATION_ONCE)
            self.assertEqual(
                coupon.redeem_by,
                timestamp_to_timezone_aware_date(stripe_response["redeem_by"]))
Exemplo n.º 5
0
    def test_create(self):
        # test creating simple coupon with no coupon_id specified (will be generated by Stripe)
        created = int(time.mktime(datetime.now().timetuple()))
        stripe_response = {
            "id": "25OFF",
            "object": "coupon",
            "amount_off": 1,
            "created": created,
            "currency": "usd",
            "duration": StripeCoupon.DURATION_FOREVER,
            "duration_in_months": None,
            "livemode": False,
            "max_redemptions": None,
            "metadata": {},
            "percent_off": 25,
            "redeem_by": created + 60,
            "times_redeemed": 0,
            "valid": True
        }
        with requests_mock.Mocker() as m:
            m.register_uri("POST",
                           "https://api.stripe.com/v1/coupons",
                           text=json.dumps(stripe_response))
            coupon = StripeCoupon.objects.create(
                duration=StripeCoupon.DURATION_FOREVER,
                percent_off=25,
                redeem_by=timezone.now() + timedelta(seconds=60))
            self.assertEqual(coupon.coupon_id, stripe_response["id"])
            self.assertEqual(
                coupon.created,
                timestamp_to_timezone_aware_date(stripe_response["created"]))
            self.assertEqual(coupon.stripe_response, stripe_response)
            self.assertIn(
                "redeem_by={}".format(dateformat.format(coupon.redeem_by,
                                                        "U")),
                m.last_request.body)

            # test creating coupon with coupon_id
            stripe_response["id"] = "YOLO1"
            m.register_uri("POST",
                           "https://api.stripe.com/v1/coupons",
                           text=json.dumps(stripe_response))
            coupon = StripeCoupon.objects.create(
                coupon_id=stripe_response["id"],
                duration=StripeCoupon.DURATION_FOREVER)
            self.assertEqual(coupon.coupon_id, stripe_response["id"])
    def handle(self, *args, **options):
        stripe.api_key = stripe_settings.API_KEY

        counts = {
            "created": 0,
            "updated": 0,
            "deleted": 0
        }
        active_coupons_ids = []
        last_stripe_coupon = None
        while True:
            stripe_coupon_list = stripe.Coupon.list(starting_after=last_stripe_coupon)
            for stripe_coupon in stripe_coupon_list["data"]:
                try:
                    coupon = StripeCoupon.objects.get(
                        coupon_id=stripe_coupon.id, created=timestamp_to_timezone_aware_date(stripe_coupon["created"]),
                        is_deleted=False)
                    counts["updated"] += coupon.update_from_stripe_data(stripe_coupon)
                except StripeCoupon.DoesNotExist:
                    # already have the data - we do not need to call Stripe API again
                    coupon = StripeCoupon(coupon_id=stripe_coupon.id)
                    coupon.update_from_stripe_data(stripe_coupon, commit=False)
                    super(StripeCoupon, coupon).save()
                    counts["created"] += 1

                # indicate which coupons should have is_deleted=False
                active_coupons_ids.append(coupon.pk)

            if not stripe_coupon_list["has_more"]:
                break
            else:
                last_stripe_coupon = stripe_coupon_list["data"][-1]

        # update can be used here, because those coupons does not exist in the Stripe API anymore
        coupons_to_delete = StripeCoupon.objects.exclude(pk__in=active_coupons_ids)
        for coupon in coupons_to_delete:
            coupon.is_deleted = True
            super(StripeCoupon, coupon).save()  # make sure pre/post save signals are triggered without calling API
        counts["deleted"] += coupons_to_delete.count()

        if options.get("verbosity") > 1:
            print("Coupons created: {created}, updated: {updated}, deleted: {deleted}".format(**counts))
    def save(self, force_retrieve=False, *args, **kwargs):
        """
        Use the force_retrieve parameter to create a new StripeCoupon object from an existing coupon created at Stripe

        API or update the local object with data fetched from Stripe.
        """
        stripe.api_key = stripe_settings.API_KEY
        if self._previous_is_deleted != self.is_deleted and self.is_deleted:
            try:
                stripe_coupon = stripe.Coupon.retrieve(self.coupon_id)
                # make sure to delete correct coupon
                if self.created == timestamp_to_timezone_aware_date(
                        stripe_coupon["created"]):
                    stripe_coupon.delete()
            except stripe.error.InvalidRequestError:
                # means that the coupon has already been removed from stripe
                pass

            return super(StripeCoupon, self).save(*args, **kwargs)

        if self.pk or force_retrieve:
            try:
                stripe_coupon = stripe.Coupon.retrieve(self.coupon_id)
                if not force_retrieve:
                    stripe_coupon.metadata = self.metadata
                    stripe_coupon.save()

                if force_retrieve:
                    # make sure we are not creating a duplicate
                    coupon_qs = StripeCoupon.objects.filter(
                        coupon_id=self.coupon_id)
                    if coupon_qs.filter(
                            created=timestamp_to_timezone_aware_date(
                                stripe_coupon["created"])).exists():
                        raise StripeCouponAlreadyExists

                    # all old coupons should be deleted
                    for coupon in coupon_qs:
                        coupon.is_deleted = True
                        super(StripeCoupon, coupon).save(
                        )  # use super save() to call pre/post save signals
                # update all fields in the local object in case someone tried to change them
                self.update_from_stripe_data(
                    stripe_coupon,
                    exclude_fields=["metadata"] if not force_retrieve else [],
                )
                self.stripe_response = stripe_coupon
            except stripe.error.InvalidRequestError:
                if force_retrieve:
                    raise

                self.is_deleted = True
        else:
            self.stripe_response = stripe.Coupon.create(
                id=self.coupon_id,
                duration=self.duration,
                amount_off=int(self.amount_off *
                               100) if self.amount_off else None,
                currency=self.currency,
                duration_in_months=self.duration_in_months,
                max_redemptions=self.max_redemptions,
                metadata=self.metadata,
                percent_off=self.percent_off,
                redeem_by=int(dateformat.format(self.redeem_by, "U"))
                if self.redeem_by else None,
            )
            # stripe will generate coupon_id if none was specified in the request
            if not self.coupon_id:
                self.coupon_id = self.stripe_response["id"]

        self.created = timestamp_to_timezone_aware_date(
            self.stripe_response["created"])
        # for future
        self.is_created_at_stripe = True
        return super(StripeCoupon, self).save(*args, **kwargs)