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:
- Improved Documentation: Creating comprehensive guides for teams implementing new services
- Performance Optimization: Ensuring our flexible architecture doesn’t compromise on performance
- 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!