Skip to content

Commit

Permalink
Merge pull request #532 from stripe/remi-add-subscription-schedules
Browse files Browse the repository at this point in the history
Add support for Subscription Schedules
  • Loading branch information
ob-stripe authored Feb 12, 2019
2 parents 448f837 + 1c1d52c commit c792fde
Show file tree
Hide file tree
Showing 10 changed files with 181 additions and 4 deletions.
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ cache:
env:
global:
# If changing this number, please also change it in `tests/conftest.py`.
- STRIPE_MOCK_VERSION=0.42.0
- STRIPE_MOCK_VERSION=0.44.0

before_install:
# Unpack and start stripe-mock so that the test suite can talk to it
Expand Down
4 changes: 4 additions & 0 deletions stripe/api_resources/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@
from stripe.api_resources.source_transaction import SourceTransaction
from stripe.api_resources.subscription import Subscription
from stripe.api_resources.subscription_item import SubscriptionItem
from stripe.api_resources.subscription_schedule import SubscriptionSchedule
from stripe.api_resources.subscription_schedule_revision import (
SubscriptionScheduleRevision,
)
from stripe.api_resources.three_d_secure import ThreeDSecure
from stripe.api_resources.token import Token
from stripe.api_resources.topup import Topup
Expand Down
29 changes: 29 additions & 0 deletions stripe/api_resources/subscription_schedule.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
from __future__ import absolute_import, division, print_function

from stripe import util
from stripe.api_resources.abstract import CreateableAPIResource
from stripe.api_resources.abstract import UpdateableAPIResource
from stripe.api_resources.abstract import ListableAPIResource
from stripe.api_resources.abstract import nested_resource_class_methods


@nested_resource_class_methods("revision", operations=["retrieve", "list"])
class SubscriptionSchedule(
CreateableAPIResource, UpdateableAPIResource, ListableAPIResource
):
OBJECT_NAME = "subscription_schedule"

def cancel(self, idempotency_key=None, **params):
url = self.instance_url() + "/cancel"
headers = util.populate_headers(idempotency_key)
self.refresh_from(self.request("post", url, params, headers))
return self

def release(self, idempotency_key=None, **params):
url = self.instance_url() + "/release"
headers = util.populate_headers(idempotency_key)
self.refresh_from(self.request("post", url, params, headers))
return self

def revisions(self, **params):
return self.request("get", self.instance_url() + "/revisions", params)
25 changes: 25 additions & 0 deletions stripe/api_resources/subscription_schedule_revision.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
from __future__ import absolute_import, division, print_function

from stripe import util
from stripe.api_resources.abstract.api_resource import APIResource
from stripe.api_resources.subscription_schedule import SubscriptionSchedule
from stripe.six.moves.urllib.parse import quote_plus


class SubscriptionScheduleRevision(APIResource):
OBJECT_NAME = "subscription_schedule_revision"

def instance_url(self):
token = util.utf8(self.id)
schedule = util.utf8(self.schedule)
base = SubscriptionSchedule.class_url()
schedule_extn = quote_plus(schedule)
extn = quote_plus(token)
return "%s/%s/revisions/%s" % (base, schedule_extn, extn)

@classmethod
def retrieve(cls, id, api_key=None, **params):
raise NotImplementedError(
"Can't retrieve a subscription schedule revision without a schedule "
"ID. Use schedule.revisions.retrieve('revision_id')"
)
2 changes: 2 additions & 0 deletions stripe/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,8 @@ def load_object_classes():
api_resources.SourceTransaction.OBJECT_NAME: api_resources.SourceTransaction,
api_resources.Subscription.OBJECT_NAME: api_resources.Subscription,
api_resources.SubscriptionItem.OBJECT_NAME: api_resources.SubscriptionItem,
api_resources.SubscriptionSchedule.OBJECT_NAME: api_resources.SubscriptionSchedule,
api_resources.SubscriptionScheduleRevision.OBJECT_NAME: api_resources.SubscriptionScheduleRevision,
api_resources.ThreeDSecure.OBJECT_NAME: api_resources.ThreeDSecure,
api_resources.Token.OBJECT_NAME: api_resources.Token,
api_resources.Topup.OBJECT_NAME: api_resources.Topup,
Expand Down
2 changes: 1 addition & 1 deletion tests/api_resources/checkout/test_session.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
class TestSession(object):
def test_is_creatable(self, request_mock):
resource = stripe.checkout.Session.create(
allowed_source_types=["card"],
cancel_url="https://stripe.com/cancel",
client_reference_id="1234",
line_items=[
Expand All @@ -23,6 +22,7 @@ def test_is_creatable(self, request_mock):
}
],
payment_intent_data={"receipt_email": "test@stripe.com"},
payment_method_types=["card"],
success_url="https://stripe.com/success",
)
request_mock.assert_requested("post", "/v1/checkout/sessions")
Expand Down
2 changes: 1 addition & 1 deletion tests/api_resources/test_payment_intent.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ def test_is_retrievable(self, request_mock):

def test_is_creatable(self, request_mock):
resource = stripe.PaymentIntent.create(
allowed_source_types=["card"], amount="1234", currency="amount"
amount="1234", currency="amount", payment_method_types=["card"]
)
request_mock.assert_requested("post", "/v1/payment_intents")
assert isinstance(resource, stripe.PaymentIntent)
Expand Down
85 changes: 85 additions & 0 deletions tests/api_resources/test_subscription_schedule.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
from __future__ import absolute_import, division, print_function

import stripe


TEST_RESOURCE_ID = "sub_sched_123"
TEST_REVISION_ID = "sub_sched_rev_123"


class TestSubscriptionScheduleSchedule(object):
def test_is_listable(self, request_mock):
resources = stripe.SubscriptionSchedule.list()
request_mock.assert_requested("get", "/v1/subscription_schedules")
assert isinstance(resources.data, list)
assert isinstance(resources.data[0], stripe.SubscriptionSchedule)

def test_is_retrievable(self, request_mock):
resource = stripe.SubscriptionSchedule.retrieve(TEST_RESOURCE_ID)
request_mock.assert_requested(
"get", "/v1/subscription_schedules/%s" % TEST_RESOURCE_ID
)
assert isinstance(resource, stripe.SubscriptionSchedule)

def test_is_creatable(self, request_mock):
resource = stripe.SubscriptionSchedule.create(customer="cus_123")
request_mock.assert_requested("post", "/v1/subscription_schedules")
assert isinstance(resource, stripe.SubscriptionSchedule)

def test_is_saveable(self, request_mock):
resource = stripe.SubscriptionSchedule.retrieve(TEST_RESOURCE_ID)
resource.metadata["key"] = "value"
resource.save()
request_mock.assert_requested(
"post", "/v1/subscription_schedules/%s" % TEST_RESOURCE_ID
)

def test_is_modifiable(self, request_mock):
resource = stripe.SubscriptionSchedule.modify(
TEST_RESOURCE_ID, metadata={"key": "value"}
)
request_mock.assert_requested(
"post", "/v1/subscription_schedules/%s" % TEST_RESOURCE_ID
)
assert isinstance(resource, stripe.SubscriptionSchedule)

def test_can_cancel(self, request_mock):
resource = stripe.SubscriptionSchedule.retrieve(TEST_RESOURCE_ID)
resource = resource.cancel()
request_mock.assert_requested(
"post", "/v1/subscription_schedules/%s/cancel" % TEST_RESOURCE_ID
)
assert isinstance(resource, stripe.SubscriptionSchedule)

def test_can_release(self, request_mock):
resource = stripe.SubscriptionSchedule.retrieve(TEST_RESOURCE_ID)
resource = resource.release()
request_mock.assert_requested(
"post", "/v1/subscription_schedules/%s/release" % TEST_RESOURCE_ID
)
assert isinstance(resource, stripe.SubscriptionSchedule)


class TestSubscriptionScheduleRevisions(object):
def test_is_listable(self, request_mock):
resources = stripe.SubscriptionSchedule.list_revisions(
TEST_RESOURCE_ID
)
request_mock.assert_requested(
"get", "/v1/subscription_schedules/%s/revisions" % TEST_RESOURCE_ID
)
assert isinstance(resources.data, list)
assert isinstance(
resources.data[0], stripe.SubscriptionScheduleRevision
)

def test_is_retrievable(self, request_mock):
resource = stripe.SubscriptionSchedule.retrieve_revision(
TEST_RESOURCE_ID, TEST_REVISION_ID
)
request_mock.assert_requested(
"get",
"/v1/subscription_schedules/%s/revisions/%s"
% (TEST_RESOURCE_ID, TEST_REVISION_ID),
)
assert isinstance(resource, stripe.SubscriptionScheduleRevision)
32 changes: 32 additions & 0 deletions tests/api_resources/test_subscription_schedule_revision.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
from __future__ import absolute_import, division, print_function

import pytest

import stripe


TEST_RESOURCE_ID = "sub_sched_rev_123"


class TestSubscriptionScheduleRevision(object):
def construct_resource(self):
revision_dict = {
"id": TEST_RESOURCE_ID,
"object": "subscription_schedule_revision",
"schedule": "sub_sched_123",
}
return stripe.SubscriptionScheduleRevision.construct_from(
revision_dict, stripe.api_key
)

def test_has_instance_url(self, request_mock):
resource = self.construct_resource()
assert (
resource.instance_url()
== "/v1/subscription_schedules/sub_sched_123/revisions/%s"
% TEST_RESOURCE_ID
)

def test_is_not_retrievable(self, request_mock):
with pytest.raises(NotImplementedError):
stripe.SubscriptionScheduleRevision.retrieve(TEST_RESOURCE_ID)
2 changes: 1 addition & 1 deletion tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@


# When changing this number, don't forget to change it in `.travis.yml` too.
MOCK_MINIMUM_VERSION = "0.42.0"
MOCK_MINIMUM_VERSION = "0.44.0"

# Starts stripe-mock if an OpenAPI spec override is found in `openapi/`, and
# otherwise fall back to `STRIPE_MOCK_PORT` or 12111.
Expand Down

0 comments on commit c792fde

Please # to comment.