Saleor Core 한국 로컬라이제이션 분석: Tax, Address, Plugin/App 시스템

분석 대상: /tmp/saleor-core/ (Saleor Core 소스코드) 분석 일자: 2026-03-27


1. Tax 모듈 분석 (Taxes)

1.1 모델 구조

TaxClass

  • name: 세금 분류명 (예: “Standard”, “Reduced”, “Zero”)
  • ModelWithMetadata 상속 — metadata 필드 활용 가능

TaxClassCountryRate

  • tax_class: FK → TaxClass (null 허용 — null이면 해당 국가의 기본 세율)
  • country: CountryField
  • rate: Decimal (퍼센트 값, 예: 10.00 = 10%)
  • 제약조건: (country, tax_class) 유니크, tax_class=null일 때 country 유니크

TaxConfiguration (채널별 1:1)

  • channel: OneToOne → Channel
  • charge_taxes: bool (기본 True)
  • tax_calculation_strategy: “FLAT_RATES” 또는 “TAX_APP”
  • display_gross_prices: bool (기본 True — 세금 포함가 표시)
  • prices_entered_with_tax: bool (기본 True — 입력가격이 세금 포함인지)
  • tax_app_id: 외부 Tax App 식별자
  • use_weighted_tax_for_shipping: bool (배송비에 가중 세율 적용, Saleor 3.21+, FLAT_RATES 전략에서만 동작)

TaxConfigurationPerCountry (국가별 예외)

  • tax_configuration: FK → TaxConfiguration
  • 채널 TaxConfiguration의 charge_taxes, tax_calculation_strategy, display_gross_prices, tax_app_id, use_weighted_tax_for_shipping 필드를 국가별로 오버라이드 (prices_entered_with_tax는 오버라이드 불가)

1.2 세금 계산 전략: Flat Rate vs Tax App

TaxCalculationStrategy:
  FLAT_RATES  — 내장 로직으로 국가별 세율 적용
  TAX_APP     — 외부 앱(Avalara 등)에 [[📒 Saleor Sync Events Tax|sync webhook]]으로 위임

Flat Rate 계산 핵심 로직

saleor/tax/calculations/__init__.pycalculate_flat_rate_tax():

def calculate_flat_rate_tax(money, tax_rate, prices_entered_with_tax):
    tax_rate = Decimal(1 + tax_rate / 100)
    if prices_entered_with_tax:
        net = money.amount / tax_rate      # 세금 역산
        gross = money.amount
    else:
        net = money.amount
        gross = money.amount * tax_rate    # 세금 가산
    return TaxedMoney(net=Money(net, currency), gross=Money(gross, currency))

Tax App (Dynamic) 계산

  • WebhookEventSyncType.CHECKOUT_CALCULATE_TAXES / ORDER_CALCULATE_TAXES sync webhook 발생
  • 외부 앱이 TaxData 객체 반환 (shipping_price_gross/net, shipping_tax_rate, lines[])
  • 각 line에 TaxLineData(tax_rate, total_gross_amount, total_net_amount) 포함

1.3 Checkout 세금 계산 흐름

flowchart TD
    A[Checkout 생성/수정] --> B{charge_taxes?}
    B -->|No| C[세금 0으로 설정]
    B -->|Yes| D{tax_calculation_strategy?}
    D -->|FLAT_RATES| E[update_checkout_prices_with_flat_rates]
    D -->|TAX_APP| F[Sync Webhook: CHECKOUT_CALCULATE_TAXES]

    E --> E1[국가 코드 결정 shipping > billing > channel default]
    E1 --> E2[TaxClassCountryRate에서 기본 세율 조회]
    E2 --> E3[각 라인별 TaxClass → 국가별 세율 조회]
    E3 --> E4[calculate_flat_rate_tax로 net/gross 산출]
    E4 --> E5[배송비 세율 계산 가중치 or TaxClass 기반]
    E5 --> E6[subtotal + shipping = total]

    F --> F1[Tax App이 TaxData 반환]
    F1 --> F2[라인별 net/gross 적용]
    F2 --> F6[subtotal + shipping = total]

1.4 한국 부가세(VAT) 적용 평가

현재 지원 가능한 부분

  • 10% 단일 세율: TaxClassCountryRate에 country=“KR”, rate=10 설정하면 즉시 적용 가능
  • 세금 포함가 표시: display_gross_prices=True, prices_entered_with_tax=True (한국 관행과 일치)
  • Flat Rate 전략: 한국처럼 단일 VAT 국가에 적합

한국 특화 개발이 필요한 부분

항목현재 상태필요 작업
10% VAT 기본 세율수동 설정 필요초기 데이터 migration으로 자동화
세금계산서 발행미지원Tax App 또는 별도 Invoice App 개발 필요
사업자등록번호Address/Order에 필드 없음metadata 활용 또는 커스텀 필드 추가
면세 품목 분류TaxClass로 가능 (rate=0)“면세”, “영세율” TaxClass 생성
현금영수증미지원Payment App에서 처리
원단위 절사quantize_price로 가능KRW 통화 설정 시 decimal_places=0 확인 필요 (Price Calculation)

세금계산서 구현 전략

  • INVOICE_REQUESTED async webhook 활용
  • 외부 Tax Invoice App에서 국세청 전자세금계산서 API 연동
  • Order metadata에 사업자등록번호(business_registration_number) 저장
  • invoice_request plugin hook 또는 App webhook으로 PDF 생성

2. Address 시스템 분석 (Address)

2.1 Address 모델 필드

필드타입한국 매핑
first_nameCharField(256)이름
last_nameCharField(256)
company_nameCharField(256)회사명
street_address_1CharField(256)도로명주소 (예: 테헤란로 521)
street_address_2CharField(256)상세주소 (예: 파르나스타워 37층)
cityCharField(256)시/군/구
city_areaCharField(128)읍/면/동 (address-level3)
postal_codeCharField(20)우편번호 (5자리)
countryCountryField”KR”
country_areaCharField(128)시/도 (address-level1)
phonePhoneNumberField전화번호 (E.164 형식)
validation_skippedBooleanField검증 건너뛰기 플래그

2.2 주소 검증 시스템

Saleor는 google-i18n-address 패키지(mirumee 개발, Google i18n address data 기반)를 사용한다.

검증 흐름:

  1. CountryAwareAddressForm.validate_address() 호출
  2. i18naddress.normalize_address(data) — 국가별 규칙으로 정규화
  3. i18naddress.get_validation_rules({"country_code": "KR"}) — 한국 규칙 로드
  4. 실패 시 InvalidAddressError 발생, 에러 필드에 매핑

국가별 규칙 오버라이드 메커니즘:

  • saleor/account/i18n_rules_override.py에서 i18naddress.load_validation_data 패치
  • 현재 일본(JP)만 오버라이드 (city 필드 문제 수정)
  • 한국도 동일 방식으로 오버라이드 가능

i18n 필드 매핑:

I18N_MAPPING = [
    ("name", ["first_name", "last_name"]),
    ("street_address", ["street_address_1", "street_address_2"]),
    ("city_area", ["city_area"]),
    ("country_area", ["country_area"]),   # 시/도
    ("company_name", ["company_name"]),
    ("postal_code", ["postal_code"]),
    ("city", ["city"]),                    # 시/군/구
]

2.3 한국 주소 호환성 평가

Google i18n Address의 한국 주소 포맷

Google의 AddressValidationMetadata에서 한국(KR)은 다음과 같이 정의된다:

  • fmt (영문): %S %C%D%n%A%n%O%n%N%n%Z (도, 시, 구/동, 도로명, 회사, 이름, 우편번호)
  • lfmt (한국어): %N%n%O%n%A%n%D%n%C%n%S%n%Z (이름, 회사, 도로명, 읍면동, 시, 도, 우편번호)
  • required_fields: country_area(시/도), city(시/군/구), street_address, postal_code
  • country_area_type: “do_si” (도/시)

현재 호환되는 부분

  • 기본 필드 구조가 한국 주소와 잘 매핑됨
  • country_area → 시/도 (서울특별시, 경기도 등)
  • city → 시/군/구 (강남구, 수원시 등)
  • street_address_1 → 도로명주소
  • postal_code → 5자리 우편번호

도로명주소 API 연동 포인트

flowchart LR
    A[사용자 주소 입력] --> B[도로명주소 검색 UI]
    B --> C[juso.go.kr API 호출]
    C --> D[결과 목록 표시]
    D --> E[선택된 주소 매핑]
    E --> F1["street_address_1 = 도로명주소"]
    E --> F2["city = 시/군/구"]
    E --> F3["country_area = 시/도"]
    E --> F4["postal_code = 우편번호"]
    E --> F5["city_area = 읍/면/동 (선택)"]
    F1 & F2 & F3 & F4 & F5 --> G[street_address_2 = 상세주소 직접 입력]

연동 방식 옵션:

  1. 프론트엔드 전용 (권장): Storefront에서 juso.go.kr API 호출, Saleor에는 매핑된 데이터만 전달
  2. Backend App: Address validation App이 주소 정규화 수행
  3. i18n_rules_override: 한국 주소 포맷 커스텀 (country_area_choices에 시/도 목록 추가)

필요한 수정사항

항목현재필요 작업난이도
한국 주소 필드 순서Google i18n 기본값i18n_rules_override에 KR fmt 패치낮음
도로명주소 검색미지원Storefront에서 juso.go.kr API 연동중간
시/도 선택지Google 데이터 기반필요 시 VALID_ADDRESS_EXTENSION_MAP 활용낮음
성+이름 순서이름 먼저UI에서 한국일 때 순서 변경낮음

3. Plugin 시스템 분석

3.1 아키텍처 개요 (Extending Overview)

Plugin은 Saleor 내장 확장 시스템이다. BasePlugin 클래스를 상속하고, PluginsManager가 등록된 플러그인들의 hook 메서드를 체이닝 방식으로 호출한다.

중요: Saleor는 Plugin 시스템 전체를 App/Webhook 방식으로 전환하는 과정에 있다(“We are in the process of deprecating plugins in favor of apps”). 개별 hook에 deprecated 어노테이션이 붙어 있지 않더라도 Plugin 시스템 자체의 방향이 deprecated다. Saleor는 Plugin 시스템을 App/Webhook 시스템으로 전환 중이다.

3.2 내장 Plugin 목록

디렉토리역할
avatax/Avalara AvaTax 세금 계산
openid_connect/OpenID Connect 인증
admin_email/관리자 이메일 알림
user_email/사용자 이메일 알림
sendgrid/SendGrid 이메일
webhook/App Webhook 디스패처 (핵심)

3.3 Plugin Hook 메서드 카탈로그

세금 관련 (Tax Hooks)

Hook설명
calculate_checkout_line_totalCheckout 라인 총액 계산
calculate_checkout_line_unit_priceCheckout 라인 단가 계산
calculate_checkout_shippingCheckout 배송비 계산
calculate_checkout_totalCheckout 총액 계산
calculate_checkout_subtotalCheckout 소계 계산
calculate_order_line_totalOrder 라인 총액 계산
calculate_order_line_unitOrder 라인 단가 계산
calculate_order_shippingOrder 배송비 계산
calculate_order_totalOrder 총액 계산
get_checkout_line_tax_rateCheckout 라인 세율 조회
get_checkout_shipping_tax_rateCheckout 배송 세율 조회
get_order_line_tax_rateOrder 라인 세율 조회
get_order_shipping_tax_rateOrder 배송 세율 조회
get_taxes_for_checkoutCheckout 전체 세금 데이터 [deprecated]
get_tax_code_from_object_meta객체에서 세금 코드 추출
get_tax_rate_type_choices세금 유형 선택지 목록

결제 관련 (Payment Hooks)

Hook설명
authorize_payment결제 승인
capture_payment결제 캡처
confirm_payment결제 확인
check_payment_balance잔액 확인
get_client_token클라이언트 토큰
get_payment_config결제 설정
get_supported_currencies지원 통화 목록
initialize_payment결제 초기화
list_payment_sources결제 수단 목록
list_stored_payment_methods저장된 결제 수단 [deprecated]
stored_payment_method_request_delete저장 결제 수단 삭제 [deprecated]
payment_gateway_initialize_tokenizationPG 토큰화 초기화 [deprecated]
payment_method_initialize_tokenization결제 수단 토큰화 초기화 [deprecated]
payment_method_process_tokenization결제 수단 토큰화 처리 [deprecated]

엔티티 이벤트 훅 (모두 deprecated — App webhook으로 대체)

Hook대상
account_confirmed, account_deleted, etc.Account
address_created, address_updated, address_deletedAddress
app_installed, app_updated, app_deletedApp
attribute_created/updated/deletedAttribute
attribute_value_created/updated/deletedAttributeValue
category_created/updated/deletedCategory
channel_created/updated/deleted/status_changed/metadata_updatedChannel
checkout_created/updated/fully_paid/fully_authorized/metadata_updatedCheckout
collection_created/updated/deleted/metadata_updatedCollection
customer_created/updated/deleted/metadata_updatedCustomer
draft_order_created/updated/deletedDraft Order
fulfillment_created/canceled/approved/metadata_updatedFulfillment
gift_card_created/updated/deleted/status_changed/metadata_updated/export_completedGift Card
invoice_delete/request/sentInvoice
menu_created/updated/deletedMenu
menu_item_created/updated/deletedMenuItem
order_created/confirmed/cancelled/expired/fulfilled/paid/fully_paid/refunded/fully_refunded/updated/metadata_updated/bulk_createdOrder
page_created/updated/deletedPage

인증 관련

Hook설명
authenticate_user사용자 인증
external_authentication_url외부 인증 URL
external_logout외부 로그아웃
external_obtain_access_tokens외부 토큰 획득
external_refresh외부 토큰 갱신
external_verify외부 인증 검증

기타

Hook설명
notify알림 전송

3.4 PluginsManager 동작 방식

class PluginsManager(PaymentInterface):
    plugins_per_channel: dict[str, list[BasePlugin]]
    global_plugins: list[BasePlugin]
    all_plugins: list[BasePlugin]
  • 채널별로 독립된 플러그인 인스턴스 관리
  • 각 hook 호출 시 체이닝: 이전 플러그인의 반환값이 다음 플러그인의 previous_value로 전달
  • CONFIGURATION_PER_CHANNEL = True이면 채널별 설정 가능

4. App/Webhook 시스템 분석

4.1 App 모델

class App(ModelWithMetadata):
    uuid: UUID (유니크)
    name: CharField(60)
    type: "LOCAL" | "THIRDPARTY"
    identifier: CharField(256)  # 앱 고유 식별자
    is_active: bool
    permissions: M2M → Permission
    manifest_url: URLField       # 앱 매니페스트 URL
    app_url: URLField            # 앱 UI URL
    # ... 기타 메타 정보
  • AppType.LOCAL: Saleor 인스턴스 내부 앱
  • AppType.THIRDPARTY: 외부 앱 (Saleor App Store 등)
  • Permission 기반 접근 제어 — 앱에 필요한 권한만 부여

4.2 Webhook 모델

class Webhook(models.Model):
    app: FK → App
    target_url: URLField (http, https, awssqs, gcpubsub)
    is_active: bool
    secret_key: 서명 검증용
    subscription_query: [[📒 Saleor GraphQL|GraphQL]] subscription query
    custom_headers: JSON
    filterable_channel_slugs: ArrayField  # 특정 채널만 필터
 
class WebhookEvent(models.Model):
    webhook: FK → Webhook
    event_type: CharField  # WebhookEventAsyncType 또는 WebhookEventSyncType

4.3 Webhook Event 타입 전체 목록

Async Events (비동기 — 이벤트 발생 후 알림)

카테고리이벤트필요 권한
Accountaccount_confirmation_requested, account_email_changed, account_change_email_requested, account_set_password_requested, account_confirmed, account_delete_requested, account_deletedMANAGE_USERS
Addressaddress_created, address_updated, address_deletedMANAGE_USERS
Appapp_installed, app_updated, app_deleted, app_status_changedMANAGE_APPS
Attributeattribute_created/updated/deleted, attribute_value_created/updated/deletedNone
Categorycategory_created/updated/deletedMANAGE_PRODUCTS
Channelchannel_created/updated/deleted/status_changed/metadata_updatedMANAGE_CHANNELS
Checkoutcheckout_created/updated/fully_authorized/fully_paid/metadata_updatedMANAGE_CHECKOUTS
Collectioncollection_created/updated/deleted/metadata_updatedMANAGE_PRODUCTS
Customercustomer_created/updated/deleted/metadata_updatedMANAGE_USERS
Draft Orderdraft_order_created/updated/deletedMANAGE_ORDERS
Fulfillmentfulfillment_created/canceled/approved/metadata_updated/tracking_number_updatedMANAGE_ORDERS
Gift Cardgift_card_created/updated/deleted/sent/status_changed/metadata_updated/export_completedMANAGE_GIFT_CARD
Invoiceinvoice_requested/deleted/sentMANAGE_ORDERS
Menumenu_created/updated/deleted, menu_item_created/updated/deletedMANAGE_MENUS
Orderorder_created/confirmed/paid/fully_paid/refunded/fully_refunded/updated/cancelled/expired/fulfilled/metadata_updated/bulk_createdMANAGE_ORDERS
Pagepage_created/updated/deletedMANAGE_PAGES
Page Typepage_type_created/updated/deletedMANAGE_PAGE_TYPES_AND_ATTRIBUTES
Permission Grouppermission_group_created/updated/deletedMANAGE_STAFF
Productproduct_created/updated/deleted/metadata_updated/export_completedMANAGE_PRODUCTS
Product Mediaproduct_media_created/updated/deletedMANAGE_PRODUCTS
Product Variantproduct_variant_created/updated/deleted/metadata_updated/out_of_stock/back_in_stock/stock_updatedMANAGE_PRODUCTS
Promotionpromotion_created/updated/deleted/started/endedMANAGE_DISCOUNTS
Promotion Rulepromotion_rule_created/updated/deletedMANAGE_DISCOUNTS
Salesale_created/updated/deleted/toggleMANAGE_DISCOUNTS
Shippingshipping_price_created/updated/deleted, shipping_zone_created/updated/deleted/metadata_updatedMANAGE_SHIPPING
Staffstaff_created/updated/deleted/set_password_requestedMANAGE_STAFF
Transactiontransaction_item_metadata_updatedHANDLE_PAYMENTS
Translationtranslation_created/updatedMANAGE_TRANSLATIONS
Vouchervoucher_created/updated/deleted/codes_created/codes_deleted/metadata_updated/code_export_completedMANAGE_DISCOUNTS
Warehousewarehouse_created/updated/deleted/metadata_updatedMANAGE_PRODUCTS
기타notify_user [deprecated], observability, thumbnail_created, shop_metadata_updated각각 다름

Sync Events (동기 — 요청-응답 패턴)

이벤트설명필요 권한
payment_list_gateways결제 게이트웨이 목록HANDLE_PAYMENTS
payment_authorize결제 승인HANDLE_PAYMENTS
payment_capture결제 캡처HANDLE_PAYMENTS
payment_refund환불HANDLE_PAYMENTS
payment_void결제 취소HANDLE_PAYMENTS
payment_confirm결제 확인HANDLE_PAYMENTS
payment_process결제 처리HANDLE_PAYMENTS
checkout_calculate_taxesCheckout 세금 계산HANDLE_TAXES
order_calculate_taxesOrder 세금 계산HANDLE_TAXES
transaction_charge_requested거래 충전 요청HANDLE_PAYMENTS
transaction_refund_requested거래 환불 요청HANDLE_PAYMENTS
transaction_cancelation_requested거래 취소 요청HANDLE_PAYMENTS
shipping_list_methods_for_checkout배송 방법 목록MANAGE_SHIPPING
checkout_filter_shipping_methods배송 방법 필터MANAGE_CHECKOUTS
order_filter_shipping_methods주문 배송 방법 필터MANAGE_ORDERS
payment_gateway_initialize_sessionPG 세션 초기화HANDLE_PAYMENTS
transaction_initialize_session거래 세션 초기화HANDLE_PAYMENTS
transaction_process_session거래 세션 처리HANDLE_PAYMENTS
list_stored_payment_methods저장 결제 수단 목록HANDLE_PAYMENTS
stored_payment_method_delete_requested저장 결제 수단 삭제HANDLE_PAYMENTS
payment_gateway_initialize_tokenization_sessionPG 토큰화 세션HANDLE_PAYMENTS
payment_method_initialize_tokenization_session결제 수단 토큰화 세션HANDLE_PAYMENTS
payment_method_process_tokenization_session결제 수단 토큰화 처리HANDLE_PAYMENTS

4.4 Sync Webhook 디스패치 흐름

flowchart TD
    A[Checkout/Order 세금 계산 요청] --> B{tax_calculation_strategy?}
    B -->|TAX_APP| C[PluginsManager.get_taxes_for_checkout/order]
    C --> D[WebhookPlugin.get_taxes_for_checkout]
    D --> E[tax_app_id로 대상 App 결정]
    E --> F[sync webhook 전송 target_url로]
    F --> G[Tax App이 TaxData JSON 반환]
    G --> H[응답 파싱 → TaxData 객체]
    H --> I[라인별 net/gross/tax_rate 적용]

    B -->|FLAT_RATES| J[내장 Flat Rate 계산]

5. Plugin vs App: 비교 및 권장사항

5.1 핵심 차이 (Architecture)

항목Plugin (Legacy)App (현재 권장)
배포Saleor 코어에 포함독립 서비스로 배포
코드 위치saleor/plugins/ 내부외부 서비스 (어떤 언어든 가능)
통신Python 함수 직접 호출HTTP Webhook (sync/async)
상태대부분 deprecated 표시적극 개발 중
확장성모놀리식마이크로서비스
설정DB PluginConfigurationApp manifest + DB
업그레이드Saleor 버전에 종속독립적 버전 관리

5.2 한국 시장 개발 권장 방식

App(Webhook) 방식을 권장한다. 이유:

  1. Plugin 시스템은 deprecated 예정
  2. 독립 배포 가능 — Saleor 업그레이드에 영향 없음
  3. 한국 PG사/세금계산서/도로명주소 등 로컬 서비스 연동에 적합
  4. 필요한 sync webhook이 이미 모두 존재 (세금, 결제, 배송)

6. 한국 시장 로컬라이제이션 종합 권장사항

6.1 Tax App (세금계산서 + VAT)

flowchart TD
    subgraph "Korean Tax App"
        A[CHECKOUT_CALCULATE_TAXES webhook] --> B[10% VAT 적용]
        B --> C[면세/영세율 품목 체크]
        C --> D[TaxData 반환]

        E[ORDER_CONFIRMED webhook] --> F[사업자등록번호 확인]
        F --> G{세금계산서 필요?}
        G -->|Yes| H[국세청 전자세금계산서 API]
        G -->|No| I[현금영수증 발행]

        J[INVOICE_REQUESTED webhook] --> K[세금계산서 PDF 생성]
    end

6.2 개발 우선순위

순위항목구현 방식예상 난이도
110% VAT 기본 세율 설정Flat Rate (TaxClassCountryRate KR=10)매우 낮음
2KRW 통화 설정Channel 설정매우 낮음
3한국 주소 포맷i18n_rules_override + 프론트엔드낮음
4도로명주소 API 연동Storefront 프론트엔드중간
5한국 PG 결제 AppSync webhook App (Toss/NicePay 등)높음
6전자세금계산서 발행 AppAsync webhook App (국세청 API)높음
7현금영수증 발행Payment App 확장중간
8면세/영세율 품목 관리TaxClass 추가 + Admin UI낮음

6.3 Flat Rate로 충분한 경우

대부분의 한국 B2C 커머스에서는 Flat Rate 전략으로 충분하다:

  • 한국은 단일 VAT 10%
  • TaxClass “면세”(rate=0), “표준”(rate=10) 두 가지면 대부분 커버
  • Tax App은 세금계산서 발행이 필요한 B2B에서만 필요

별도 Tax App이 필요한 경우:

  • B2B 세금계산서 발행
  • 면세/과세 혼합 거래의 자동 분류
  • 국세청 API 연동

관련 문서

핵심 개념

확장 시스템

결제 및 기타

상위 인덱스