From b9bd82c903791070b02cb2240be2c531010b1906 Mon Sep 17 00:00:00 2001 From: Ancor Cruz Date: Thu, 26 Sep 2024 08:59:26 +0100 Subject: [PATCH] ## Roadmap Task MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 👉 https://getlago.canny.io/feature-requests/p/add-features-to-the-customer-portal ## Context The current customer portal does not provide any customer wallets information. ## Description This change improves the customer access to their active wallets information and provides the means to top-up paid credits to their wallets. --- .../wallet_transactions/create.rb | 26 + .../customer_portal/wallets_resolver.rb | 27 + .../wallet_transactions/object.rb | 24 + .../types/customer_portal/wallets/object.rb | 28 + app/graphql/types/mutation_type.rb | 1 + app/graphql/types/query_type.rb | 1 + schema.graphql | 89 ++ schema.json | 809 ++++++++++++++++-- .../wallet_transactions/create_spec.rb | 58 ++ .../customer_portal/wallets_resolver_spec.rb | 56 ++ .../wallet_transactions/object_spec.rb | 21 + .../customer_portal/wallets/object_spec.rb | 23 + 12 files changed, 1088 insertions(+), 75 deletions(-) create mode 100644 app/graphql/mutations/customer_portal/wallet_transactions/create.rb create mode 100644 app/graphql/resolvers/customer_portal/wallets_resolver.rb create mode 100644 app/graphql/types/customer_portal/wallet_transactions/object.rb create mode 100644 app/graphql/types/customer_portal/wallets/object.rb create mode 100644 spec/graphql/mutations/customer_portal/wallet_transactions/create_spec.rb create mode 100644 spec/graphql/resolvers/customer_portal/wallets_resolver_spec.rb create mode 100644 spec/graphql/types/customer_portal/wallet_transactions/object_spec.rb create mode 100644 spec/graphql/types/customer_portal/wallets/object_spec.rb diff --git a/app/graphql/mutations/customer_portal/wallet_transactions/create.rb b/app/graphql/mutations/customer_portal/wallet_transactions/create.rb new file mode 100644 index 000000000000..1a42761a22c7 --- /dev/null +++ b/app/graphql/mutations/customer_portal/wallet_transactions/create.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +module Mutations + module CustomerPortal + module WalletTransactions + class Create < BaseMutation + include AuthenticableCustomerPortalUser + + graphql_name "CreateCustomerPortalWalletTransaction" + description "Creates a new Customer Wallet Transaction from Customer Portal" + + argument :paid_credits, String, required: false + argument :wallet_id, ID, required: true + + type Types::CustomerPortal::WalletTransactions::Object.collection_type + + def resolve(**args) + organization = context[:customer_portal_user].organization + result = ::WalletTransactions::CreateService.call(organization:, params: args) + + result.success? ? result.wallet_transactions : result_error(result) + end + end + end + end +end diff --git a/app/graphql/resolvers/customer_portal/wallets_resolver.rb b/app/graphql/resolvers/customer_portal/wallets_resolver.rb new file mode 100644 index 000000000000..b456c2f8126a --- /dev/null +++ b/app/graphql/resolvers/customer_portal/wallets_resolver.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +module Resolvers + module CustomerPortal + class WalletsResolver < Resolvers::BaseResolver + include AuthenticableCustomerPortalUser + + description 'Query wallets' + + argument :limit, Integer, required: false + argument :page, Integer, required: false + + type Types::CustomerPortal::Wallets::Object.collection_type, null: false + + def resolve(page: nil, limit: nil) + context[:customer_portal_user] + .wallets + .active + .page(page) + .per(limit) + .order(created_at: :desc) + rescue ActiveRecord::RecordNotFound + not_found_error(resource: 'customer') + end + end + end +end diff --git a/app/graphql/types/customer_portal/wallet_transactions/object.rb b/app/graphql/types/customer_portal/wallet_transactions/object.rb new file mode 100644 index 000000000000..eb84aa5e2e4a --- /dev/null +++ b/app/graphql/types/customer_portal/wallet_transactions/object.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +module Types + module CustomerPortal + module WalletTransactions + class Object < Types::BaseObject + graphql_name "CustomerPortalWalletTransaction" + + field :id, ID, null: false + field :wallet, Types::CustomerPortal::Wallets::Object + + field :amount, String, null: false + field :credit_amount, String, null: false + field :status, Types::WalletTransactions::StatusEnum, null: false + field :transaction_status, Types::WalletTransactions::TransactionStatusEnum, null: false + field :transaction_type, Types::WalletTransactions::TransactionTypeEnum, null: false + + field :created_at, GraphQL::Types::ISO8601DateTime, null: false + field :settled_at, GraphQL::Types::ISO8601DateTime, null: true + field :updated_at, GraphQL::Types::ISO8601DateTime, null: false + end + end + end +end diff --git a/app/graphql/types/customer_portal/wallets/object.rb b/app/graphql/types/customer_portal/wallets/object.rb new file mode 100644 index 000000000000..383be0bc1df9 --- /dev/null +++ b/app/graphql/types/customer_portal/wallets/object.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +module Types + module CustomerPortal + module Wallets + class Object < Types::BaseObject + graphql_name "CustomerPortalWallet" + description "CustomerPortalWallet" + + field :id, ID, null: false + + field :currency, Types::CurrencyEnum, null: false + field :expiration_at, GraphQL::Types::ISO8601DateTime, null: true + field :name, String, null: true + field :status, Types::Wallets::StatusEnum, null: false + + field :balance_cents, GraphQL::Types::BigInt, null: false + field :consumed_amount_cents, GraphQL::Types::BigInt, null: false + field :consumed_credits, GraphQL::Types::Float, null: false + field :credits_balance, GraphQL::Types::Float, null: false + field :credits_ongoing_balance, GraphQL::Types::Float, null: false + field :ongoing_balance_cents, GraphQL::Types::BigInt, null: false + field :ongoing_usage_balance_cents, GraphQL::Types::BigInt, null: false + field :rate_amount, GraphQL::Types::Float, null: false + end + end + end +end diff --git a/app/graphql/types/mutation_type.rb b/app/graphql/types/mutation_type.rb index e126991324af..9f922c0e0e96 100644 --- a/app/graphql/types/mutation_type.rb +++ b/app/graphql/types/mutation_type.rb @@ -23,6 +23,7 @@ class MutationType < Types::BaseObject field :update_customer, mutation: Mutations::Customers::Update field :update_customer_invoice_grace_period, mutation: Mutations::Customers::UpdateInvoiceGracePeriod + field :create_customer_portal_wallet_transaction, mutation: Mutations::CustomerPortal::WalletTransactions::Create field :download_customer_portal_invoice, mutation: Mutations::CustomerPortal::DownloadInvoice field :generate_customer_portal_url, mutation: Mutations::CustomerPortal::GenerateUrl diff --git a/app/graphql/types/query_type.rb b/app/graphql/types/query_type.rb index e9e71d99e230..79aa0f3b39f2 100644 --- a/app/graphql/types/query_type.rb +++ b/app/graphql/types/query_type.rb @@ -26,6 +26,7 @@ class QueryType < Types::BaseObject field :customer_portal_organization, resolver: Resolvers::CustomerPortal::OrganizationResolver field :customer_portal_overdue_balances, resolver: Resolvers::CustomerPortal::Analytics::OverdueBalancesResolver field :customer_portal_user, resolver: Resolvers::CustomerPortal::CustomerResolver + field :customer_portal_wallets, resolver: Resolvers::CustomerPortal::WalletsResolver field :customer_usage, resolver: Resolvers::Customers::UsageResolver field :customers, resolver: Resolvers::CustomersResolver field :events, resolver: Resolvers::EventsResolver diff --git a/schema.graphql b/schema.graphql index 67b837b0c7fb..73606df550c3 100644 --- a/schema.graphql +++ b/schema.graphql @@ -1933,6 +1933,18 @@ input CreateCustomerInput { zipcode: String } +""" +Autogenerated input type of CreateCustomerPortalWalletTransaction +""" +input CreateCustomerPortalWalletTransactionInput { + """ + A unique identifier for the client performing the mutation. + """ + clientMutationId: String + paidCredits: String + walletId: ID! +} + """ Create Wallet Input """ @@ -3218,6 +3230,68 @@ input CustomerMetadataInput { value: String! } +""" +CustomerPortalWallet +""" +type CustomerPortalWallet { + balanceCents: BigInt! + consumedAmountCents: BigInt! + consumedCredits: Float! + creditsBalance: Float! + creditsOngoingBalance: Float! + currency: CurrencyEnum! + expirationAt: ISO8601DateTime + id: ID! + name: String + ongoingBalanceCents: BigInt! + ongoingUsageBalanceCents: BigInt! + rateAmount: Float! + status: WalletStatusEnum! +} + +""" +CustomerPortalWalletCollection type +""" +type CustomerPortalWalletCollection { + """ + A collection of paginated CustomerPortalWalletCollection + """ + collection: [CustomerPortalWallet!]! + + """ + Pagination Metadata for navigating the Pagination + """ + metadata: CollectionMetadata! +} + +type CustomerPortalWalletTransaction { + amount: String! + createdAt: ISO8601DateTime! + creditAmount: String! + id: ID! + settledAt: ISO8601DateTime + status: WalletTransactionStatusEnum! + transactionStatus: WalletTransactionTransactionStatusEnum! + transactionType: WalletTransactionTransactionTypeEnum! + updatedAt: ISO8601DateTime! + wallet: CustomerPortalWallet +} + +""" +CustomerPortalWalletTransactionCollection type +""" +type CustomerPortalWalletTransactionCollection { + """ + A collection of paginated CustomerPortalWalletTransactionCollection + """ + collection: [CustomerPortalWalletTransaction!]! + + """ + Pagination Metadata for navigating the Pagination + """ + metadata: CollectionMetadata! +} + enum CustomerTypeEnum { company individual @@ -4553,6 +4627,16 @@ type Mutation { input: CreateCustomerInput! ): Customer + """ + Creates a new Customer Wallet Transaction from Customer Portal + """ + createCustomerPortalWalletTransaction( + """ + Parameters for CreateCustomerPortalWalletTransaction + """ + input: CreateCustomerPortalWalletTransactionInput! + ): CustomerPortalWalletTransactionCollection + """ Creates a new Customer Wallet """ @@ -5973,6 +6057,11 @@ type Query { """ customerPortalUser: Customer + """ + Query wallets + """ + customerPortalWallets(limit: Int, page: Int): CustomerPortalWalletCollection! + """ Query the usage of the customer on the current billing period """ diff --git a/schema.json b/schema.json index 3e821876ab2e..a25ce7f0b653 100644 --- a/schema.json +++ b/schema.json @@ -7436,6 +7436,57 @@ ], "enumValues": null }, + { + "kind": "INPUT_OBJECT", + "name": "CreateCustomerPortalWalletTransactionInput", + "description": "Autogenerated input type of CreateCustomerPortalWalletTransaction", + "interfaces": null, + "possibleTypes": null, + "fields": null, + "inputFields": [ + { + "name": "clientMutationId", + "description": "A unique identifier for the client performing the mutation.", + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "walletId", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "ID", + "ofType": null + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "paidCredits", + "description": null, + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + } + ], + "enumValues": null + }, { "kind": "INPUT_OBJECT", "name": "CreateCustomerWalletInput", @@ -13218,14 +13269,507 @@ ] }, { - "name": "id", + "name": "id", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "ID", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null, + "args": [ + + ] + } + ], + "inputFields": null, + "enumValues": null + }, + { + "kind": "INPUT_OBJECT", + "name": "CustomerBillingConfigurationInput", + "description": null, + "interfaces": null, + "possibleTypes": null, + "fields": null, + "inputFields": [ + { + "name": "documentLocale", + "description": null, + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + } + ], + "enumValues": null + }, + { + "kind": "OBJECT", + "name": "CustomerCollection", + "description": "CustomerCollection type", + "interfaces": [ + + ], + "possibleTypes": null, + "fields": [ + { + "name": "collection", + "description": "A collection of paginated CustomerCollection", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "Customer", + "ofType": null + } + } + } + }, + "isDeprecated": false, + "deprecationReason": null, + "args": [ + + ] + }, + { + "name": "metadata", + "description": "Pagination Metadata for navigating the Pagination", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "CollectionMetadata", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null, + "args": [ + + ] + } + ], + "inputFields": null, + "enumValues": null + }, + { + "kind": "OBJECT", + "name": "CustomerMetadata", + "description": null, + "interfaces": [ + + ], + "possibleTypes": null, + "fields": [ + { + "name": "createdAt", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "ISO8601DateTime", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null, + "args": [ + + ] + }, + { + "name": "displayInInvoice", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null, + "args": [ + + ] + }, + { + "name": "id", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "ID", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null, + "args": [ + + ] + }, + { + "name": "key", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null, + "args": [ + + ] + }, + { + "name": "updatedAt", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "ISO8601DateTime", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null, + "args": [ + + ] + }, + { + "name": "value", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null, + "args": [ + + ] + } + ], + "inputFields": null, + "enumValues": null + }, + { + "kind": "INPUT_OBJECT", + "name": "CustomerMetadataInput", + "description": null, + "interfaces": null, + "possibleTypes": null, + "fields": null, + "inputFields": [ + { + "name": "id", + "description": null, + "type": { + "kind": "SCALAR", + "name": "ID", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "key", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "value", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "displayInInvoice", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + } + ], + "enumValues": null + }, + { + "kind": "OBJECT", + "name": "CustomerPortalWallet", + "description": "CustomerPortalWallet", + "interfaces": [ + + ], + "possibleTypes": null, + "fields": [ + { + "name": "balanceCents", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "BigInt", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null, + "args": [ + + ] + }, + { + "name": "consumedAmountCents", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "BigInt", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null, + "args": [ + + ] + }, + { + "name": "consumedCredits", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Float", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null, + "args": [ + + ] + }, + { + "name": "creditsBalance", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Float", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null, + "args": [ + + ] + }, + { + "name": "creditsOngoingBalance", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Float", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null, + "args": [ + + ] + }, + { + "name": "currency", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "ENUM", + "name": "CurrencyEnum", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null, + "args": [ + + ] + }, + { + "name": "expirationAt", + "description": null, + "type": { + "kind": "SCALAR", + "name": "ISO8601DateTime", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null, + "args": [ + + ] + }, + { + "name": "id", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "ID", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null, + "args": [ + + ] + }, + { + "name": "name", + "description": null, + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null, + "args": [ + + ] + }, + { + "name": "ongoingBalanceCents", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "BigInt", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null, + "args": [ + + ] + }, + { + "name": "ongoingUsageBalanceCents", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "BigInt", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null, + "args": [ + + ] + }, + { + "name": "rateAmount", "description": null, "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "SCALAR", - "name": "ID", + "name": "Float", "ofType": null } }, @@ -13234,38 +13778,33 @@ "args": [ ] - } - ], - "inputFields": null, - "enumValues": null - }, - { - "kind": "INPUT_OBJECT", - "name": "CustomerBillingConfigurationInput", - "description": null, - "interfaces": null, - "possibleTypes": null, - "fields": null, - "inputFields": [ + }, { - "name": "documentLocale", + "name": "status", "description": null, "type": { - "kind": "SCALAR", - "name": "String", - "ofType": null + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "ENUM", + "name": "WalletStatusEnum", + "ofType": null + } }, - "defaultValue": null, "isDeprecated": false, - "deprecationReason": null + "deprecationReason": null, + "args": [ + + ] } ], + "inputFields": null, "enumValues": null }, { "kind": "OBJECT", - "name": "CustomerCollection", - "description": "CustomerCollection type", + "name": "CustomerPortalWalletCollection", + "description": "CustomerPortalWalletCollection type", "interfaces": [ ], @@ -13273,7 +13812,7 @@ "fields": [ { "name": "collection", - "description": "A collection of paginated CustomerCollection", + "description": "A collection of paginated CustomerPortalWalletCollection", "type": { "kind": "NON_NULL", "name": null, @@ -13285,7 +13824,7 @@ "name": null, "ofType": { "kind": "OBJECT", - "name": "Customer", + "name": "CustomerPortalWallet", "ofType": null } } @@ -13321,7 +13860,7 @@ }, { "kind": "OBJECT", - "name": "CustomerMetadata", + "name": "CustomerPortalWalletTransaction", "description": null, "interfaces": [ @@ -13329,14 +13868,14 @@ "possibleTypes": null, "fields": [ { - "name": "createdAt", + "name": "amount", "description": null, "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "SCALAR", - "name": "ISO8601DateTime", + "name": "String", "ofType": null } }, @@ -13347,14 +13886,14 @@ ] }, { - "name": "displayInInvoice", + "name": "createdAt", "description": null, "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "SCALAR", - "name": "Boolean", + "name": "ISO8601DateTime", "ofType": null } }, @@ -13365,14 +13904,14 @@ ] }, { - "name": "id", + "name": "creditAmount", "description": null, "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "SCALAR", - "name": "ID", + "name": "String", "ofType": null } }, @@ -13383,14 +13922,14 @@ ] }, { - "name": "key", + "name": "id", "description": null, "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "SCALAR", - "name": "String", + "name": "ID", "ofType": null } }, @@ -13401,14 +13940,28 @@ ] }, { - "name": "updatedAt", + "name": "settledAt", + "description": null, + "type": { + "kind": "SCALAR", + "name": "ISO8601DateTime", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null, + "args": [ + + ] + }, + { + "name": "status", "description": null, "type": { "kind": "NON_NULL", "name": null, "ofType": { - "kind": "SCALAR", - "name": "ISO8601DateTime", + "kind": "ENUM", + "name": "WalletTransactionStatusEnum", "ofType": null } }, @@ -13419,14 +13972,14 @@ ] }, { - "name": "value", + "name": "transactionStatus", "description": null, "type": { "kind": "NON_NULL", "name": null, "ofType": { - "kind": "SCALAR", - "name": "String", + "kind": "ENUM", + "name": "WalletTransactionTransactionStatusEnum", "ofType": null } }, @@ -13435,80 +13988,116 @@ "args": [ ] - } - ], - "inputFields": null, - "enumValues": null - }, - { - "kind": "INPUT_OBJECT", - "name": "CustomerMetadataInput", - "description": null, - "interfaces": null, - "possibleTypes": null, - "fields": null, - "inputFields": [ + }, { - "name": "id", + "name": "transactionType", "description": null, "type": { - "kind": "SCALAR", - "name": "ID", - "ofType": null + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "ENUM", + "name": "WalletTransactionTransactionTypeEnum", + "ofType": null + } }, - "defaultValue": null, "isDeprecated": false, - "deprecationReason": null + "deprecationReason": null, + "args": [ + + ] }, { - "name": "key", + "name": "updatedAt", "description": null, "type": { "kind": "NON_NULL", "name": null, "ofType": { "kind": "SCALAR", - "name": "String", + "name": "ISO8601DateTime", "ofType": null } }, - "defaultValue": null, "isDeprecated": false, - "deprecationReason": null + "deprecationReason": null, + "args": [ + + ] }, { - "name": "value", + "name": "wallet", "description": null, + "type": { + "kind": "OBJECT", + "name": "CustomerPortalWallet", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null, + "args": [ + + ] + } + ], + "inputFields": null, + "enumValues": null + }, + { + "kind": "OBJECT", + "name": "CustomerPortalWalletTransactionCollection", + "description": "CustomerPortalWalletTransactionCollection type", + "interfaces": [ + + ], + "possibleTypes": null, + "fields": [ + { + "name": "collection", + "description": "A collection of paginated CustomerPortalWalletTransactionCollection", "type": { "kind": "NON_NULL", "name": null, "ofType": { - "kind": "SCALAR", - "name": "String", - "ofType": null + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "CustomerPortalWalletTransaction", + "ofType": null + } + } } }, - "defaultValue": null, "isDeprecated": false, - "deprecationReason": null + "deprecationReason": null, + "args": [ + + ] }, { - "name": "displayInInvoice", - "description": null, + "name": "metadata", + "description": "Pagination Metadata for navigating the Pagination", "type": { "kind": "NON_NULL", "name": null, "ofType": { - "kind": "SCALAR", - "name": "Boolean", + "kind": "OBJECT", + "name": "CollectionMetadata", "ofType": null } }, - "defaultValue": null, "isDeprecated": false, - "deprecationReason": null + "deprecationReason": null, + "args": [ + + ] } ], + "inputFields": null, "enumValues": null }, { @@ -22295,6 +22884,35 @@ } ] }, + { + "name": "createCustomerPortalWalletTransaction", + "description": "Creates a new Customer Wallet Transaction from Customer Portal", + "type": { + "kind": "OBJECT", + "name": "CustomerPortalWalletTransactionCollection", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null, + "args": [ + { + "name": "input", + "description": "Parameters for CreateCustomerPortalWalletTransaction", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "INPUT_OBJECT", + "name": "CreateCustomerPortalWalletTransactionInput", + "ofType": null + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + } + ] + }, { "name": "createCustomerWallet", "description": "Creates a new Customer Wallet", @@ -29803,6 +30421,47 @@ ] }, + { + "name": "customerPortalWallets", + "description": "Query wallets", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "CustomerPortalWalletCollection", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null, + "args": [ + { + "name": "limit", + "description": null, + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "page", + "description": null, + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + } + ] + }, { "name": "customerUsage", "description": "Query the usage of the customer on the current billing period", diff --git a/spec/graphql/mutations/customer_portal/wallet_transactions/create_spec.rb b/spec/graphql/mutations/customer_portal/wallet_transactions/create_spec.rb new file mode 100644 index 000000000000..c81c0b7e25b6 --- /dev/null +++ b/spec/graphql/mutations/customer_portal/wallet_transactions/create_spec.rb @@ -0,0 +1,58 @@ +# frozen_string_literal: true + +require "rails_helper" + +RSpec.describe Mutations::CustomerPortal::WalletTransactions::Create, type: :graphql do + let(:wallet) { create(:wallet, balance: 10.0, credits_balance: 10.0) } + + let(:mutation) do + <<-GQL + mutation($input: CreateCustomerPortalWalletTransactionInput!) { + createCustomerPortalWalletTransaction(input: $input) { + collection { id, status, amount } + } + } + GQL + end + + before do + wallet + end + + it_behaves_like "requires a customer portal user" + + it "creates a wallet transaction", :aggregate_failures do + result = execute_graphql( + customer_portal_user: wallet.customer, + query: mutation, + variables: { + input: { + walletId: wallet.id, + paidCredits: "5.00" + } + } + ) + + result_data = result["data"]["createCustomerPortalWalletTransaction"] + + expect(result_data["collection"].count).to eq 1 + expect(result_data["collection"].first["status"]).to eq "pending" + expect(result_data["collection"].first["amount"]).to eq "5.0" + end + + context "without customer portal user" do + it "returns an error" do + result = execute_graphql( + query: mutation, + variables: { + input: { + walletId: wallet.id, + paidCredits: "5.00" + } + } + ) + + expect_unauthorized_error(result) + end + end +end diff --git a/spec/graphql/resolvers/customer_portal/wallets_resolver_spec.rb b/spec/graphql/resolvers/customer_portal/wallets_resolver_spec.rb new file mode 100644 index 000000000000..20ab3c21d282 --- /dev/null +++ b/spec/graphql/resolvers/customer_portal/wallets_resolver_spec.rb @@ -0,0 +1,56 @@ +# frozen_string_literal: true + +require "rails_helper" + +RSpec.describe Resolvers::CustomerPortal::WalletsResolver, type: :graphql do + let(:query) do + <<~GQL + query { + customerPortalWallets { + collection { + id + name + currency + } + } + } + GQL + end + + let(:membership) { create(:membership) } + let(:organization) { membership.organization } + let(:customer) { create(:customer, organization:) } + let(:wallet) { create(:wallet, organization:, customer:) } + + before do + wallet + + create(:wallet, status: :terminated, customer:, organization:) + end + + it_behaves_like "requires a customer portal user" + + it "returns a list of active wallets", :aggregate_failures do + result = execute_graphql( + customer_portal_user: customer, + query: + ) + + wallets_response = result["data"]["customerPortalWallets"] + + expect(wallets_response["collection"].count).to eq(customer.wallets.active.count) + expect(wallets_response["collection"].first["id"]).to eq(wallet.id) + expect(wallets_response["collection"].first["name"]).to eq(wallet.name) + expect(wallets_response["collection"].first["currency"]).to eq(wallet.currency) + end + + context "without customer portal user" do + it "returns an error" do + result = execute_graphql( + query: + ) + + expect_unauthorized_error(result) + end + end +end diff --git a/spec/graphql/types/customer_portal/wallet_transactions/object_spec.rb b/spec/graphql/types/customer_portal/wallet_transactions/object_spec.rb new file mode 100644 index 000000000000..1c9fd258eae6 --- /dev/null +++ b/spec/graphql/types/customer_portal/wallet_transactions/object_spec.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +require "rails_helper" + +RSpec.describe Types::CustomerPortal::WalletTransactions::Object do + subject { described_class } + + it { is_expected.to have_field(:id).of_type("ID!") } + + it { is_expected.to have_field(:wallet).of_type("CustomerPortalWallet") } + + it { is_expected.to have_field(:amount).of_type("String!") } + it { is_expected.to have_field(:credit_amount).of_type("String!") } + it { is_expected.to have_field(:status).of_type("WalletTransactionStatusEnum!") } + it { is_expected.to have_field(:transaction_status).of_type("WalletTransactionTransactionStatusEnum!") } + it { is_expected.to have_field(:transaction_type).of_type("WalletTransactionTransactionTypeEnum!") } + + it { is_expected.to have_field(:created_at).of_type("ISO8601DateTime!") } + it { is_expected.to have_field(:settled_at).of_type("ISO8601DateTime") } + it { is_expected.to have_field(:updated_at).of_type("ISO8601DateTime!") } +end diff --git a/spec/graphql/types/customer_portal/wallets/object_spec.rb b/spec/graphql/types/customer_portal/wallets/object_spec.rb new file mode 100644 index 000000000000..434b66ed3336 --- /dev/null +++ b/spec/graphql/types/customer_portal/wallets/object_spec.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +require "rails_helper" + +RSpec.describe Types::CustomerPortal::Wallets::Object do + subject { described_class } + + it { is_expected.to have_field(:id).of_type("ID!") } + + it { is_expected.to have_field(:currency).of_type("CurrencyEnum!") } + it { is_expected.to have_field(:expiration_at).of_type("ISO8601DateTime") } + it { is_expected.to have_field(:name).of_type("String") } + it { is_expected.to have_field(:status).of_type("WalletStatusEnum!") } + + it { is_expected.to have_field(:balance_cents).of_type("BigInt!") } + it { is_expected.to have_field(:consumed_amount_cents).of_type("BigInt!") } + it { is_expected.to have_field(:consumed_credits).of_type("Float!") } + it { is_expected.to have_field(:credits_balance).of_type("Float!") } + it { is_expected.to have_field(:credits_ongoing_balance).of_type("Float!") } + it { is_expected.to have_field(:ongoing_balance_cents).of_type("BigInt!") } + it { is_expected.to have_field(:ongoing_usage_balance_cents).of_type("BigInt!") } + it { is_expected.to have_field(:rate_amount).of_type("Float!") } +end