import json
import re
import requests
import traceback
from typing import Dict, List
from requests.exceptions import ConnectionError, Timeout
from django.conf import settings
from django.utils import timezone
from apps.utils.logger import create_mongo_logger
from apps.utils.image import get_image_url
from apps.utils.cache import cache_wrapper
from apps.helpers import currency, getSetting, updateOrCreateSetting
from apps.models_helper.setting_tb import SettingTb
from sentry_sdk import capture_exception
CONFIG_INCOMPLETE = 'Incomplete configuration for importir API.'
CONNECTION_FAILED = 'Failed connecting to importir.org'
def get_file_name(cd):
"""
Get filename from content-disposition
"""
if not cd:
return None
fname = re.findall('filename=(.+)', cd)
if len(fname) == 0:
return None
return fname[0]
class ImportirAPI:
# docs: https://importir.docs.apiary.io/
def __init__(self):
self.retry_count = 0
self.base_url = getattr(settings, "IMPORTIR_BASE_URL", None)
self.is_demo = getattr(settings, "IMPORTIR_IS_DEMO", True)
self.timeout = getattr(settings, "IMPORTIR_TIMEOUT", 20)
self.api_token = None
setting_token = SettingTb.objects.filter(setting_key='importir_api_token')\
.first()
if setting_token is None:
token_response = self.generate_token()
if token_response.get('action'):
self.api_token = token_response.get('result')\
.get('message', {})\
.get('token', '')
SettingTb.objects.create(
setting_desc='Importir API Token',
setting_key='importir_api_token',
setting_title='Importir API Token',
setting_value=self.api_token
)
else:
self.api_token = setting_token.setting_value
setting_token.last_used_at = timezone.now()
setting_token.save(update_fields=['last_used_at'])
def generate_token(self):
"""
Generate auth token for importir API
"""
ret = {'action': False, 'status_code': 500, 'result': None, 'message': ''}
endpoint = '/api/vendor/generate-token'
api_url = self.base_url + endpoint
user_email = getattr(settings, 'IMPORTIR_USER_EMAIL', None)
api_key = getattr(settings, 'IMPORTIR_API_KEY', None)
if not user_email or not api_key:
ret['message'] = CONFIG_INCOMPLETE
return ret
request_params = {'email': user_email, 'api_key': api_key}
try:
response = requests.get(api_url, params=request_params)
ret['status_code'] = response.status_code
if response.status_code == 200:
ret['action'] = True
ret['result'] = response.json()
if isinstance(ret['result'], dict) and ret.get('result').get('status', True) == False:
ret['action'] = False
except (ConnectionError, Timeout) as err:
ret['message'] = CONNECTION_FAILED
except Exception as err:
capture_exception(traceback.format_exc())
log_data = {
'METHOD': 'GET',
'URL': api_url,
'STATUS_CODE': ret['status_code'],
'REQUEST_PARAMETERS': request_params,
'RESPONSE_DATA': ret
}
# logger = create_mongo_logger('importir', collection_name="mongolog-request")
# logger.info(log_data)
return ret
def send(self, method, endpoint, request_data={}, request_files={}, request_params={}, request_headers={}, is_json=False):
ret = {'action': False, 'status_code': 500, 'message': '', 'result': None}
if not all([self.base_url, self.api_token]):
ret['message'] = CONFIG_INCOMPLETE
return ret
api_headers = {
# 'Content-Type': content_type,
'Authorization': 'Bearer ' + self.api_token
}
api_headers.update({**request_headers})
api_url = self.base_url + endpoint
log_data = {
'METHOD': method,
'HEADERS': api_headers,
'REQUEST_DATA': request_data,
'URL': api_url
}
try:
if method in ['post', 'POST']:
# print("request data: ", request_data)
# print("request files: ", request_files)
if is_json:
response = requests.post(api_url, headers=api_headers, data=json.dumps(request_data), timeout=7) # files=request_files
else:
response = requests.post(api_url, headers=api_headers, data=request_data, files=request_files, timeout=7)
elif method in ['get', 'GET']:
response = requests.get(api_url, headers=api_headers, params=request_params, timeout=self.timeout)
log_data['STATUS_CODE'] = response.status_code
log_data['FULL_URL'] = response.request.url
ret['status_code'] = response.status_code
if response.status_code == 200:
ret['result'] = response.json()
ret['action'] = True
if isinstance(ret['result'], dict):
# validasi api create shipping
if ret.get('result').get('status', True) == False:
ret['action'] = False
# validasi api product 1688 & checkout
importir_success_status = ['success_product_feeds_200', 'success_product_detail_200',
'success_product_description_view_200', 'success_checkout_200',
'success_categories_200', 'success_category_detail_200']
if ret.get('result').get('body') and ret.get('result').get('body').get('status_code') not in importir_success_status:
ret['action'] = False
except (ConnectionError, Timeout) as err:
ret['message'] = CONNECTION_FAILED
except Exception as err:
capture_exception(traceback.format_exc())
log_data['RESPONSE_DATA'] = ret
# logger = create_mongo_logger('importir', collection_name="mongolog-request")
# logger.info(log_data)
# If response is `Token is Expired`, retry request with new token
if self.retry_count == 0 and isinstance(ret.get('result'), dict) and ret.get('result').get('data') == 'Token is Expired':
self.retry_count += 1
token_response = self.generate_token()
if token_response.get('action'):
self.api_token = token_response.get('result')\
.get('message', {})\
.get('token', '')
_ = updateOrCreateSetting('importir_api_token', self.api_token, 'Importir API Token')
return self.send(method, endpoint, request_data, request_files, request_params, request_headers, is_json)
return ret
def get_file(self, file_url):
"""
Return response and filename
"""
response = None
try:
response = requests.get(file_url, allow_redirects=True)
if response.status_code == 200:
file_name = get_file_name(response.headers.get('content-disposition')) or file_url.rsplit('/', 1)[1]
return response, file_name
except (ConnectionError, Timeout) as err:
pass
except Exception as err:
response = None
print("Exception get_file: ", str(err))
return response, None
def create_shipping_step_one(self, cart_tk: "CartTitipkirimin"):
endpoint = '/api/shipping/step/1'
estimation_weight = cart_tk.estimation_weight
estimation_weight_kg = estimation_weight / 1000 if estimation_weight else 0
request_data = {
'freight': 10, # Laut LCL
'volume': cart_tk.estimation_volume,
'weight': estimation_weight_kg,
'is_demo': 1 if self.is_demo else 0,
'use_wood': 1 if cart_tk.is_wood else 0,
'wood_note': cart_tk.wood_note
}
request_headers = {
'lang': 'id',
'currency': 'idr'
}
return self.send('POST', endpoint, request_data, request_headers=request_headers)
def create_shipping_step_two(self, shipping_id: int):
endpoint = '/api/shipping/step/2'
request_data = {
'id': shipping_id, # `shipping_id` from step 1
'other_price_rmb': '',
}
request_headers = {
'lang': 'id',
'currency': 'idr'
}
return self.send('POST', endpoint, request_data, request_headers=request_headers)
def create_shipping_step_three(self, shipping_id: int, cart_tk: "CartTitipkirimin"):
endpoint = '/api/shipping/step/3'
request_data = {
'id': shipping_id, # `shipping_id` from step 1
'supplier_email': cart_tk.shipper_email,
'supplier_phone': cart_tk.shipper_phone_number,
'wechat_id': ''
}
request_files = {
# 'supplier_bank_info': '',
# 'invoice_file': '',
# 'packing_ist_file': ''
}
# content_type = 'application/x-www-form-urlencoded'
if cart_tk.invoice_file:
response, filename = self.get_file(get_image_url(cart_tk.invoice_file))
if response:
request_files.update({'invoice_file': (filename, response.content, response.headers.get('Content-Type'))})
# content_type = 'multipart/form-data'
request_headers = {
'lang': 'id',
'currency': 'idr'
}
return self.send('POST', endpoint, request_data, request_files, request_headers=request_headers)
def create_shipping_step_four(self, shipping_id: int):
endpoint = '/api/shipping/step/4'
request_data = {
'id': shipping_id, # `shipping_id` from step 1
'note': ''
}
request_headers = {
'lang': 'id',
'currency': 'idr'
}
return self.send('POST', endpoint, request_data, request_headers=request_headers)
def shipping_save_product(self, shipping_id: int, cart_detail: "CartDetail"):
endpoint = '/api/shipping/save-product/{}'.format(shipping_id)
product_type = cart_detail.get_product_type
product_price = cart_detail.price_original \
if cart_detail.currency == 'CNY' \
else currency(cart_detail.price_original, from_money=cart_detail.currency, to_money='CNY')
price_per_unit = product_price / cart_detail.quantity
is_lartas = 1 if cart_detail.additional_document == 'laporan' else 0
hscode = cart_detail.hscode # object
hscode_number = ''
if hscode is not None:
hscode_number = (hscode.hscode).replace('.', '')
request_data = {
'product_name': cart_detail.product.product_title,
'hscode': hscode_number,
'is_lartas': is_lartas,
'quantity': cart_detail.quantity,
'price_per_unit': price_per_unit,
'category[]': product_type.name
}
request_files = {
# 'cover_product': ''
}
if cart_detail.product.image_file:
response, filename = self.get_file(get_image_url(cart_detail.product.image_file))
if response:
request_files.update({'cover_product': (filename, response.content, response.headers.get('Content-Type'))})
request_headers = {
'lang': 'id',
'currency': 'idr'
}
return self.send('POST', endpoint, request_data, request_files, request_headers=request_headers)
def chat_list(self, shipping_id: int):
"""
Get chat list from Importir API
Args:
shipping_id (int): Importir shipping ID (see: Cart.additional_json)
"""
endpoint = '/api/shipping-chat/detail/{}'.format(shipping_id)
request_headers = {
'lang': 'id',
'currency': 'idr'
}
return self.send('GET', endpoint, request_headers=request_headers)
def post_chat(self, shipping_id: int, message: str):
"""
Send chat to Importir API
Args:
shipping_id (int): Importir shipping ID (see: Cart.additional_json)
"""
endpoint = '/api/shipping-chat/send-message/{}'.format(shipping_id)
request_data = {
'message': message
}
request_headers = {
'lang': 'id',
'currency': 'idr'
}
return self.send('POST', endpoint, request_data, request_headers=request_headers)
def get_shipping_detail(self, shipping_id: int):
"""
Get shipping detail from ImportirAPI
Args:
shipping_id (int): Importir Shipping ID
"""
endpoint = '/api/shipping/detail/{}'.format(shipping_id)
request_headers = {
'lang': 'id',
'currency': 'idr'
}
return self.send('GET', endpoint, request_headers=request_headers)
def get_shipping_status(self, shipping_id: int):
"""
Get shipping status from ImportirAPI
Args:
shipping_id (int): Importir Shipping ID
"""
endpoint = '/api/shipping-status/{}'.format(shipping_id)
request_headers = {
'lang': 'id',
'currency': 'idr'
}
return self.send('GET', endpoint, request_headers=request_headers)
def get_product_feeds(self, query_params: Dict={}) -> Dict:
"""
Get 1688 product feeds from importir
Args:
query_params (Dict): request query parameters
Returns:
Dict: Json response from importir
"""
endpoint = '/api/product/feeds'
if not isinstance(query_params, dict):
query_params = {}
def cache_validator(response):
if response.get('action'): return True
return False
return cache_wrapper(key='importir:1688_product_feeds', timeout=3600, validator=cache_validator)(self.send)(method='GET', endpoint=endpoint, request_params=query_params, request_data={}, request_files={}, request_headers={}, is_json=False)
def get_product_detail(self, product_id) -> Dict:
"""
Get 1688 product detail from importir
Args:
product_id (int): 1688 product ID
Returns:
Dict: Json response from importir
"""
if not product_id:
ret = {'action': False, 'status_code': 500, 'message': 'Invalid product ID'}
return ret
endpoint = '/api/product/detail/1688/%s' % product_id
return self.send('GET', endpoint)
def get_product_description(self, product_id: int) -> Dict:
"""
Get 1688 product description from importir
Args:
product_id (int): 1688 product ID
Returns:
Dict: Json response from importir
"""
if not product_id or not isinstance(product_id, int):
ret = {'action': False, 'status_code': 500, 'message': 'Invalid product ID'}
return ret
endpoint = '/product/description-view/%s' % product_id
return self.send('GET', endpoint)
def get_categories(self) -> Dict:
"""
Get product categories from importir
"""
endpoint = '/api/category/categories'
return self.send('GET', endpoint)
def get_sub_categories(self, category_slug: str) -> Dict:
"""
Get list sub categories from importir
Args:
category_slug (str): category slug
"""
endpoint = '/api/category/detail'
request_params = {
'slug': category_slug
}
return self.send('GET', endpoint, request_params=request_params)
def checkout(self, product_id: int, cart_details) -> Dict:
"""
Checkout Importir
"""
endpoint = '/api/cart/order-vendor'
quantity = subtotal = weight = 0
is_multi_price = False
spec_list = []
price_multiple_desc_list = []
cart_details = cart_details
first_cart_detail = cart_details.first()
for cart_detail in cart_details:
quantity += cart_detail.quantity
weight += cart_detail.weight
product_type = cart_detail.product_type
margin_percent = product_type.get_1688_margin(cart_detail.customer)
margin = (100 + margin_percent) / 100
subtotal += (cart_detail.sub_total / margin)
additional_info = cart_detail.get_additional_information()
if additional_info.get('spec_id') and additional_info.get('sku_id'):
spec_price = cart_detail.price / margin
spec_list.append({'quantity': cart_detail.quantity, 'price': str(spec_price), 'specId': additional_info.get('spec_id')})
price_multiple_desc_list.append({'id': str(additional_info.get('sku_id')), 'qty': cart_detail.quantity})
if not is_multi_price:
is_multi_price = additional_info.get('is_multi_price') == 1
request_data = {
'product_id': product_id,
'specIds': spec_list,
'note': first_cart_detail.product.product_title if first_cart_detail else '',
'note_ori': first_cart_detail.product.product_title if first_cart_detail else '',
'quantity': quantity,
'price': str(subtotal),
'freight': 'air' if first_cart_detail.package_track == 'udara' else 'sea',
'is_parse': 0,
'is_multiple_price': 1 if not is_multi_price else 0,
'price_type': 'RANGE' if is_multi_price else 'OTHERS',
'price_multiple_desc': price_multiple_desc_list,
'weight': weight,
'cbm': 0
}
request_headers = {
'Content-Type': 'application/json',
'lang': 'id',
'currency': 'idr'
}
return self.send('POST', endpoint, request_data=request_data, request_headers=request_headers, is_json=True)
def get_official_order_status(self, order_id: int) -> Dict:
"""
Get official 1688 order status from importir
Args:
order_id (int): Importir order ID
"""
endpoint = '/api/vendor/tracking/official-last-status'
request_params = {
'order_id': order_id
}
request_headers = {
'lang': 'id',
'currency': 'idr'
}
return self.send('GET', endpoint, request_params=request_params, request_headers=request_headers)