Skip to content

Commit bb5658d

Browse files
PLUX-KRyhk1038heozeopjune20516
authored
기능: 외부채널 데이터에 대한 검색 조건 추가(created_at) 및 외부채널 cart_item에 unit_price 개념 도입 (#125)
* 핫픽스: 배송 관련 필드 타입 수정 (#49) * shipping_addresses 테이블 문자셋을 utf8mb4 로 변경 * ship_info.user_memo 필드 타입을 Text 로 변경 * 핫픽스 : gomibranch 에 store module 명시 * 수정 : gomibranch payment manager 추가 * 수정: 마이그레이션으로 인한 annotate 작동 * 수정: 외부채널 컨트롤러가 매니저에게 전달하는 파라미터를 해시로 변경 * 수정: 베이스 어댑터의 parse_query_hash 메소드 수정 * 수정: 하라반, 티키 어댑터에 변경사항 적용 * 수정: 베이스 어댑터에 주석 추가 * 수정: 개발 환경에서 사용할 lazada app console 정보 업데이트 * 수정: 하라반 어댑터 오류 수정 * 수정: 라자다 어댑터에 parse_query_hash 메소드 업데이트 적용 * 수정: 파트너 센터 유저의 jwt 기한을 임시로 1년으로 수정 * 수정: cors에 스테이징 파트너센터 url을 추가 * 수정: 쇼피 어댑터 일부 수정, 승찬님께 전달 * 형식: uniprice 추가에 따른 주석 변경 * 리팩토링: 쇼피 어뎁터 parse query방식 수정 사용하기로 협의한대로 사용할 수 있도록 parse query 변경 * 기능: external_channel_cart_item에 unit price 컬럼 추가 (#124) * 기능: 외부채널 cart item에 unit 가격 추가 외부채널 cart item에 값 보정을 위한 unit price 컬럼 추가 및 데이터 받아옴 * 형식: DB 변경에 따른 주석 변경 * 리팩토링: sendo 어뎁터 요청 방식 규정 sendo 어뎁터에서 새로 규정한 request 방식으로 요청을 보내고 받을 수 있도록 리팩토링 * 리팩토링: 쇼피 호출 로직 변경 기존에 2중 while문 사용하면서 읽기 어려워서 query생성하는 함수를 추가했다. * 수정: 라자다 어댑터에서 데이터가 없을 경우 에러를 발생시키지 않도록 수정 * 수정: 용현님 코드리뷰 적용 * 수정: 외부채널 어댑터 query_hash 데이터 스타일 통일 * 버그: 쇼피 어뎁터 일자 안잘리는 문제 hash 포인터를 받아서 생기는 에러였다. Co-authored-by: Yonghyun Kim (Freddy) <fred@gomicorp.com> Co-authored-by: Lee seung chan <dltmdcks702@gmail.com> Co-authored-by: Bran <40688044+june20516@users.noreply.github.com>
1 parent dc8df9e commit bb5658d

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+1105
-359
lines changed

Gemfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ gem 'schema_to_scaffold'
2222
gem 'http_accept_language'
2323
gem 'locale_router', git: 'https://github.com/yhk1038/locale_router.git'
2424
gem 'omise'
25+
gem 'iamport'
2526
gem 'rack-cors'
2627
gem 'rails-file_storage', git: 'https://github.com/yhk1038/file_storage.git'
2728

app/adapters/external_channel/adapter_factory.rb

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,21 @@
11
module ExternalChannel
22
class AdapterFactory
3-
def self.get_adapter(channel_name)
4-
case channel_name.downcase
5-
when 'haravan'
6-
HaravanAdapter.new
7-
when 'tiki'
8-
TikiAdapter.new
9-
when 'sendo'
10-
SendoAdapter.new
11-
when 'lazada'
12-
LazadaAdapter.new
13-
when 'shopee'
14-
ShopeeAdapter.new
15-
else
3+
class << self
4+
def known_adapter_map
5+
{
6+
haravan: HaravanAdapter,
7+
tiki: TikiAdapter,
8+
sendo: SendoAdapter,
9+
lazada: LazadaAdapter,
10+
shopee: ShopeeAdapter
11+
}
12+
end
13+
14+
def adapter(channel_name)
15+
known_adapter_map[channel_name.to_s.downcase.to_sym].new || adapter_not_found!
16+
end
17+
18+
def adapter_not_found!
1619
raise NotImplementedError, 'Requested Data Is Not Supported!'
1720
end
1821
end

app/adapters/external_channel/base_adapter.rb

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
module ExternalChannel
22
class BaseAdapter
3-
DEAFULT_EXCEPTION = [
3+
DEFAULT_EXCEPTION = [
44
Errno::ETIMEDOUT, Timeout::Error,
55
Faraday::TimeoutError, Faraday::RetriableResponse, Faraday::ConnectionFailed
66
].freeze
@@ -28,7 +28,7 @@ def check_token_validation; end
2828

2929
# == 리퀘스트를 던지는 caller 메소드입니다.
3030
def request_get(endpoint, params, headers, retry_exceptions = nil)
31-
retry_exceptions ||= DEAFULT_EXCEPTION
31+
retry_exceptions ||= DEFAULT_EXCEPTION
3232
Faraday.new(endpoint, params: params, request: { open_timeout: 5, timeout: 5 }) do |conn|
3333
conn.response :logger, Rails.logger
3434
conn.request(:retry, max: 5, interval: 1, exceptions: retry_exceptions)
@@ -38,7 +38,7 @@ def request_get(endpoint, params, headers, retry_exceptions = nil)
3838
end
3939

4040
def request_post(endpoint, body, headers, retry_exceptions = nil)
41-
retry_exceptions ||= DEAFULT_EXCEPTION
41+
retry_exceptions ||= DEFAULT_EXCEPTION
4242
Faraday.new(endpoint, request: { open_timeout: 5, timeout: 5 }) do |conn|
4343
conn.response :logger, Rails.logger
4444
conn.request(:retry, max: 5,
@@ -54,7 +54,20 @@ def request_post(endpoint, body, headers, retry_exceptions = nil)
5454
end
5555

5656
# == 전달받은 쿼리 파라미터를 각 채널의 포맷에 맞게 변환합니다.
57-
def parse_query_hash(query); end
57+
# == query_mapper 와 date_formatter 가 반드시 필요합니다.
58+
# == query_mapper 와 date_formatter 는 하위 클래스에서 세부 구현합니다.
59+
def parse_query_hash(query_mapper, query_hash)
60+
query_hash['from'] ||= date_formatter((DateTime.now - 1.days).beginning_of_day.utc)
61+
query_hash['to'] ||= date_formatter((DateTime.now - 1.days).end_of_day.utc)
62+
query_hash['key'] ||= 'updated'
63+
64+
query_hash.replace({
65+
query_mapper[query_hash['key']][0]=> date_formatter(query_hash['from'].to_time),
66+
query_mapper[query_hash['key']][1]=> date_formatter(query_hash['to'].to_time)
67+
})
68+
end
69+
70+
def date_formatter(utc_time); end
5871

5972
# == 적절하게 정제된 데이터를 리턴합니다.
6073
def products(query = {}); end

app/adapters/external_channel/haravan_adapter.rb

Lines changed: 10 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,11 @@ class HaravanAdapter < BaseAdapter
4040
# = fields : response 데이터 중 특정 스키마만 골라서 볼 수 있습니다
4141
}
4242

43+
QUERY_MAPPER = {
44+
'created'=> %w[created_at_min created_at_max],
45+
'updated'=> %w[updated_at_min updated_at_max],
46+
}
47+
4348
def initialize
4449
super
4550
api_key = Rails.application.credentials.dig(:haravan, :api, :key)
@@ -52,26 +57,17 @@ def initialize
5257

5358
# == 적절하게 정제된 데이터를 리턴합니다.
5459
def products(query_hash = {})
55-
parse_query_hash(query_hash)
56-
57-
refine_products(call_products(query_hash))
60+
refine_products(call_products(parse_query_hash(QUERY_MAPPER, query_hash)))
5861
end
5962

6063
def orders(query_hash = {})
61-
parse_query_hash(query_hash)
62-
63-
refine_orders(call_orders(query_hash))
64+
refine_orders(call_orders(parse_query_hash(QUERY_MAPPER, query_hash)))
6465
end
6566

6667
def login; end
6768

68-
def parse_query_hash(query_hash)
69-
query_hash['updated_from'] ||= DateTime.now - 1.days
70-
query_hash['updated_to'] ||= DateTime.now
71-
query_hash['updated_at_min'] = query_hash['updated_from'].to_s
72-
query_hash['updated_at_max'] = query_hash['updated_to'].to_s
73-
query_hash.delete('updated_from')
74-
query_hash.delete('updated_to')
69+
def date_formatter(utc_time)
70+
utc_time.strftime('%Y-%m-%d %H:%M')
7571
end
7672

7773
# == 외부 채널의 API 를 사용하여 각 레코드를 가져옵니다.
@@ -151,7 +147,7 @@ def refine_orders(records)
151147
paid_at: paid_at,
152148
billing_amount: record['total_price'],
153149
ship_fee: record['shipping_lines'].inject(0) { |sum, line| sum + (line['price']) },
154-
variant_ids: record['line_items'].map { |variant| [variant['variant_id'], variant['quantity'].to_i] },
150+
variant_ids: record['line_items'].map { |variant| [variant['variant_id'], variant['quantity'].to_i, variant['price'].to_i ] },
155151
cancelled_status: record['cancelled_status'],
156152
shipping_status: record['fulfillments_status']
157153
}

app/adapters/external_channel/lazada_adapter.rb

Lines changed: 21 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,23 @@ class LazadaAdapter < BaseAdapter
2929
# = sort_direction : 오름차순과 내림차순을 결정
3030
# = offset : 지정한 갯수의 데이터를 skip
3131
# = limit : 응답 받을 데이터의 갯수
32-
# = create_before : 주문 생성 시간 <= date (ISO 8601)
33-
# = create_after : 주문 생성 시간 >= date (ISO 8601)
32+
# = created_before : 주문 생성 시간 <= date (ISO 8601)
33+
# = created_after : 주문 생성 시간 >= date (ISO 8601)
3434
# = update_before : 주문 업데이트 시간 <= date (ISO 8601)
3535
# = update_after : 주문 업데이트 시간 >= date (ISO 8601)
3636
}
3737

38+
QUERY_MAPPER = {
39+
'products'=> {
40+
'created'=> %w[create_after create_before],
41+
'updated'=> %w[update_after update_before],
42+
},
43+
'orders'=> {
44+
'created'=> %w[created_after created_before],
45+
'updated'=> %w[update_after update_before],
46+
}
47+
}
48+
3849
def initialize
3950
super
4051
@token = ExternalChannel::Token.find_or_create_by(country: Country.send(ApplicationRecord.country_code),
@@ -62,7 +73,7 @@ def get_code
6273
options.add_argument('--no-sandbox')
6374

6475
browser = Selenium::WebDriver.for :chrome, options: options
65-
browser.navigate.to "https://auth.lazada.com/oauth/authorize?response_type=code&force_auth=true&redirect_uri=https://api.gomistore.com/external_channels/code&client_id=#{app_key}"
76+
browser.navigate.to "https://auth.lazada.com/oauth/authorize?response_type=code&force_auth=true&redirect_uri=https://408d19fef5ad.ngrok.io/external_channels/code&client_id=#{app_key}"
6677

6778
# = 로그인이 안 되어있는 경우 : form.empty? => true
6879
form = browser.find_elements(css: 'form[name=form1]')
@@ -131,29 +142,20 @@ def check_token_validation
131142
def products(query_hash = {})
132143
check_token_validation
133144

134-
parse_query_hash(query_hash)
135-
136-
refine_products(call_products(query_hash))
145+
refine_products(call_products(parse_query_hash(QUERY_MAPPER['products'], query_hash)))
137146
end
138147

139148
def orders(query_hash = {})
140149
check_token_validation
141150

142-
parse_query_hash(query_hash)
143-
144-
refine_orders(call_orders(query_hash))
151+
refine_orders(call_orders(parse_query_hash(QUERY_MAPPER['orders'], query_hash)))
145152
end
146153

147154
protected
148155
def login; end
149156

150-
def parse_query_hash(query_hash)
151-
query_hash['updated_from'] ||= DateTime.now - 1.days
152-
query_hash['updated_to'] ||= DateTime.now
153-
query_hash['update_after'] = query_hash['updated_from'].to_time.iso8601.to_s
154-
query_hash['update_before'] = query_hash['updated_to'].to_time.iso8601.to_s
155-
query_hash.delete('updated_from')
156-
query_hash.delete('updated_to')
157+
def date_formatter(utc_time)
158+
utc_time.to_datetime.iso8601
157159
end
158160

159161
def call_products(query_hash)
@@ -162,8 +164,6 @@ def call_products(query_hash)
162164
end
163165

164166
def call_orders(query_hash)
165-
query_hash.merge!({ created_after: '2018-02-10T16:00:00+08:00' }) if query_hash.empty?
166-
167167
response = request_get('/orders/get', query_hash)
168168
response.body['data']['orders']
169169
end
@@ -193,7 +193,7 @@ def refine_products(records)
193193
brand_name: record['attributes']['brand'],
194194
variants: refine_product_options(record['skus'])
195195
}
196-
end
196+
end if records.present?
197197

198198
product_property
199199
end
@@ -223,7 +223,6 @@ def refine_orders(records)
223223

224224
records.each do |record|
225225
call_order_items(record['order_id']).each_with_index do |order_item, index|
226-
#Rails.logger.info order_item['sku']
227226
order_property << {
228227
id: "#{record['order_id']}-#{index}",
229228
order_number: record['order_number'],
@@ -235,13 +234,12 @@ def refine_orders(records)
235234
billing_amount: record['price'].to_i + record['shipping_fee'],
236235
ship_fee: record['shipping_fee'],
237236
cancelled_status: ['cancelled'].include?(order_item['status']) ? order_item['status'] : nil,
238-
variant_ids: [[order_item['sku'].to_s, 1]],
237+
variant_ids: [[order_item['sku'].to_s, 1, order_item['item_price'].to_i]],
239238
shipping_status: %w[ready_to_ship, delivered, shipped returned].include?(record['statuses']) ? order_item['status'] : nil
240239
}
241240
end
242-
end
241+
end if records.present?
243242

244-
#Rails.logger.info order_property
245243
order_property
246244
end
247245
end

app/adapters/external_channel/sendo_adapter.rb

Lines changed: 46 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,17 @@ class SendoAdapter < BaseAdapter
1616
# order_status: 주문 상태
1717
# }
1818

19-
attr_reader :base_url, :default_headers, :token
19+
attr_reader :base_url, :default_headers, :token, :request_type
20+
21+
QUERY_MAPPER = {
22+
'products'=> {
23+
'updated'=> %w[date_from date_to]
24+
},
25+
'orders'=> {
26+
'created'=> %w[order_date_from order_date_to],
27+
'updated'=> %w[order_status_date_from order_status_date_to]
28+
}
29+
}
2030

2131
def initialize
2232
super
@@ -35,30 +45,26 @@ def check_token_validation
3545
login if !token.auth_token || @token.auth_token_expired?
3646
end
3747

38-
# query로 들어오는 값을 지원하는 방식에 맞게 수정합니다.
39-
40-
# === sendo는 데이터 형식에 따라 query를 리턴하는 방식이 다릅니다.
41-
# === 따라서 curring하는 형식으로 처리했습니다.
42-
def parse_query_hash(data_type)
43-
case data_type
44-
when 'product'
45-
->(query) { parse_query_on_product(default_query(query)) }
46-
when 'order'
47-
->(query) { parse_query_on_order(default_query(query)) }
48-
end
49-
end
50-
5148
# == 적절하게 정제된 데이터를 리턴합니다.
5249
def products(query_hash = {})
5350
check_token_validation
5451

55-
refine_products(call_products(parse_query_hash('product').call(query_hash)))
52+
refine_products(call_products(parse_query_hash(QUERY_MAPPER['products'], query_hash)))
5653
end
5754

5855
def orders(query_hash = {})
5956
check_token_validation
6057

61-
refine_orders(call_orders(parse_query_hash('order').call(query_hash)))
58+
refine_orders(call_orders(parse_query_hash(QUERY_MAPPER['orders'], query_hash)))
59+
end
60+
61+
def parse_query_hash(query_mapper, query_hash)
62+
@request_type = query_hash['key'] || 'updated'
63+
super
64+
end
65+
66+
def date_formatter(utc_time)
67+
utc_time.to_datetime.new_offset(0)
6268
end
6369

6470
def login
@@ -101,8 +107,14 @@ def call_orders(query_hash = {})
101107
'Content-Type': 'application/json',
102108
'cache-control': 'no-cache'
103109
}
104-
request_lists(orders_url, orders_body, orders_header) do |orders|
105-
orders['result']['data']
110+
from, to = QUERY_MAPPER['orders'][request_type]
111+
interval = 2.days
112+
request_interval(orders_body[from], orders_body[to], interval) do |requestFrom, requestTo|
113+
orders_body[from] = date_time_format(requestFrom)
114+
orders_body[to] = date_time_format(requestTo)
115+
request_lists(orders_url, orders_body, orders_header) do |orders|
116+
orders['result']['data']
117+
end
106118
end
107119
end
108120

@@ -127,7 +139,7 @@ def refine_orders(orders)
127139
id: sales_data['order_number'].to_s,
128140
order_number: sales_data['order_number'],
129141
billing_amount: sales_data['total_amount_buyer'],
130-
variant_ids: sales_details.map { |option| [option['sku'], option['quantity']] },
142+
variant_ids: sales_details.map { |option| [ option['sku'], option['quantity'].to_i, option['price'].to_i ] },
131143
order_status: map_order_status(sales_data['order_status']),
132144
cancelled_status: cancelled_status(sales_data['order_status']),
133145
shipping_status: shipping_status(sales_data['order_status']),
@@ -142,30 +154,8 @@ def refine_orders(orders)
142154

143155
private
144156

145-
### === 요청 query데이터를 parsing하는 로직입니다.
146-
147-
def parse_query_on_product(query_hash)
148-
update_from = query_hash[:updated_from].to_datetime
149-
update_to = query_hash[:updated_to].to_datetime
150-
{
151-
date_from: "#{update_from.year}/#{update_from.month}/#{update_from.day}",
152-
date_to: "#{update_to.year}/#{update_to.month}/#{update_to.day}"
153-
}
154-
end
155-
156-
def parse_query_on_order(query_hash)
157-
update_from = query_hash[:updated_from].to_datetime
158-
update_to = query_hash[:updated_to].to_datetime
159-
{
160-
order_date_from: "#{update_from.year}/#{update_from.month}/#{update_from.day}",
161-
order_date_to: "#{update_to.year}/#{update_to.month}/#{update_to.day}"
162-
}
163-
end
164-
165-
def default_query(query_hash)
166-
query_hash[:updated_from] ||= Time.now - 1.days
167-
query_hash[:updated_to] ||= Time.now
168-
query_hash
157+
def date_time_format(utc_time)
158+
"#{utc_time.year}/#{utc_time.month}/#{utc_time.day}"
169159
end
170160

171161
### === 데이터를 불러오는 로직입니다.
@@ -190,6 +180,19 @@ def get_all_by_ids(ids, url, query_hash = {})
190180
end
191181
end
192182

183+
def request_interval(from, to, interval)
184+
return unless block_given?
185+
186+
data = []
187+
while from < to
188+
tempFrom = to - interval
189+
request_from = tempFrom < from ? from : tempFrom
190+
data << yield(request_from, to)
191+
to = request_from
192+
end
193+
data.flatten
194+
end
195+
193196
# == list 에서 모든 데이터를 요청합니다.
194197
def request_lists(url, body, header)
195198
body[:token] = ''

0 commit comments

Comments
 (0)