Skip to content

Commit

Permalink
feat: SB-572 Create stripe setup intent using GraphQL
Browse files Browse the repository at this point in the history
  • Loading branch information
mkleszcz committed Oct 6, 2022
1 parent f652a40 commit a5f0ddc
Show file tree
Hide file tree
Showing 26 changed files with 802 additions and 138 deletions.
14 changes: 14 additions & 0 deletions services/backend/apps/finances/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,13 @@ class Meta:
fields = ("pk", "id", "amount", "currency", "client_secret")


class StripeSetupIntentType(StripeDjangoObjectType):
class Meta:
model = djstripe_models.SetupIntent
interfaces = (relay.Node,)
exclude_fields = ('setup_intents',)


class UpdateDefaultPaymentMethodMutation(PaymentMethodGetObjectMixin, mutations.SerializerMutation):
active_subscription = graphene.Field(SubscriptionScheduleType)
payment_method_edge = graphene.Field(PaymentMethodConnection.Edge)
Expand Down Expand Up @@ -278,6 +285,12 @@ def get_queryset(cls, model_class, root, info, **input):
return djstripe_models.PaymentIntent.objects.filter(customer__subscriber=info.context.user)


class CreateSetupIntentMutation(mutations.CreateModelMutation):
class Meta:
model = djstripe_models.SetupIntent
serializer_class = serializers.SetupIntentSerializer


class Query(graphene.ObjectType):
all_subscription_plans = graphene.relay.ConnectionField(SubscriptionPlanConnection)
active_subscription = graphene.Field(SubscriptionScheduleType)
Expand Down Expand Up @@ -324,3 +337,4 @@ class Mutation(graphene.ObjectType):
delete_payment_method = DeletePaymentMethodMutation.Field()
create_payment_intent = CreatePaymentIntentMutation.Field()
update_payment_intent = UpdatePaymentIntentMutation.Field()
create_setup_intent = CreateSetupIntentMutation.Field()
38 changes: 38 additions & 0 deletions services/backend/apps/finances/tests/test_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -759,3 +759,41 @@ def test_update_payment_intent_amount(self, graphene_client, payment_intent_fact

payment_intent.refresh_from_db()
assert payment_intent.amount == 1000


class TestCreateSetupIntentMutation:
CREATE_PAYMENT_INTENT_MUTATION = '''
mutation($input: CreateSetupIntentMutationInput!) {
createSetupIntent(input: $input) {
setupIntent {
id
clientSecret
}
}
}
'''

def test_return_error_for_unauthorized_user(self, graphene_client):
executed = graphene_client.mutate(
self.CREATE_PAYMENT_INTENT_MUTATION,
variable_values={'input': {}},
)

assert executed["errors"]
assert executed["errors"][0]["message"] == "permission_denied"

def test_creates_payment_intent(self, graphene_client, user):
graphene_client.force_authenticate(user)
executed = graphene_client.mutate(
self.CREATE_PAYMENT_INTENT_MUTATION,
variable_values={'input': {}},
)

assert executed['data']['createSetupIntent']
assert executed['data']['createSetupIntent']['setupIntent']

setup_intent_global_id = executed['data']['createSetupIntent']['setupIntent']['id']
_, pk = from_global_id(setup_intent_global_id)
setup_intent = djstripe_models.SetupIntent.objects.get(pk=pk)

assert executed['data']['createSetupIntent']['setupIntent']['clientSecret'] == setup_intent.client_secret
7 changes: 0 additions & 7 deletions services/backend/apps/finances/urls.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,6 @@
from django.urls import path, include
from rest_framework.routers import DefaultRouter

from . import views

stripe_router = DefaultRouter()
stripe_router.register(r'setup-intent', views.StripeSetupIntentViewSet, basename='setup-intent')

stripe_urls = [
path("", include(stripe_router.urls)),
path("", include("djstripe.urls", namespace="djstripe")),
]

Expand Down
15 changes: 0 additions & 15 deletions services/backend/apps/finances/views.py

This file was deleted.

123 changes: 123 additions & 0 deletions services/webapp/graphql/schema/api.graphql.chunk
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ type ApiMutation {
deletePaymentMethod(input: DeletePaymentMethodMutationInput!): DeletePaymentMethodMutationPayload
createPaymentIntent(input: CreatePaymentIntentMutationInput!): CreatePaymentIntentMutationPayload
updatePaymentIntent(input: UpdatePaymentIntentMutationInput!): UpdatePaymentIntentMutationPayload
createSetupIntent(input: CreateSetupIntentMutationInput!): CreateSetupIntentMutationPayload
changePassword(input: ChangePasswordMutationInput!): ChangePasswordMutationPayload
updateCurrentUser(input: UpdateCurrentUserMutationInput!): UpdateCurrentUserMutationPayload
tokenAuth(input: ObtainTokenMutationInput!): ObtainTokenMutationPayload
Expand Down Expand Up @@ -473,6 +474,15 @@ type CreatePaymentIntentMutationPayload {
clientMutationId: String
}

input CreateSetupIntentMutationInput {
clientMutationId: String
}

type CreateSetupIntentMutationPayload {
setupIntent: StripeSetupIntentType
clientMutationId: String
}

type CrudDemoItemConnection {
"""Pagination data for this connection."""
pageInfo: PageInfo!
Expand Down Expand Up @@ -914,6 +924,52 @@ type Query {
): Node
}

"""An enumeration."""
enum SetupIntentCancellationReason {
"""Abandoned"""
ABANDONED

"""Duplicate"""
DUPLICATE

"""Requested by Customer"""
REQUESTED_BY_CUSTOMER
}

"""An enumeration."""
enum SetupIntentStatus {
"""
Cancellation invalidates the intent for future confirmation and cannot be undone.
"""
CANCELED

"""Required actions have been handled."""
PROCESSING

"""Payment Method require additional action, such as 3D secure."""
REQUIRES_ACTION

"""Intent is ready to be confirmed."""
REQUIRES_CONFIRMATION

"""Intent created and requires a Payment Method to be attached."""
REQUIRES_PAYMENT_METHOD

"""
Setup was successful and the payment method is optimized for future payments.
"""
SUCCEEDED
}

"""An enumeration."""
enum SetupIntentUsage {
"""Off session"""
OFF_SESSION

"""On session"""
ON_SESSION
}

input SingUpMutationInput {
id: String
email: String!
Expand Down Expand Up @@ -1527,6 +1583,73 @@ type StripeProductType implements Node {
pk: String
}

type StripeSetupIntentType implements Node {
djstripeCreated: DateTime!
djstripeUpdated: DateTime!
djstripeId: ID!

"""The ID of the object."""
id: ID!

"""
Null here indicates that the livemode status is unknown or was previously
unrecorded. Otherwise, this field indicates whether this record comes from
Stripe test mode or live mode operation.
"""
livemode: Boolean

"""The datetime this object was created in stripe."""
created: DateTime

"""
A set of key/value pairs that you can attach to an object. It can be useful
for storing additional information about an object in a structured format.
"""
metadata: String

"""A description of this object."""
description: String

"""ID of the Connect application that created the SetupIntent."""
application: String!

"""
Reason for cancellation of this SetupIntent, one of abandoned, requested_by_customer, or duplicate
"""
cancellationReason: SetupIntentCancellationReason

"""
The client secret of this SetupIntent. Used for client-side retrieval using a publishable key.
"""
clientSecret: String!

"""The error encountered in the previous SetupIntent confirmation."""
lastSetupError: String

"""
If present, this property tells you what actions you need to take inorder for your customer to continue payment setup.
"""
nextAction: String

"""Payment method used in this PaymentIntent."""
paymentMethod: StripePaymentMethodType

"""
The list of payment method types (e.g. card) that this PaymentIntent is allowed to use.
"""
paymentMethodTypes: String!

"""
Status of this SetupIntent, one of requires_payment_method,
requires_confirmation, requires_action, processing, canceled, or succeeded.
"""
status: SetupIntentStatus!

"""Indicates how the payment method is intended to be used in the future."""
usage: SetupIntentUsage!
pk: String
}

type StripeSubscriptionType implements Node {
"""The ID of the object."""
id: ID!
Expand Down
101 changes: 101 additions & 0 deletions services/webapp/graphql/schema/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -509,6 +509,7 @@ type ApiMutation {
deletePaymentMethod(input: DeletePaymentMethodMutationInput!): DeletePaymentMethodMutationPayload
createPaymentIntent(input: CreatePaymentIntentMutationInput!): CreatePaymentIntentMutationPayload
updatePaymentIntent(input: UpdatePaymentIntentMutationInput!): UpdatePaymentIntentMutationPayload
createSetupIntent(input: CreateSetupIntentMutationInput!): CreateSetupIntentMutationPayload
changePassword(input: ChangePasswordMutationInput!): ChangePasswordMutationPayload
updateCurrentUser(input: UpdateCurrentUserMutationInput!): UpdateCurrentUserMutationPayload
tokenAuth(input: ObtainTokenMutationInput!): ObtainTokenMutationPayload
Expand Down Expand Up @@ -876,6 +877,15 @@ type CreatePaymentIntentMutationPayload {
clientMutationId: String
}

input CreateSetupIntentMutationInput {
clientMutationId: String
}

type CreateSetupIntentMutationPayload {
setupIntent: StripeSetupIntentType
clientMutationId: String
}

type CrudDemoItemConnection {
"""Pagination data for this connection."""
pageInfo: PageInfo!
Expand Down Expand Up @@ -1236,6 +1246,44 @@ enum ProductType {
SERVICE
}

"""An enumeration."""
enum SetupIntentCancellationReason {
"""Abandoned"""
ABANDONED
"""Duplicate"""
DUPLICATE
"""Requested by Customer"""
REQUESTED_BY_CUSTOMER
}

"""An enumeration."""
enum SetupIntentStatus {
"""
Cancellation invalidates the intent for future confirmation and cannot be undone.
"""
CANCELED
"""Required actions have been handled."""
PROCESSING
"""Payment Method require additional action, such as 3D secure."""
REQUIRES_ACTION
"""Intent is ready to be confirmed."""
REQUIRES_CONFIRMATION
"""Intent created and requires a Payment Method to be attached."""
REQUIRES_PAYMENT_METHOD
"""
Setup was successful and the payment method is optimized for future payments.
"""
SUCCEEDED
}

"""An enumeration."""
enum SetupIntentUsage {
"""Off session"""
OFF_SESSION
"""On session"""
ON_SESSION
}

input SingUpMutationInput {
id: String
email: String!
Expand Down Expand Up @@ -1742,6 +1790,59 @@ type StripeProductType implements Node {
pk: String
}

type StripeSetupIntentType implements Node {
djstripeCreated: DateTime!
djstripeUpdated: DateTime!
djstripeId: ID!
"""The ID of the object."""
id: ID!
"""
Null here indicates that the livemode status is unknown or was previously
unrecorded. Otherwise, this field indicates whether this record comes from
Stripe test mode or live mode operation.
"""
livemode: Boolean
"""The datetime this object was created in stripe."""
created: DateTime
"""
A set of key/value pairs that you can attach to an object. It can be useful
for storing additional information about an object in a structured format.
"""
metadata: String
"""A description of this object."""
description: String
"""ID of the Connect application that created the SetupIntent."""
application: String!
"""
Reason for cancellation of this SetupIntent, one of abandoned, requested_by_customer, or duplicate
"""
cancellationReason: SetupIntentCancellationReason
"""
The client secret of this SetupIntent. Used for client-side retrieval using a publishable key.
"""
clientSecret: String!
"""The error encountered in the previous SetupIntent confirmation."""
lastSetupError: String
"""
If present, this property tells you what actions you need to take inorder for your customer to continue payment setup.
"""
nextAction: String
"""Payment method used in this PaymentIntent."""
paymentMethod: StripePaymentMethodType
"""
The list of payment method types (e.g. card) that this PaymentIntent is allowed to use.
"""
paymentMethodTypes: String!
"""
Status of this SetupIntent, one of requires_payment_method,
requires_confirmation, requires_action, processing, canceled, or succeeded.
"""
status: SetupIntentStatus!
"""Indicates how the payment method is intended to be used in the future."""
usage: SetupIntentUsage!
pk: String
}

type StripeSubscriptionType implements Node {
"""The ID of the object."""
id: ID!
Expand Down
1 change: 0 additions & 1 deletion services/webapp/src/mocks/server/handlers/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
export * from './auth';
export * from './demoItems';
export * from './transactionHistory';
//<-- IMPORT API MODULE MOCK -->
Loading

0 comments on commit a5f0ddc

Please sign in to comment.