diff --git a/.changeset/tidy-comics-five.md b/.changeset/tidy-comics-five.md new file mode 100644 index 000000000..0ae5d1f59 --- /dev/null +++ b/.changeset/tidy-comics-five.md @@ -0,0 +1,7 @@ +--- +'@voucherify/sdk': minor +--- + +Add support for few endpoints of Loyalties API. +- Added support for new endpoints: `GET /loyalties/members/{memberId}`, `GET /loyalties/members/{memberId}/activities`, `GET /loyalties/members/{memberId}/rewards`, `POST /loyalties/{campaignId}/members/{memberId}/transfers`, `GET /loyalties/{campaignId}/members/{memberId}/points-expiration`, `GET /loyalties/members/{memberId}/transactions`, `GET /loyalties/{campaignId}/members/{memberId}/transactions`, `POST /loyalties/members/{memberId}/transactions/export` and `POST /loyalties/{campaignId}/members/{memberId}/transactions/export` [(examples of usage available in readme.md)](..%2F..%2Fpackages%2Fsdk%2FREADME.md) +- New exported types/interfaces: `LoyaltiesTransferPointsResponseBody`, `LoyaltiesTransferPointsRequestBody`, `LoyaltiesListMemberRewardsRequestQuery`, `LoyaltiesListMemberRewardsResponseBody`, `LoyaltiesGetPointsExpirationRequestQuery`, `LoyaltiesGetPointsExpirationResponseBody`, `LoyaltiesListCardTransactionsRequestQuery`, `LoyaltiesListCardTransactionsResponseBody`, `LoyaltiesExportCardTransactionsRequestBody`, `LoyaltiesExportCardTransactionsResponseBody`, `LoyaltiesAddOrRemoveCardBalanceRequestBody`, `LoyaltiesAddOrRemoveCardBalanceResponseBody`, `LoyaltyCardTransaction`, `SimpleLoyaltyVoucher`, `LoyaltiesTransferPoints`, `LoyaltyCardTransactionsFields`, `LoyaltyCardTransactionsType`, `Reward`, `RewardTypeCampaign`, `RewardTypeCoin`, `RewardTypeMaterial`, `RewardType`, `RewardAssignment` diff --git a/packages/sdk/README.md b/packages/sdk/README.md index 30a1292b8..6536292e6 100644 --- a/packages/sdk/README.md +++ b/packages/sdk/README.md @@ -990,10 +990,15 @@ Methods are provided within `client.loyalties.*` namespace. - [List Loyalty Program Earning Rules](#list-loyalty-program-earning-rules) - [Create Loyalty Program Member](#create-loyalty-program-member) - [Get Loyalty Program Member](#get-loyalty-program-member) -- [List Loyalty Program Members](#list-loyalty-members) +- [List Loyalty Program Members](#list-loyalty-program-members) - [Get Loyalty Program Member Activities](#get-loyalty-program-member-activities) -- [Add Loyalty Card Balance](#add-loyalty-card-balance) +- [Add Or Remove Loyalty Card Balance](#add-or-remove-loyalty-card-balance) +- [[Deprecated] Add Loyalty Card Balance](#add-loyalty-card-balance) +- [Transfer Loyalty Points](#transfer-loyalty-points) +- [Get Loyalty Points Expiration](#get-loyalty-points-expiration) - [Redeem Loyalty Card](#redeem-loyalty-card) +- [List Loyalty Card Transactions](#list-loyalty-card-transactions) +- [[Export Loyalty Card Transactions](#export-loyalty-card-transactions) #### [Create Loyalty Program](https://docs.voucherify.io/reference/create-loyalty-program) @@ -1102,6 +1107,8 @@ client.loyalties.createMember(campaignId, member) #### [Get Loyalty Program Member](https://docs.voucherify.io/reference/get-member) +Depending on the parameters, this method sends requests to [v1/loyalties/members/{memberId}](https://docs.voucherify.io/reference/get-member) or [v1/loyalties/{campaignId}/members/{memberId}](https://docs.voucherify.io/reference/get-member-1) API endpoint + ```javascript client.loyalties.getMember(campaignId, memberId) ``` @@ -1115,13 +1122,36 @@ client.loyalties.listMembers(campaignId, params) #### [Get Loyalty Program Member Activities](https://docs.voucherify.io/reference#get-member-activities) +Depending on the parameters, this method sends requests to [v1/loyalties/members/{memberId}/activities](https://docs.voucherify.io/reference/get-member-activities) or [v1/loyalties/{campaignId}/members/{memberId}/activities](https://docs.voucherify.io/reference/get-member-activities-1) API endpoint + ```javascript client.loyalties.getMemberActivities(campaignId, memberId) ``` `memberId` referrers to Loyalty Card code. -#### [Add Loyalty Card Balance](https://docs.voucherify.io/reference/add-loyalty-card-balance) + +#### [List Member Rewards](https://docs.voucherify.io/reference/list-member-rewards) + +```javascript +client.loyalties.listMemberRewards(memberId, params) +``` + +`memberId` referrers to Loyalty Card code. + +--- + +#### [Add Or Remove Loyalty Card Balance](https://docs.voucherify.io/reference/add-remove-loyalty-card-balance) + +Depending on the parameters, this method sends requests to [v1/loyalties/members/{memberId}/balance](https://docs.voucherify.io/reference/add-remove-loyalty-card-balance) or [v1/loyalties/{campaignId}/members/{memberId}/balance](https://docs.voucherify.io/reference/add-remove-loyalty-card-balance-1) API endpoint + +```javascript +client.loyalties.addOrRemoveCardBalance(memberId, balance, campaignId) +``` + +`memberId` referrers to Loyalty Card code. + +#### [Add Loyalty Card Balance](https://docs.voucherify.io/reference/add-loyalty-card-balance-1) ```javascript client.loyalties.addPoints(campaignId, memberId, balance) @@ -1129,6 +1159,26 @@ client.loyalties.addPoints(campaignId, memberId, balance) `memberId` referrers to Loyalty Card code. +#### [Transfer Loyalty Points](https://docs.voucherify.io/reference/transfer-points) + +```javascript +client.loyalties.transferPoints(campaignId, memberId, transferLoyaltyPoints) +``` + +`memberId` referrers to Loyalty Card code. + +--- + +#### [Get Loyalty Points Expiration](https://docs.voucherify.io/reference/get-points-expiration) + +```javascript +client.loyalties.getPointsExpiration(campaignId, memberId) +``` + +`memberId` referrers to Loyalty Card code. + +--- + #### [Redeem Loyalty Card] ```javascript @@ -1141,6 +1191,30 @@ When redeeming reward with type `COIN` you need to provide additional `order` ob --- +#### [List Loyalty Card Transactions](https://docs.voucherify.io/reference/list-loyalty-card-transactions) + +Depending on the parameters, this method sends requests to [v1/loyalties/members/{memberId}/transactions](https://docs.voucherify.io/reference/list-loyalty-card-transactions) or [v1/loyalties/{campaignId}/members/{memberId}/transactions](https://docs.voucherify.io/reference/list-loyalty-card-transactions-1) API endpoint + +```javascript +client.loyalties.listCardTransactions(memberId, campaignId, params) +``` + +`memberId` referrers to Loyalty Card code. + +--- + +#### [Export Loyalty Card Transactions](https://docs.voucherify.io/reference/export-loyalty-card-transactions) + +Depending on the parameters, this method sends requests to [v1/loyalties/members/{memberId}/transactions/export](https://docs.voucherify.io/reference/export-loyalty-card-transactions) or [v1/loyalties/{campaignId}/members/{memberId}/transactions/export](https://docs.voucherify.io/reference/export-loyalty-card-transactions-1) API endpoint + +```javascript +client.loyalties.exportCardTransactions(memberId, campaignId, params) +``` + +`memberId` referrers to Loyalty Card code. + +--- + ### Segments Methods are provided within `client.segments.*` namespace. diff --git a/packages/sdk/src/Loyalties.ts b/packages/sdk/src/Loyalties.ts index 5925544be..7f64e5a44 100644 --- a/packages/sdk/src/Loyalties.ts +++ b/packages/sdk/src/Loyalties.ts @@ -142,20 +142,51 @@ export class Loyalties { } /** * @see https://docs.voucherify.io/reference/get-member + * @see https://docs.voucherify.io/reference/get-member-1 */ - public getMember(campaignId: string, memberId: string) { - return this.client.get(`/loyalties/${encode(campaignId)}/members/${memberId}`) + public getMember(campaignId: string | null, memberId: string) { + return this.client.get( + campaignId ? `/loyalties/${encode(campaignId)}/members/${memberId}` : `/loyalties/members/${memberId}`, + ) } /** * @see https://docs.voucherify.io/reference/get-member-activities + * @see https://docs.voucherify.io/reference/get-member-activities-1 */ - public getMemberActivities(campaignId: string, memberId: string) { + public getMemberActivities(campaignId: string | null, memberId: string) { return this.client.get( - `/loyalties/${encode(campaignId)}/members/${memberId}/activities`, + campaignId + ? `/loyalties/${encode(campaignId)}/members/${memberId}/activities` + : `/loyalties/members/${memberId}/activities`, + ) + } + /** + * @see https://docs.voucherify.io/reference/list-member-rewards + */ + public listMemberRewards(memberId: string, params?: T.LoyaltiesListMemberRewardsRequestQuery) { + return this.client.get( + `/loyalties/members/${encode(memberId)}/rewards`, + params, ) } /** - * @see https://docs.voucherify.io/reference/add-loyalty-card-balance + * @see https://docs.voucherify.io/reference/add-remove-loyalty-card-balance + * @see https://docs.voucherify.io/reference/add-remove-loyalty-card-balance-1 + */ + public addOrRemoveCardBalance( + memberId: string, + balance: T.LoyaltiesAddOrRemoveCardBalanceRequestBody, + campaignId?: string, + ) { + return this.client.post( + campaignId + ? `/loyalties/${encode(campaignId)}/members/${memberId}/balance` + : `/loyalties/members/${memberId}/balance`, + balance, + ) + } + /** + * @see https://docs.voucherify.io/reference/add-remove-loyalty-card-balance-1 */ public addPoints(campaignId: string, memberId: string, balance: T.LoyaltiesAddPoints) { return this.client.post( @@ -163,6 +194,32 @@ export class Loyalties { balance, ) } + /** + * @see https://docs.voucherify.io/reference/transfer-points + */ + public transferPoints( + campaignId: string, + memberId: string, + loyaltiesTransferPoints: T.LoyaltiesTransferPointsRequestBody, + ) { + return this.client.post( + `/loyalties/${encode(campaignId)}/members/${encode(memberId)}/transfers`, + loyaltiesTransferPoints, + ) + } + /** + * @see https://docs.voucherify.io/reference/get-points-expiration + */ + public getPointsExpiration( + campaignId: string, + memberId: string, + params?: T.LoyaltiesGetPointsExpirationRequestQuery, + ) { + return this.client.get( + `/loyalties/${encode(campaignId)}/members/${memberId}/points-expiration`, + params, + ) + } /** * @see https://docs.voucherify.io/reference/redeem-loyalty-card */ @@ -172,4 +229,36 @@ export class Loyalties { params, ) } + /** + * @see https://docs.voucherify.io/reference/list-loyalty-card-transactions + * @see https://docs.voucherify.io/reference/list-loyalty-card-transactions-1 + */ + public listCardTransactions( + memberId: string, + campaignId: string | null, + params?: T.LoyaltiesListCardTransactionsRequestQuery, + ) { + return this.client.get( + campaignId + ? `/loyalties/${encode(campaignId)}/members/${encode(memberId)}/transactions` + : `/loyalties/members/${encode(memberId)}/transactions`, + params, + ) + } + /** + * @see https://docs.voucherify.io/reference/export-loyalty-card-transactions + * @see https://docs.voucherify.io/reference/export-loyalty-card-transactions-1 + */ + public exportCardTransactions( + memberId: string, + campaignId: string | null, + params: T.LoyaltiesExportCardTransactionsRequestBody = {}, + ) { + return this.client.post( + campaignId + ? `/loyalties/${encode(campaignId)}/members/${encode(memberId)}/transactions/export` + : `/loyalties/members/${encode(memberId)}/transactions/export`, + params, + ) + } } diff --git a/packages/sdk/src/types/Categories.ts b/packages/sdk/src/types/Categories.ts index 3681d3f7c..55feffef6 100644 --- a/packages/sdk/src/types/Categories.ts +++ b/packages/sdk/src/types/Categories.ts @@ -27,3 +27,14 @@ export type ResponseUpdateCategory = ResponseCreateCategory & { } export type UpdateCategoryRequest = CreateCategory + +// domain types + +export type Category = { + id: string + name: string + hierarchy: number + created_at: string + updated_at?: string + object: 'category' +} diff --git a/packages/sdk/src/types/Loyalties.ts b/packages/sdk/src/types/Loyalties.ts index 346da9882..2bc2198fc 100644 --- a/packages/sdk/src/types/Loyalties.ts +++ b/packages/sdk/src/types/Loyalties.ts @@ -4,7 +4,10 @@ import { ProductsCreateResponse, ProductsCreateSkuResponse } from './Products' import { SimpleCustomer } from './Customers' import { ValidationRulesCreateAssignmentResponse } from './ValidationRules' import { VouchersResponse } from './Vouchers' +import { Reward, RewardAssignment } from './Rewards' +import { Category } from './Categories' +// Legacy code interface LoyaltiesVoucher { code_config?: { length?: number @@ -507,7 +510,152 @@ export interface LoyaltyPointsTransfer { points: number } -// 0 Level types +// 0-level types + +export type LoyaltiesTransferPointsResponseBody = { + id: string + code: string + campaign: string + campaign_id: string + category: string | null + category_id: string | null + categories: Category[] + type: 'LOYALTY_CARD' + loyalty_card: { + points: number + balance: number + next_expiration_date?: string + next_expiration_points?: number + } + start_date: string | null + expiration_date: string | null + validity_timeframe: { + interval?: string + duration?: string + } | null + validity_day_of_week: number[] | null + publish?: { + object: 'list' + count?: number + entries?: string[] + url?: string + } + redemption?: { + quantity: number | null + redeemed_points?: number + redeemed_quantity?: number + redemption_entries?: string[] + object?: 'list' + url?: string + } + active: boolean + additional_info: string | null + metadata: Record + is_referral_code: boolean + holder_id?: string + updated_at?: string + created_at: string +} + +export type LoyaltiesTransferPointsRequestBody = LoyaltiesTransferPoints[] + +export interface LoyaltiesListMemberRewardsRequestQuery { + affordable_only?: boolean + limit?: number + page?: number +} + +export interface LoyaltiesListMemberRewardsResponseBody { + object: 'list' + data_ref: 'data' + data: { reward: Reward; assignment: RewardAssignment; object: 'loyalty_reward' }[] + total: number +} + +export interface LoyaltiesGetPointsExpirationRequestQuery { + limit?: number + page?: number +} + +export interface LoyaltiesGetPointsExpirationResponseBody { + object: 'list' + data_ref: 'data' + data: { + id: string + voucher_id: string + campaign_id: string + bucket: { + total_points: number + } + created_at: string + status: string + expires_at: string + updated_at?: string + object: 'loyalty_points_bucket' + }[] + total: number +} + +export interface LoyaltiesListCardTransactionsRequestQuery { + limit?: number + page?: number +} + +export interface LoyaltiesListCardTransactionsResponseBody { + object: 'list' + data_ref: 'data' + data: LoyaltyCardTransaction[] + has_more: boolean +} + +export interface LoyaltiesExportCardTransactionsRequestBody { + order?: 'created_at' | '-created_at' + fields?: LoyaltyCardTransactionsFields[] +} + +export interface LoyaltiesExportCardTransactionsResponseBody { + id: string + object: 'export' + created_at: string + status: 'SCHEDULED' + channel: string + exported_object: 'voucher_transactions' + parameters: { + order?: string + fields?: LoyaltyCardTransactionsFields[] + filters: { + voucher_id: { + conditions: { + $in: [string] //memberId + } + } + } + } + result: null + user_id: null | string +} + +export interface LoyaltiesAddOrRemoveCardBalanceRequestBody { + points: number + expiration_date?: string //ISO-8601 + expiration_type?: PointsExpirationTypes + reason?: string + source_id?: string +} + +export interface LoyaltiesAddOrRemoveCardBalanceResponseBody { + points?: number + amount?: number + total: number + balance: number + type: 'LOYALTY_CARD' | 'GIFT_VOUCHER' + object: 'balance' + related_object: { + type: 'voucher' + id: string + } + operation_type?: 'MANUAL' | 'AUTOMATIC' +} export type LoyaltiesGetEarningRuleResponseBody = EarningRuleBase & { validation_rule_id: string | null @@ -525,7 +673,128 @@ export type LoyaltiesDisableEarningRulesResponseBody = EarningRuleBase & { active: false } -// Domain types +// domain types + +export type PointsExpirationTypes = 'PROGRAM_RULES' | 'CUSTOM_DATE' | 'NON_EXPIRING' + +export interface LoyaltyCardTransaction { + id: string + source_id: string | null + voucher_id: string + campaign_id: string + source: string | null + reason: string | null + type: LoyaltyCardTransactionsType + details: { + balance: { + type: 'loyalty_card' + total: number + object: 'balance' + points: number + balance: number + related_object: { + id: string + type: 'voucher' + } + } + order?: { + id: string + source_id: string + } + event?: { + id: string + type: string + } + earning_rule?: { + id: string + source: { + banner: string + } + } + segment?: { + id: string + name: string + } + loyalty_tier?: { + id: string + name: string + } + redemption?: { + id: string + } + rollback?: { + id: string + } + reward?: { + id: string + name: string + } + custom_event?: { + id: string + type: string + } + event_schema?: { + id: string + name: string + } + source_voucher?: SimpleLoyaltyVoucher + destination_voucher?: SimpleLoyaltyVoucher + } + related_transaction_id: string | null + created_at: string +} + +export interface SimpleLoyaltyVoucher { + id: string + code: string + loyalty_card: { + points: number + balance: number + next_expiration_date?: string + next_expiration_points?: string + } + type: 'LOYALTY_CARD' + campaign: string + campaign_id: string + is_referral_code?: boolean + holder_id?: string + referrer_id?: string + created_at?: string + object: 'voucher' +} + +export interface LoyaltiesTransferPoints { + code: string + points: number + reason?: string + source_id: string +} + +export type LoyaltyCardTransactionsFields = + | 'id' + | 'campaign_id' + | 'voucher_id' + | 'type' + | 'source_id' + | 'reason' + | 'source' + | 'balance' + | 'amount' + | 'related_transaction_id' + | 'created_at' + | 'details' + +export type LoyaltyCardTransactionsType = + | 'POINTS_ACCRUAL' + | 'POINTS_CANCELLATION' + | 'POINTS_REDEMPTION' + | 'POINTS_REFUND' + | 'POINTS_ADDITION' + | 'POINTS_REMOVAL' + | 'POINTS_EXPIRATION' + | 'POINTS_TRANSFER_IN' + | 'POINTS_TRANSFER_OUT' + export interface EarningRuleBase { id: string created_at: string diff --git a/packages/sdk/src/types/Rewards.ts b/packages/sdk/src/types/Rewards.ts index eb1b03ae2..289421cd9 100644 --- a/packages/sdk/src/types/Rewards.ts +++ b/packages/sdk/src/types/Rewards.ts @@ -18,6 +18,7 @@ export interface RewardsResponse { } export type RewardsCreateResponse = RewardsResponse & RewardsTypeResponse +export type RewardsGetResponse = RewardsResponse & RewardsTypeResponse export interface RewardsListResponse { object: 'list' @@ -84,8 +85,6 @@ export type RewardsTypeResponse = export type RewardsCreate = Rewards & RewardsType -export type RewardsGetResponse = RewardsCreateResponse - export type RewardsUpdate = Omit & { id: string } export type RewardsUpdateResponse = RewardsCreateResponse @@ -137,3 +136,67 @@ export interface RewardRedemptionParams { assignment_id?: string id?: string } + +// domain types + +export type Reward = { + id: string + name?: string + stock?: string + redeemed?: string + attributes?: { + image_url?: string + description?: string + } + created_at: string + updated_at?: string + object: 'reward' +} & RewardType + +export interface RewardTypeCampaign { + type: 'CAMPAIGN' + parameters: { + campaign: { + id: string + balance?: number + type: 'DISCOUNT_COUPONS' | 'PROMOTION' | 'GIFT_VOUCHERS' | 'REFERRAL_PROGRAM' + } + } +} + +export interface RewardTypeCoin { + type: 'COIN' + parameters: { + coin: { + exchange_ratio: number + points_ratio?: number + } + } +} + +export interface RewardTypeMaterial { + type: 'MATERIAL' + parameters: { + product: { + id: string + sku: string | null + } + } +} + +export type RewardType = RewardTypeCampaign | RewardTypeCoin | RewardTypeMaterial + +export interface RewardAssignment { + id: string + reward_id: string + related_object_id?: string + related_object_type?: string + parameters?: { + loyalty?: { + points: number + } + } + created_at: string + updated_at?: string + object: 'reward_assignment' +} diff --git a/packages/sdk/test/loyalties.spec.ts b/packages/sdk/test/loyalties.spec.ts index 3305ce3ab..cd63283f7 100644 --- a/packages/sdk/test/loyalties.spec.ts +++ b/packages/sdk/test/loyalties.spec.ts @@ -1,5 +1,12 @@ import { voucherifyClient as client } from './client' import { generateRandomString } from './utils/generateRandomString' +import { + LoyaltiesCreateCampaignResponse, + LoyaltiesCreateMemberResponse, + LoyaltiesAddPointsResponse, + LoyaltiesListCardTransactionsResponseBody, +} from '@voucherify/sdk' +import { isoRegex } from './utils/isoRegex' describe('Loyalties API', () => { it('Create loyalty campaign, create earning rule and validate it', async () => { @@ -57,4 +64,76 @@ describe('Loyalties API', () => { } } }) + + let loyaltyCampaign: LoyaltiesCreateCampaignResponse, + loyaltiesMember: LoyaltiesCreateMemberResponse, + startBalance = 100, + addPoints = 88 + + it('Should create loyalties campaign and 1 member', async () => { + loyaltyCampaign = await client.loyalties.create({ + name: generateRandomString(), + voucher: { + type: 'LOYALTY_CARD', + loyalty_card: { + points: startBalance, + }, + }, + type: 'AUTO_UPDATE', + }) + loyaltiesMember = await client.loyalties.createMember(loyaltyCampaign.id, { + customer: { name: generateRandomString() }, + }) //member + }) + + it('Should add loyalties points to a member previously added', async () => { + const response = await client.loyalties.addOrRemoveCardBalance( + loyaltiesMember.code, + { points: addPoints }, + loyaltyCampaign.id, + ) + expect(response).toEqual({ + points: addPoints, + total: startBalance + addPoints, + balance: startBalance + addPoints, + type: 'loyalty_card', + object: 'balance', + related_object: { type: 'voucher', id: expect.stringMatching(/^v_.*/) }, + operation_type: 'MANUAL', + // use as `type` for typescript check + } as LoyaltiesAddPointsResponse) + }) + + it('Should list members activities', async () => { + const memberCardTransactions = await client.loyalties.listCardTransactions(loyaltiesMember.code, null) + expect(memberCardTransactions).toEqual({ + object: 'list', + data_ref: 'data', + data: [ + { + id: expect.stringMatching(/^vtx_.*/), + source_id: null, + voucher_id: expect.stringMatching(/^v_.*/), + campaign_id: expect.stringMatching(/^camp_.*/), + source: expect.stringMatching(/^Node.js.*/), + reason: null, + type: 'POINTS_ADDITION', + details: { + balance: { + type: 'loyalty_card', + total: startBalance + addPoints, + object: 'balance', + points: addPoints, + balance: startBalance + addPoints, + related_object: { id: expect.stringMatching(/^v_.*/), type: 'voucher' }, + }, + }, + related_transaction_id: null, + created_at: expect.stringMatching(isoRegex), + }, + ], + has_more: false, + // use as `type` for typescript check + } as LoyaltiesListCardTransactionsResponseBody) + }) })