WorldShopService

유료 재화를 통한 월드 내 상품 구매 관련 기능을 제공합니다.

# Methods

[{"index":"WorldShopProduct GetProductAndWait(string productId) (Yield)","0":"상품의 정보를 얻습니다. 수행하는 동안 스크립트 실행을 중단합니다. 출시된 월드에서는 Waiting, Ing, End 상태의 상품의 정보를 얻을 수 있고, 제작 중인 월드에서는 Register, Waiting, Ing, End 상태의 상품의 정보를 얻을 수 있습니다."}]

[{"index":"WorldShopProductPages GetProductsAndWait(WorldShopProductType productType, WorldShopProductStatus productStatus) (Yield)","0":"검색 조건에 부합하는 상품들의 정보를 확인할 수 있는 객체를 얻습니다. 수행하는 동안 스크립트 실행을 중단합니다. productType에 nil 지정 시 모든 종류의 상품을 검색하고, productStatus에 nil 지정 시 검색 가능한 모든 상품이 검색됩니다. 출시된 월드에서는 Waiting, Ing, End 상태의 상품만 검색이 가능하고, 제작 중인 월드에서는 Register, Waiting, Ing, End 상태의 상품이 검색 가능합니다."}]

[{"index":"void PromptPurchase(string productId) (ClientOnly)","0":"상품 구매 창을 출력합니다. 구매할 수 없는 상품이라면 구매 불가 사유가 출력됩니다."}]

[{"index":"void SetProcessPurchaseCallback(func -> boolean callback) (ServerOnly)","0":"상품 구매가 발생했을 때 상품을 지급하는 callback을 지정합니다.callback은 전달받은 WorldShopPurchaseInfo로 구매 정보를 확인하고 상품 지급에 성공하면 true, 그렇지 않으면 false를 반환해야 합니다. true를 반환하지 않은 구매인 경우 구매 유저가 월드에 재입장했을 때 상품 지급 callback이 다시 실행됩니다."}]

[{"index":"boolean UserHasPassAndWait(string userId, string productId) (Yield)","0":"유저가 이용권을 보유하고 있는지 확인합니다. 수행하는 동안 스크립트 실행을 중단합니다. 제작 중인 월드에서는 모든 이용권을 보유하지 않은 상태로 실행합니다."}]

##### inherited from Service:

[{"index":"EventHandlerBase ConnectEvent(string key, IScriptFunction eventHandler)","0":"이벤트를 연결합니다. 이 서비스에서 타입의 이름이 key인 이벤트가 발생하면, 핸들러로 통지받습니다. 핸들러는 event 매개 변수를 전달받습니다."}]

[{"index":"EventHandlerBase ConnectEvent(Type eventType, IScriptFunction eventHandler)","0":"이벤트를 연결합니다. 이 서비스에서 타입이 eventType인 이벤트가 발생하면, 핸들러로 통지받습니다. 핸들러는 event 매개 변수를 전달받습니다."}]

[{"index":"boolean DisconnectEvent(string key, EventHandlerBase eventHandler)","0":"타입의 이름이 key인 이벤트와 핸들러와의 이벤트 연결을 끊습니다."}]

[{"index":"boolean DisconnectEvent(Type eventType, EventHandlerBase eventHandler)","0":"타입이 eventType인 이벤트와 핸들러와의 연결을 끊습니다."}]

[{"index":"void SendEvent(EventType sendEvent)","0":"이벤트를 발생시킵니다."}]

# Examples

#### SetProcessPurchaseCallback

구매가 발생했을 때 상품 지급을 실행할 함수와 이 함수를 `WorldShopService`에 등록하는 예제입니다. 상품 지급 함수는 월드가 생성될 때 등록되고 계속 유지되어야 하므로 Logic으로 작성하는 것을 권장합니다. 상품 지급은 서버에서 진행되므로 함수 등록도 서버에서 수행되어야 합니다.
    
    
    @Component
    script PurchaseProcessor extends Component
        @ExecSpace("ServerOnly")
        method void OnBeginPlay()
        -- 상품 지급 콜백을 등록합니다.
        _WorldShopService:SetProcessPurchaseCallback(self.ProcessPurchase)
        end
    
        @ExecSpace("ServerOnly")
        method boolean ProcessPurchase(any purchaseInfo)
        log("Process purchase. PurchaseId: " .. purchaseInfo.PurchaseId .. ", UserId: " .. purchaseInfo.UserId .. ", ProductId: " .. purchaseInfo.ProductId .. ", OrderNumber: " .. tostring(purchaseInfo.OrderNumber))
        return true
        end
    end
    

#### SetProcessPurchaseCallback

상품 지급 함수는 `WorldShopPurchaseInfo`를 매개 변수로 받고 boolean을 반환해야 합니다. `WorldShopPurchaseInfo`에는 구매한 유저, 구매한 상품과 같은 처리할 구매의 정보가 들어있습니다. 이를 이용해 상품을 지급하거나 지급하지 않을지 여부를 결정합니다. 상품 지급이 정상적으로 진행되어 구매를 완료시키려면 true를 리턴합니다. 만약 상품을 지급할 수 없다고 판단되어 정상적인 구매 과정을 수행할 수 없다면 false를 리턴합니다. false를 리턴한 구매의 경우, 구매를 수행한 유저가 다음에 월드에 접속했을 때 다시 상품 지급을 시도하게 됩니다.
    
    
    @Component
    script ProductPurchaseProcessor extends Component
        @ExecSpace("ServerOnly")
        method void OnBeginPlay()
        -- 상품 ID별 지급 함수 테이블을 구성합니다.
        -- 이 예제에서 지급 함수는 유저 Entity를 인자로 받고 지급 성공 여부를 boolean으로 리턴합니다.
        self._T.productPurchaseFunctions = {}
    
        -- ID가 "000000"인 상품은 유저에게 1000 코인을 지급합니다.
        self._T.productPurchaseFunctions["000000"] = function(userEntity) return self:GiveCoin1000(userEntity) end
    
        -- 상품 지급 콜백 등록
        _WorldShopService:SetProcessPurchaseCallback(self.ProcessPurchase)
        end
    
        @ExecSpace("ServerOnly")
        method boolean ProcessPurchase(any purchaseInfo)
        local userEntity = _UserService:GetUserEntityByUserId(purchaseInfo.UserId)
    
        if not _EntityService:IsValid(userEntity) then
        	-- 구매 유저의 엔티티를 찾을 수 없어 지급 실패
        	log_error("Process purchase failed. Cannot find user entity. UserId: " .. purchaseInfo.UserId)
        	return false
        end
    
        local productPurchaseFunction = self._T.productPurchaseFunctions[purchaseInfo.ProductId]
    
        if productPurchaseFunction == nil then
        	-- 상품 ID에 대한 지급 함수를 찾을 수 없어 실패
        	log_error("Process purchase failed. Cannot find product purchase function. ProductId: " .. purchaseInfo.ProductId)
        	return false
        end
    
        -- 상품 지급을 시도합니다.
        -- success: 오류 없이 함수 호출에 성공했는지 여부
        -- result: 함수 호출에 성공했다면 함수 리턴값(상품 지급 여부). 함수 호출에서 오류가 있었다면 오류 메시지
        local success, result = pcall(productPurchaseFunction, userEntity)
    
        if success and result then
        	-- 오류가 발생하지 않았고, 상품 지급에 성공했습니다.
        	-- 이후에 성공한 구매 내역들을 확인할 수 있도록 기록을 남깁니다.
        	_LogStorageService:LogPurchaseInfo(purchaseInfo, "Success")
        	return true
        else
        	-- 상품 지급에 실패했습니다.
        	log_error("Process purchase failed. Product purchase function failed. PurchaseId: " .. purchaseInfo.PurchaseId .. ", UserId: " .. purchaseInfo.UserId .. ", ProductId: " .. purchaseInfo.ProductId .. ", OrderNumber: " .. tostring(purchaseInfo.OrderNumber) .. ", Result: " .. tostring(result))
        	return false
        end
        end
    
        @ExecSpace("ServerOnly")
        method boolean GiveCoin1000(EntityRef userEntity)
        local walletComponent = userEntity.WalletComponent
    
        if walletComponent == nil then
        	return false
        end
    
        -- 유저에게 1000 코인 지급
        walletComponent:AddCoin(1000)
    
        -- 상품 지급 성공
        return true
        end
    end
    

#### GetProductsAndWait

`GetProductsAndWait()` 함수를 이용해 조건에 해당하는 상품들을 검색할 수 있습니다. 이 함수가 리턴하는 `WorldShopProductPages` 타입의 객체는 검색된 결과가 많을 경우 결과를 페이지 단위로 나눠 구할 수 있게 해줍니다.
    
    
    @Component
    script OnSaleItemLister extends Component
        method void SearchOnSaleItems()
        -- 상품 종류가 아이템이고, 판매 상태가 판매 중인 상품을 검색합니다.
        local pages = _WorldShopService:GetProductsAndWait(WorldShopProductType.Item, WorldShopProductStatus.Ing)
    
        while true do
        	local pageDatas = pages:GetCurrentPageDatas()
    
        	for _, product in ipairs(pageDatas) do
        		-- 검색된 상품의 정보
        		log("Product Id: " .. product.Id .. ", Name: " .. product.Name .. ", Price: " .. tostring(product.Price))
        	end
    
        	if pages.IsLastPage then
        		break
        	end
    
        	pages:MoveToNextPageAndWait()
        end
        end
    end
    

# SeeAlso

  * WorldShopPurchaseInfo
  * EntityService
  * LogStorageService
  * UserService
  * [월드 숍 서비스 활용하기](/docs?postId=659)