Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
01f1585
feat: add promo code functionality for tariff plans, including previe…
Dobrunia Jun 11, 2026
328b77c
utm
Dobrunia Jun 12, 2026
e413993
chore: update @hawk.so/types dependency from 0.6.2 to 0.6.3 in packag…
Dobrunia Jun 12, 2026
571ef35
refactor: clean up code and improve component structure by removing u…
Dobrunia Jun 12, 2026
ee13486
feat: enhance payment processing by introducing structured promo data…
Dobrunia Jun 12, 2026
914e22c
feat: update previewPromoCode mutation to validate promo codes for wo…
Dobrunia Jun 13, 2026
a7ecf56
feat: enhance billing types by introducing PromoCodePreviewInput and …
Dobrunia Jun 13, 2026
3578bac
feat: add chargeAmount field to payment schema and update related com…
Dobrunia Jun 15, 2026
d5a0800
feat: rename and restructure promo code functionality to apply promo …
Dobrunia Jun 15, 2026
ddc3311
feat: rename and update promo code functionality to verify promo code…
Dobrunia Jun 15, 2026
2120868
refactor: simplify promo code pricing logic by consolidating return s…
Dobrunia Jun 15, 2026
d3b756d
lint fix
Dobrunia Jun 15, 2026
cf89380
feat: enhance payment queries and payload structure by adding detaile…
Dobrunia Jun 15, 2026
5a82755
fix
Dobrunia Jun 17, 2026
a601709
docs: update PaymentDetailsDialog.vue to clarify payment processing m…
Dobrunia Jun 17, 2026
c11a8f7
refactor: convert PromoCodeDialog to Composition API, streamline prop…
Dobrunia Jun 17, 2026
934db81
refactor: reorganize billing types and promo code logic by removing o…
Dobrunia Jun 17, 2026
7c25093
refactor: update promo code types and imports across billing and pric…
Dobrunia Jun 17, 2026
8f678cc
refactor: clarify chargeAmount description in billing queries and str…
Dobrunia Jun 17, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
"dependencies": {
"@codexteam/ui": "0.2.3",
"@hawk.so/javascript": "3.3.0",
"@hawk.so/types": "^0.5.9",
"@hawk.so/types": "^0.6.3",
"axios": "^1.12.2",
"codex-notifier": "^1.1.2",
"cssnano": "^7.1.1",
Expand Down
76 changes: 75 additions & 1 deletion schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,50 @@ input ComposePaymentInput {

"""Whether card should be saved for future recurrent payments"""
shouldSaveCard: Boolean

"""Promo code value entered by user"""
promoCode: String

"""UTM parameters captured when promo code was applied"""
promoUtm: UtmInput
}

"""Input for promo code verification"""
input VerifyPromoCodeInput {
"""Workspace id for which promo code is applied"""
workspaceId: ID!

"""Promo code value entered by user"""
value: String!
}

"""Promo code benefit type"""
enum PromoCodeBenefitType {
grant_plan
percent_discount
amount_discount
fixed_price
}

"""Verified promo code data for client-side price calculation"""
type VerifyPromoCodeResponse {
"""Normalized promo code value"""
value: String!

"""Benefit type"""
benefitType: PromoCodeBenefitType!

"""Discount percent for percent promos"""
percent: Int

"""Fixed price amount"""
amount: Int

"""Minimum final price after percent discount"""
minFinalPrice: Int

"""Plan ids this promo can be applied to"""
applicablePlanIds: [ID!]
}

"""Minimal plan info used in composePayment response"""
Expand All @@ -207,7 +251,7 @@ type ComposePaymentPlanInfo {
"""Plan name"""
name: String!

"""Monthly charge for plan"""
"""Monthly charge for plan (full tariff price)"""
monthlyCharge: Int!
}

Expand All @@ -219,6 +263,9 @@ type ComposePaymentResponse {
"""Selected plan info"""
plan: ComposePaymentPlanInfo!

"""Amount to charge for this payment (may differ from plan.monthlyCharge when promo is applied)"""
chargeAmount: Int!

"""True if only card linking validation payment is expected"""
isCardLinkOperation: Boolean!

Expand All @@ -230,6 +277,30 @@ type ComposePaymentResponse {

"""Next payment date (recurrent start)"""
nextPaymentDate: DateTime!

"""CloudPayments public id (merchant identifier for payment widget)"""
cloudPaymentsPublicId: String!

"""Applied promo code data"""
promo: ComposePaymentPromo
}

"""Promo data returned with composePayment"""
type ComposePaymentPromo {
"""Applied promo code id"""
id: ID!

"""Promo benefit type"""
benefitType: PromoCodeBenefitType!

"""Plan price before promo"""
originalAmount: Int!

"""Plan price after promo"""
finalAmount: Int!

"""Actual discount amount in money"""
discountAmount: Int
}

"""Confirmed member data in workspace"""
Expand Down Expand Up @@ -579,6 +650,9 @@ type Mutation {
"""Returns JSON data with payment link and initiate card attach procedure"""
attachCard(language: String): BillingSession!

"""Verifies promo code for workspace admin and returns benefit data"""
verifyPromoCode(input: VerifyPromoCodeInput!): VerifyPromoCodeResponse! @requireAdmin

"""Mutation marks event as visited for current user"""
visitEvent(
"""ID of project event is related to"""
Expand Down
76 changes: 44 additions & 32 deletions src/api/billing/index.ts
Original file line number Diff line number Diff line change
@@ -1,58 +1,70 @@
import { MUTATION_PAY_WITH_CARD, QUERY_BUSINESS_OPERATIONS, QUERY_COMPOSE_PAYMENT } from './queries';
import { MUTATION_PAY_WITH_CARD, MUTATION_VERIFY_PROMO_CODE, QUERY_BUSINESS_OPERATIONS, QUERY_COMPOSE_PAYMENT } from './queries';

Check warning on line 1 in src/api/billing/index.ts

View workflow job for this annotation

GitHub Actions / ESlint

Can't resolve './queries' in '/home/runner/work/hawk.garage/hawk.garage/src/api/billing'
import * as api from '../';

Check warning on line 2 in src/api/billing/index.ts

View workflow job for this annotation

GitHub Actions / ESlint

Can't resolve '../' in '/home/runner/work/hawk.garage/hawk.garage/src/api/billing'
import type { BusinessOperation } from '../../types/business-operation';

Check warning on line 3 in src/api/billing/index.ts

View workflow job for this annotation

GitHub Actions / ESlint

Can't resolve '../../types/business-operation' in '/home/runner/work/hawk.garage/hawk.garage/src/api/billing'
import { BeforePaymentPayload } from '@/types/before-payment-payload';
import type { Utm as UtmInput } from '@hawk.so/types';
import type { PromoCodeVerify, PromoCodeVerifyInput } from '@/types/promoCode';

Check warning on line 5 in src/api/billing/index.ts

View workflow job for this annotation

GitHub Actions / ESlint

Can't resolve '@/types/promoCode' in '/home/runner/work/hawk.garage/hawk.garage/src/api/billing'

/**
* Request business operations list for passed workspaces
* @param ids - ids of workspaces
*/
export async function getBusinessOperations(ids: string[]): Promise<BusinessOperation[]> {
return (await api.callOld(QUERY_BUSINESS_OPERATIONS, { ids })).businessOperations;
}
interface ComposePaymentInput {

Check warning on line 7 in src/api/billing/index.ts

View workflow job for this annotation

GitHub Actions / ESlint

Missing JSDoc comment
workspaceId: string;

Check warning on line 8 in src/api/billing/index.ts

View workflow job for this annotation

GitHub Actions / ESlint

Missing JSDoc comment
tariffPlanId: string;

Check warning on line 9 in src/api/billing/index.ts

View workflow job for this annotation

GitHub Actions / ESlint

Missing JSDoc comment
shouldSaveCard?: boolean;

Check warning on line 10 in src/api/billing/index.ts

View workflow job for this annotation

GitHub Actions / ESlint

Missing JSDoc comment

/**
* Data for processing payment with saved card
*/
export interface PayWithCardInput {
/**
* Checksum for payment validation
* Promo code value entered by user.
*/
checksum: string;
promoCode?: string;

/**
* Saved card id for payment
*/
cardId: string;
promoUtm?: UtmInput;
}

/**
* Is payment recurrent or not. If payment is recurrent, then the money will be debited every month
*/
export interface PayWithCardInput {
checksum: string;
cardId: string;
isRecurrent?: boolean;
}

/**
* Request business operations list for passed workspaces
* @param ids - ids of workspaces
*/
export async function getBusinessOperations(ids: string[]): Promise<BusinessOperation[]> {
const response = await api.call<{ businessOperations: BusinessOperation[] }>(QUERY_BUSINESS_OPERATIONS, { ids });

return response.data?.businessOperations || [];
}

/**
* Process payment via saved card
* @param input - data for payment processing
*/
export async function payWithCard(input: PayWithCardInput): Promise<unknown> {
return (await api.callOld(MUTATION_PAY_WITH_CARD, { input })).payWithCard.record;
const response = await api.call<{ payWithCard: { record: unknown } }>(MUTATION_PAY_WITH_CARD, { input });

return response.data?.payWithCard.record;
}

/**
* Prepare payment data (compose payment)
* @param workspaceId - id of workspace
* @param tariffPlanId - id of plan
* @param shouldSaveCard - whether to save the card
* @param input - compose payment input
*/
export async function composePayment(
workspaceId: string,
tariffPlanId: string,
shouldSaveCard = false
input: ComposePaymentInput
): Promise<unknown> {
return await api.call(QUERY_COMPOSE_PAYMENT, {
input: { workspaceId,
tariffPlanId,
shouldSaveCard },
input,
});
}

/**
* Verify promo code and return validated benefit data.
* @param input - promo code input
*/
export async function verifyPromoCode(input: PromoCodeVerifyInput): Promise<PromoCodeVerify> {
const response = await api.call<{ verifyPromoCode: PromoCodeVerify }>(MUTATION_VERIFY_PROMO_CODE, { input });

if (!response.data) {
throw new Error('Empty promo code response');
}

return response.data.verifyPromoCode;
}
41 changes: 40 additions & 1 deletion src/api/billing/queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ export const QUERY_BUSINESS_OPERATIONS = `
// language=GraphQL
/**
* Mutation for processing payment with savedcard
*
* Sends: checksum, cardId, isRecurrent
*/
export const MUTATION_PAY_WITH_CARD = `
mutation PayWithCard($input: PayWithCardInput!) {
Expand All @@ -66,7 +68,16 @@ export const MUTATION_PAY_WITH_CARD = `

// language=GraphQL
/**
* Query to prepare payment data (GraphQL version of composePayment)
* Prepare payment data before opening CloudPayments widget.
*
* Sends (ComposePaymentInput):
* workspaceId, tariffPlanId, shouldSaveCard?, promoCode?, promoUtm?
*
* Receives (calculated on server):
* chargeAmount — actual amount to charge (card validation, promo price, or full plan price)
* plan.monthlyCharge — full tariff price (for recurrent)
* promo — server-validated discount breakdown for UI
* checksum, invoiceId, etc.
*/
export const QUERY_COMPOSE_PAYMENT = `
query ComposePayment($input: ComposePaymentInput!) {
Expand All @@ -77,11 +88,39 @@ export const QUERY_COMPOSE_PAYMENT = `
name
monthlyCharge
}
chargeAmount
isCardLinkOperation
currency
checksum
nextPaymentDate
cloudPaymentsPublicId
promo {
originalAmount
finalAmount
}
}
}
`;

// language=GraphQL
/**
* Verify promo code before payment (UI discount preview).
*
* Sends (VerifyPromoCodeInput):
* workspaceId, value
*
* Receives (benefit data for client-side price calculation):
* value, benefitType, percent?, amount?, minFinalPrice?, applicablePlanIds?
*/
export const MUTATION_VERIFY_PROMO_CODE = `
mutation VerifyPromoCode($input: VerifyPromoCodeInput!) {
verifyPromoCode(input: $input) {
value
benefitType
percent
amount
minFinalPrice
applicablePlanIds
}
}
`;
Loading
Loading