Building a Flexible Checkout Solution: Frontend Architecture for Multi-Service Integration

Hello. This is David, a Frontend Engineer from Merpay Payment & Customer Platform, and EM @anzai.
This article is the 14th-day entry for the Merpay & Mercoin Tech Openness Month 2025.

This time, we would like to delve deeper into the Frontend design of CheckoutSolution, which was also mentioned in the article "New Challenges for the Payment Platform: Development of a Payment Checkout Solution" dated 2025/06/06.

New Challenges for the Payment Platform: Development of a Payment Checkout Solution(This article is written in Japanese.)

Introduction

At Mercari, we’ve been on an exciting journey to create a unified checkout solution that serves multiple services across our platform. As a Frontend Engineer on this project, I want to share our experience building a flexible, scalable checkout system that can adapt to diverse business requirements while maintaining consistency and performance.

The challenge was clear: we need to provide a purchase experience for a variety of services, including the Mercari app itself, our global-facing Mercari services, and NFT purchasing. Each service required different UI customizations, language support, and business logic, yet they all needed to share core functionality and maintain a cohesive user experience.

The Challenge: One Size Doesn’t Fit All

Traditional checkout systems are typically built for a single use case. However, our requirements were far more complex, needing to cater to:

  • The core Mercari app: Supporting a vast range of items and domestic transactions.
  • Global-facing Mercari services: Handling international sales with complex shipping and tax calculations.
  • NFT purchasing platforms: Managing digital product purchases with instant delivery.

Each of these areas had unique requirements:

  • Different UI layouts and branding needs
  • Multiple language support (Japanese, English, Traditional Chinese)
  • Varying payment methods and validation rules
  • Service-specific business logic and workflows

Building separate checkout systems for each service would have led to code duplication, inconsistent user experiences, and maintenance nightmares. We needed a solution that was both flexible and unified.

Technical Foundation: Building on Solid Ground

Technology Stack

Rather than reinventing the wheel, we leveraged Mercari’s established web platform standards:

  • React & Next.js: Following our golden path for modern web applications
  • TypeScript: Ensuring type safety across the entire system
  • Monorepo Architecture: Using PNPM workspaces for package management

The technology stack was largely predetermined by our web platform team’s tech radar, which allowed us to focus on the architectural challenges rather than technology selection.

Monorepo Strategy

One of our key architectural decisions was adopting a monorepo structure. This choice was driven by several factors:

checkout-solution/
├── packages/
│   ├── core/                 # Core elements managed by checkout team
│   ├── global-checkout/         # Global-specific implementations
│   ├── nft-checkout/    # NFT-specific implementations
│   └── shared/               # Shared utilities and types
└── apps/
    └── checkout-app/         # Main Next.js application

This structure enables:

  • Separation of Concerns: Each service team can work independently on their package
  • Code Reusability: Shared components and utilities in common packages
  • Version Management: Future capability for independent package versioning
  • Scalability: Easy addition of new services without affecting existing ones

Core Architecture: Elements-Based Design

The Element Concept

At the heart of our architecture is the concept of "Elements" – React components with enhanced capabilities:

interface Element {
  // Defined API for data access
  getData: () => CheckoutData;
  updateData: (data: Partial<CheckoutData>) => void;

  // Component implementation
  render: () => JSX.Element;
}

Elements are more than just React components. They:

  • Have well-defined APIs for data interaction
  • Can directly access and update our frontend data store
  • Require minimal scaffolding to integrate into the checkout flow
  • Maintain type safety through TypeScript definitions

Core vs Flex Elements

We categorized elements into two types:

Core Elements:

  • Built and maintained by the MP checkout team
  • Support all languages and use cases
  • Provide fundamental checkout functionality (payment forms, coupon discount, shipping selection, order summary)
  • Ensure consistency across all services

Flex Elements:

  • Developed by individual service teams
  • Customized for specific business requirements
  • Can override or extend core functionality
  • Allow for service-specific UI and business logic

This dual approach gives us the best of both worlds: consistency where it matters and flexibility where it’s needed.

Data Flow Architecture

Our data management follows a centralized store pattern:

// Simplified data flow
const CheckoutProvider = ({ children }) => {
  const [checkoutData, setCheckoutData] = useState(initialState);

  const updateData = useCallback((updates) => {
    setCheckoutData(prev => ({ ...prev, ...updates }));
  }, []);

  return (
    <CheckoutContext.Provider value={{ checkoutData, updateData }}>
      {children}
    </CheckoutContext.Provider>
  );
};

This centralized approach ensures:

  • Consistent data state across all elements
  • Easy debugging and state management
  • Seamless integration between core and flex elements
  • Reliable data persistence throughout the checkout flow

Design System Integration

Maintaining Visual Consistency

One of our biggest challenges was maintaining visual consistency while allowing customization. We addressed this through:

DS4 Integration: We implemented Mercari’s design system (DS4) as our foundation, using only default configurations initially.

Controlled Flexibility: When teams requested customizations (different text colors, font weights, etc.), we created a prop-passing system that allows flex elements to customize core elements within defined boundaries:

// Flex element can pass styling props to core elements
<CorePaymentForm 
  textColor="primary" 
  fontWeight="bold"
  // Other customizations within DS4 constraints
/>

This approach prevents visual divergence while still allowing necessary customizations.

Language and Localization: A Complex Challenge

Multi-Language Architecture

Supporting multiple languages across different services proved to be one of our most complex challenges:

Core Element Requirements:

  • Must support all languages (Japanese, English, Traditional Chinese)
  • Consistent translations across services
  • Centralized language management

Flex Element Flexibility:

  • Can support subset of languages specific to their service
  • Service-specific terminology and messaging
  • Custom localization logic

URL-Based Language Detection:

// Simplified language detection and routing
const useLanguageRouting = () => {
  const router = useRouter();
  const { locale } = router;

  useEffect(() => {
    // Validate locale against service-supported languages
    if (!supportedLocales.includes(locale)) {
      router.push(`/${defaultLocale}${router.asPath}`);
    }
  }, [locale, router]);
};

The complexity multiplies when you consider that different services support different language combinations, and we need to ensure users always see content in a supported language.

Single Domain Requirement: A Double-Edged Sword

The Challenge

One of our top-level requirements was to serve all checkout experiences from a single domain and URL structure. While this provides a seamless user experience, it significantly increased our technical complexity.

Without Single Domain (simpler approach):

  • Each service could have its own Next.js instance
  • Independent deployments and versioning
  • Isolated failure domains
  • Simpler routing and configuration

With Single Domain (our requirement):

  • Single Next.js instance serving all services
  • Shared deployment pipeline
  • Complex routing and service detection
  • Coordinated release management

Implementation Challenges

This requirement led to several technical challenges:

Package Versioning Complexity:
When one service releases an update, all services are affected, requiring:

  • Comprehensive regression testing across all services
  • Careful branching and release strategies
  • Cross-team coordination for deployments

Routing Complexity:
Hosting the checkout for all services on the same URL meant we couldn’t use the URL path to determine which service was being used. Instead we needed to determine the service and the related configurations based on the checkout session.

Internationalization:
As mentioned, one of our most complex challenges was dealing with multiple languages, with each service supporting a different subset of languages and with a different default. This was further complicated by working within a single Next.js instance. Care had to be taken to ensure that the language configurations for each service would not override one another.

Current State and Future Improvements

What We’ve Accomplished

Flexible Architecture: Successfully serving multiple services with different requirements
Type Safety: Comprehensive TypeScript implementation ensuring reliable interfaces
Design System Integration: Consistent visual foundation with controlled customization
Multi-Language Support: Working solution for Japanese, English, and Traditional Chinese
Monorepo Structure: Scalable codebase organization for multiple teams

Areas for Improvement

🔄 Package Versioning: While our monorepo structure supports it, we haven’t fully implemented independent package versioning yet.

🔄 Documentation: Our frontend documentation covers the basics but needs expansion to fully explain all architectural decisions and patterns.

🔄 Visual Consistency Governance: As more teams implement flex elements, we need clearer guidelines and governance around visual customizations.

Lessons Learned

Complexity Management

The biggest lesson has been about managing complexity. Each requirement that seems simple in isolation can create exponential complexity when combined with others. The single domain requirement, multi-language support, and flexible UI customization each add layers of complexity that interact in unexpected ways.

Team Coordination

Building a platform used by multiple teams requires extensive coordination:

  • Regular sync meetings across all stakeholders
  • Clear documentation of decisions and rationale
  • Proactive communication about changes and impacts
  • Shared understanding of architectural principles

Flexibility vs Consistency Trade-offs

Finding the right balance between flexibility and consistency is an ongoing challenge. Too much flexibility leads to fragmented user experiences; too little flexibility prevents teams from meeting their specific business requirements.

Looking Forward

As we continue to evolve our checkout solution, we’re focusing on:

  1. Improved Documentation: Creating comprehensive guides for teams implementing new services
  2. Performance Optimization: Ensuring our flexible architecture doesn’t compromise on performance
  3. Governance Framework: Establishing clear guidelines for visual and functional consistency

Conclusion

Building a flexible checkout solution for multiple services has been one of the most challenging and rewarding projects I’ve worked on at Mercari. The architecture we’ve created successfully balances the need for consistency with the requirement for flexibility, enabling teams to build service-specific experiences while maintaining a cohesive platform.

The key to our success has been:

  • Clear architectural principles that guide decision-making
  • Strong type safety that prevents integration issues
  • Flexible element system that accommodates diverse requirements
  • Extensive team coordination that keeps everyone aligned

While we still have areas to improve, our foundation is solid and scalable. As Mercari continues to expand globally and launch new services, our checkout solution is ready to support that growth.

The journey of building this system has taught us that flexibility and consistency aren’t mutually exclusive – with the right architecture and team coordination, you can achieve both.

Tomorrow’s article will be "@foostan’s " Challenges faced and improvements made during six years of incident response/management."
Please continue to enjoy!

  • X
  • Facebook
  • linkedin
  • このエントリーをはてなブックマークに追加