8000 ADCIO-2907) feat: pub-sub add-to-cart by hanchchch · Pull Request #38 · corca-ai/adcio.js · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content
This repository was archived by the owner on Oct 24, 2024. It is now read-only.

ADCIO-2907) feat: pub-sub add-to-cart #38

Open
wants to merge 7 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 1 addition & 4 deletions mock/handlers.ts
10000
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,7 @@ import {
} from "./constants";
import { suggestionResponse } from "./suggestion.demo";
import { SuggestionRequestDto } from "../src/api/controller/v1";
import {
ERROR_CODE,
PLACEMENT_ERROR_MESSAGE,
} from "../src/lib/constants/error";
import { ERROR_CODE, PLACEMENT_ERROR_MESSAGE } from "../src/lib/error";
import { APIError } from "../src/lib/error";

const RECEIVER_API_BASE_URL = "https://receiver.adcio.ai";
Expand Down
40 changes: 10 additions & 30 deletions src/adcio/adcio.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { AdcioAnalytics } from "lib/analytics/analytics";
import { AddToCartEvent, AdcioAnalytics } from "lib/analytics";
import { ClientAPI } from "lib/client-api/client-api.interface";
import { AdcioCore } from "lib/core";
import { AdcioImpressionObserver } from "lib/impression-observer/impression-observer";
import { AdcioPlacement } from "lib/placement/placement";
import { CartsStorage } from "lib/storage/tracker-storage";
import {
AdcioConfig,
AdcioParams,
Expand Down Expand Up @@ -74,6 +73,12 @@ export class Adcio {
);
}

private listenAddToCart() {
AddToCartEvent.listen((event) => {
this.onAddToCart(event);
});
}

// AdcioPlacement
public async fetchPlacements(params: AdcioFetchPlacementsParams) {
return this.adcioPlacement.fetchPlacements(params);
Expand Down Expand Up @@ -108,6 +113,8 @@ export class Adcio {
}

public async collectLogs(clientApi: ClientAPI) {
this.listenAddToCart();

await clientApi.init();

try {
Expand All @@ -121,7 +128,6 @@ export class Adcio {

return Promise.allSettled([
...(await this.handleView(clientApi)),
...(await this.handleCarts(clientApi)),
...(await this.handleOrder(clientApi)),
]);
}
Expand All @@ -140,33 +146,6 @@ export class Adcio {
];
}

private async handleCarts(clientApi: ClientAPI): Promise<Promise<void>[]> {
const carts = await clientApi.getCarts();
if (!carts) {
return [];
}

const storage = new CartsStorage({
key: `adcio-carts-${this.adcioCore.getClientId()}`,
});
const existing = storage.getOrSet();
storage.set(carts);

const newCarts = carts.filter(
(cart) => !existing.find((c) => c.id === cart.id),
);
if (newCarts.length === 0) {
return [];
}

return newCarts.map((cart) =>
this.onAddToCart({
cartId: cart.id,
productIdOnStore: cart.productIdOnStore,
}),
);
}

private async handleOrder(clientApi: ClientAPI): Promise<Promise<void>[]> {
const order = await clientApi.getOrder();
if (!order) {
Expand All @@ -181,6 +160,7 @@ export class Adcio {
),
quantity: product.quantity,
productIdOnStore: product.idOnStore,
categoryIdOnStore: product.categoryIdOnStore,
}),
);
}
Expand Down
3 changes: 3 additions & 0 deletions src/adcio/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ export type {
AdcioCreateSuggestionParams,
} from "./adcio.interface";

// Events
export { ImpressionEvent, AddToCartEvent, PurchaseEvent } from "lib/analytics";

// FrontAPI
export type { ClientAPI } from "lib/client-api";
export { Cafe24API } from "lib/client-api";
Expand Down
73 changes: 73 additions & 0 deletions src/lib/analytics/events.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import {
AdcioAnalyticsOnAddToCartParams,
AdcioAnalyticsOnImpressionParams,
AdcioAnalyticsOnPurchaseParams,
} from "lib/analytics";

export const EVENT_IMPRESSION = "adcio.impression";
export const EVENT_ADD_TO_CART = "adcio.add_to_cart";
export const EVENT_PURCHASE = "adcio.purchase";

function dispatch<T>(eventName: string, params: T) {
document.dispatchEvent(
new CustomEvent(eventName, {
detail: params,
}),
);
}

function listen<T>(eventName: string, callback: (params: T) => void) {
document.addEventListener(eventName, (event) => {
const { detail: params } = event as CustomEvent;
if (!params) {
return;
}
callback(params);
});
}

export abstract class AdcioEvent<T> {
protected params: T;

constructor(params: T) {
this.params = params;
}

abstract dispatch(): void;
}

export class ImpressionEvent extends AdcioEvent<AdcioAnalyticsOnImpressionParams> {
static eventName = EVENT_IMPRESSION;

dispatch() {
dispatch(ImpressionEvent.eventName, this.params);
}

static listen(callback: (params: AdcioAnalyticsOnImpressionParams) => void) {
listen(ImpressionEvent.eventName, callback);
}
}

export class AddToCartEvent extends AdcioEvent<AdcioAnalyticsOnAddToCartParams> {
static eventName = EVENT_ADD_TO_CART;

dispatch() {
dispatch(AddToCartEvent.eventName, this.params);
}

static listen(callback: (params: AdcioAnalyticsOnAddToCartParams) => void) {
listen(AddToCartEvent.eventName, callback);
}
}

export class PurchaseEvent extends AdcioEvent<AdcioAnalyticsOnPurchaseParams> {
static eventName = EVENT_PURCHASE;

dispatch() {
dispatch(PurchaseEvent.eventName, this.params);
}

static listen(callback: (params: AdcioAnalyticsOnPurchaseParams) => void) {
listen(PurchaseEvent.eventName, callback);
}
}
2 changes: 2 additions & 0 deletions src/lib/analytics/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,5 @@ export {
AdcioAnalyticsOnAddToCartParams,
AdcioAnalyticsOnPurchaseParams,
} from "./analytics.interface";

export { ImpressionEvent, AddToCartEvent, PurchaseEvent } from "./events";
71 changes: 71 additions & 0 deletions src/lib/client-api/cafe24/cafe24-action-basket.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { AdcioAnalyticsOnAddToCartParams } from "lib/analytics";

interface ICafe24ActionBasket {
selected_item: string[]; // `${quantity}||${model_name}`
needed: string[];
optionIds: string[];
basket_type: string;
ch_ref: string;
command: string;
delvType: string;
display_group: number;
has_option: "T" | "F";
is_direct_buy: "T" | "F";
is_individual: "T" | "F";
main_cate_no: number;
multi_option_data: string;
multi_option_schema: string;
option1: string;
option2: string;
option_type: "T" | "F";
prd_detail_ship_type: string;
product_max: number;
product_max_type: string;
product_min: number;
product_name: string;
product_no: number;
product_price: number;
quantity: number;
redirect: number;
relation_product: "yes" | "no";
}

export class Cafe24ActionBasket {
private data: ICafe24ActionBasket;

constructor(data: ICafe24ActionBasket) {
this.data = data;
}

static fromArg(arg: string) {
const data = decodeURI(arg)
.split("&")
.reduce((acc, cur) => {
const [key, value] = cur.split("=");
Comment on lines +41 to +44

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

URL 객체써서 요렇게 파싱하는건 어떠세요

const data = new URL(arg);
Object.fromEntries(data.searchParams)

if (key.endsWith("[]")) {
const trimmedKey = key.slice(0, -2) as keyof ICafe24ActionBasket;
return {
...acc,
[trimmedKey]: [...((acc[trimmedKey] as string[]) || []), value],
};
}
return {
...acc,
[key]: isNaN(Number(value)) ? value : Number(value),
};
}, {} as ICafe24ActionBasket);
return new Cafe24ActionBasket(data);
}

toCart(): AdcioAnalyticsOnAddToCartParams {
return {
cartId: String(Date.now()),
productIdOnStore: String(this.data.product_no),
categoryIdOnStore: String(this.data.main_cate_no),
quantity: this.data.selected_item.reduce(
(acc, cur) => acc + Number(cur.split("||")[0]),
0,
),
};
}
}
49 changes: 27 additions & 22 deletions src/lib/client-api/cafe24/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import { AddToCartEvent } from "lib/analytics/events";
import { getMeta, getQuery } from "lib/utils";
import { Cafe24ActionBasket } from "./cafe24-action-basket";
import { ICAFE24, ICAFE24API } from "./cafe24.interface";
import { Cart, Customer, ClientAPI, Order } from "../client-api.interface";
import { Customer, ClientAPI, Order } from "../client-api.interface";

const CORCA_CAFE24_CLIENT_ID = "8HE5BizGD9agkHIObMfXRF";
const CORCA_CAFE24_API_VERSION = "2023-06-01";
const ACTION_BASKET_NAME = `action_basket_${CORCA_CAFE24_CLIENT_ID}`;

export class Cafe24API implements ClientAPI {
private authorized: boolean;
Expand All @@ -28,6 +31,28 @@ export class Cafe24API implements ClientAPI {
console.warn("Failed to initialize cafe24 api", e);
this.authorized = false;
}
this.observeAddToCart();
}

observeAddToCart() {
if ((window as any)[ACTION_BASKET_NAME]) {
return;
}
(window as any)[ACTION_BASKET_NAME] = (window as any).action_basket;
(window as any).action_basket = (
...args: [
sType: number,
sGroup: string,
sAction: string,
sParam: string,
aBasketType: string,
bNonDuplicateChk: unknown,
]
) => {
const actionBasket = Cafe24ActionBasket.fromArg(args[3]); // sParam
new AddToCartEvent(actionBasket.toCart()).dispatch();
(window as any)[ACTION_BASKET_NAME](...args);
};
}

getCustomer() {
Expand Down Expand Up @@ -72,27 +97,6 @@ export class Cafe24API implements ClientAPI {
return { idOnStore };
}

getCarts() {
return new Promise<Cart[] | null>((resolve, reject) => {
this.api.getCartList((err, res) => {
if (err) {
reject(err);
} else if (!res.carts) {
resolve(null);
} else {
resolve(
res.carts.map((cart) => ({
id: `${cart.basket_product_no}`,
productIdOnStore: `${cart.product_no}`,
productPrice: cart.product_price,
quantity: Number(cart.quantity),
})),
);
}
});
});
}

getOrder() {
return new Promise<Order | null>((resolve) => {
const { order_id, order_product, payed_amount, total_basic_ship_fee } =
Expand All @@ -102,6 +106,7 @@ export class Cafe24API implements ClientAPI {
id: order_id,
products: order_product.map((product) => ({
idOnStore: `${product.product_no}`,
// categoryIdOnStore: `${product.category_no_1}`,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

혹시 여기가 주석처리된 이유가 있을까요? 주석처리된 상태로 남겨둔다면, 간단히 이유를 남겨줘도 좋을 듯 해요

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

우리가 원하는 카테고리(구매 플로우에서 처음에 접근한 상품분류 페이지)가 맞는지 확실하지 않아서 주석처리했습니다.
테스트해보고 주석 유지 여부 결정할게요. 아무래도 구매다보니 테스트가 쉽지 않네요 ..ㅎㅎ

quantity: product.quantity,
price: product.product_price,
subTotalPrice: product.sub_total_price,
Expand Down
9 changes: 1 addition & 8 deletions src/lib/client-api/client-api.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export interface Order {
id: string;
products: {
idOnStore: string;
categoryIdOnStore?: string;
quantity: number;
price: number;
subTotalPrice?: number;
Expand All @@ -17,13 +18,6 @@ export interface Order {
amount: number;
}

export interface Cart {
id: string;
productIdOnStore: string;
productPrice: number;
quantity: number;
}

export interface Product {
idOnStore: string;
}
Expand All @@ -42,5 +36,4 @@ export interface ClientAPI {
getProduct(): NullableAwaitable<Product>;
getCategory(): NullableAwaitable<Category>;
getOrder(): NullableAwaitable<Order>;
getCarts(): NullableAwaitable<Cart[]>;
}
15 changes: 0 additions & 15 deletions src/lib/constants/error.ts

This file was deleted.

16 changes: 16 additions & 0 deletions src/lib/error/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,17 @@
export { APIError } from "./api-error";

export const ERROR_CODE = {
SUGGESTION: {
PLACEMENT_NOT_FOUND: 12_001,
INVALID_PLACEMENT_TYPE: 12_003,
NO_ACTIVATED_PLACEMENT: 12_004,
},
};

export const PLACEMENT_ERROR_MESSAGE = {
PLACEMENT_NOT_FOUND:
"Failed to suggestions: The placement id is not registered",
NO_ACTIVATED_PLACEMENT: "Failed to suggestions: The placement is not active",
NOT_UUID: "placementId must be a UUID",
UNKNOWN_ERROR: "Failed to suggestions: An unknown error occurred",
};
Loading
0