Milesoft Square Extension (square-ext)

1. Overview & Purpose

The Square Extension (square-ext) is a unified, highly optimized, and robust developer utility designed to provide a secure and standardized facade around the official Square Java SDK. It simplifies payment processing integration within the Milesoft ecosystem by offering a simple API to manage customers, register payment cards securely, and process payment transactions, while natively isolating downstream developers from raw Square API boilerplate.

Key Benefits & Core Capabilities:

  • Standardized Facade: Abstracts complex Square SDK client objects (such as CustomersApi and PaymentsApi) into a clean, thread-safe, and dependency-injection friendly SquareService.
  • Type-Safe Currency Handling: Implements a custom Money domain model representing currency securely as dollars (long) and cents (int), ensuring arithmetic operations (plus, minus, isMoreThan, isLessThan, etc.) are highly robust and free from floating-point precision hazards.
  • Built-in Jackson Support: Provides a pre-configured Jackson module (MoneyModule) with dedicated serializer and deserializer classes to ensure clean, standardized double-decimal string representation in JSON payloads (e.g. "150.00").
  • Robust Error Mapping: Standardizes how Square-specific ApiException types and IO communication errors are handled by mapping them to a platform-friendly, unified CardError structure.

2. Installation & Dependency Configuration

The Square Extension is hosted securely inside our Google Cloud Artifact Registry repository.

To configure your application to resolve and pull this dependency:

2.1 Gradle Setup (build.gradle)

Apply the Google Cloud Artifact Registry plugin and configure the Maven repository block:

plugins {
    id "com.google.cloud.artifactregistry.gradle-plugin" version "2.2.5"
    id "java"
}

repositories {
    mavenLocal()
    mavenCentral()
    maven {
        url "artifactregistry://us-west2-maven.pkg.dev/milesoft-repo/repo-maven"
    }
}

dependencies {
    // Milesoft Commons foundation (required by square-ext)
    implementation "io.milesoft:commons:2.0.1"
    
    // Square Integration Extension
    implementation "io.milesoft:square-ext:2.0.1"
}

3. Configuring Square in Spring Boot

Downstream Spring Boot microservices and applications utilize a standard configuration pattern to initialize and inject the SquareService as a Spring-managed bean by loading the required credentials securely (e.g., from Secret Manager).

3.1 Define Application Properties

Configure your Square configuration properties in application.yml or application.properties:

secrets:
  square:
    token: "square_access_token_secret_key"

3.2 Square Configuration Bean

Instantiate SquareService inside a configuration class. Below is the standard configuration pattern used within the Milesoft ecosystem:

import static com.google.common.base.Preconditions.checkNotNull;

import com.squareup.square.Environment;
import io.milesoft.secrets.services.SecretManagerService;
import io.milesoft.square.services.SquareService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class SquareConfig {

    private final SquareService squareService;

    @Autowired
    public SquareConfig(SecretManagerService secretManagerService,
        @Value("${secrets.square.token}") String secretAccessToken) {

        checkNotNull(secretManagerService, "secretManagerService must not be null");

        // Load the access token securely from Secret Manager
        final String accessToken = secretManagerService.loadSecret(secretAccessToken);

        // Instantiate SquareService targeting Environment.PRODUCTION or Environment.SANDBOX
        this.squareService = new SquareService(Environment.PRODUCTION, accessToken);
    }

    @Bean
    public SquareService squareService() {
        return squareService;
    }
}

4. Key Components & Domain Models

4.1 Money

A custom domain model that handles monetary values using integer-based cents to avoid the rounding errors associated with floating-point calculations.

  • Factory Method: Money.of(long cents) parses a cents total into a structured Money instance.
  • Constants: Money.FREE (representing $0.00).
  • Core Operations:
    • plus(Money), minus(Money)
    • plusCents(long), minusCents(long)
    • plusDollars(long), minusDollars(long)
    • isMoreThan(Money), isLessThan(Money), isFree()
    • toCents() (returns total cents)

4.2 Card

Represents the metadata of a registered payment method.

  • Fields:
    • id (String): The Square card identifier.
    • type (String): The card brand (e.g. VISA, MASTERCARD).
    • last4 (String): The last 4 digits of the card number.

4.3 CardResult

The result model returned from all card-related and charging operations, representing either success or containing a collection of mapped failures.

  • Fields:
    • card (Card): The successfully registered card metadata (if applicable).
    • receiptUrl (String): The public receipt URL generated after a successful charge.
    • errors (List): A list of errors encountered during the operation.
  • Methods:
    • isSuccess(): Returns true if errors is empty.
    • isFailure(): Returns true if any CardError is present.

4.4 CardError

Standardized error structure holding the reason for a transaction or registration failure.

  • Fields:
    • category (String): The category of the error (e.g., error, merchant_error).
    • code (String): Square's specific error code.
    • detail (String): Readable explanation of the error.
    • field (String): The field associated with the error (if any).
  • Static Factory:
    • CardError.of(Exception e): Transforms a standard Java Exception into a generic CardError.

5. Jackson JSON Serialization

To serialize and deserialize Money smoothly, square-ext provides MoneyModule which registers custom serializer and deserializer logic.

  • MoneyModule: A standard Jackson SimpleModule registering MoneySerializer and MoneyDeserializer.
  • Format: Money is serialized as a plain string with two decimal places (e.g., "150.00"). A non-decimal string, empty string, or missing value defaults to Money.FREE during deserialization.

Usage:

Simply register the module on your Spring Boot ObjectMapper bean or manually instantiated ObjectMapper:

import com.fasterxml.jackson.databind.ObjectMapper;
import io.milesoft.square.json.MoneyModule;

ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new MoneyModule());

6. Practical Real-World Code Examples

These integration examples represent standard, battle-tested patterns for using SquareService within the Milesoft codebase.

Example A: Customer Registration & Payment Method Setup

Used during account onboarding to provision a Customer Profile in Square and register a credit card via a front-end generated Web SDK nonce.

import io.milesoft.square.services.SquareService;
import io.milesoft.square.domain.Card;
import io.milesoft.square.domain.CardResult;
import org.springframework.stereotype.Service;

@Service
public class BillingService {

    private final SquareService squareService;

    public BillingService(SquareService squareService) {
        this.squareService = squareService;
    }

    public String onboardCompanyAndRegisterCard(String companyName, String cardNonce) {
        // 1. Create a Customer Profile in Square
        String customerId = squareService.createCustomer(companyName);

        // 2. Register the Card (Nonce from frontend) to the Customer Profile
        CardResult result = squareService.createCard(customerId, cardNonce);

        if (result.isSuccess()) {
            Card card = result.getCard();
            System.out.println("Registered card " + card.getType() + " ending in " + card.getLast4());
            return customerId;
        } else {
            throw new IllegalStateException("Failed to register credit card: " + 
                result.getErrors().get(0).getDetail());
        }
    }
}

Example B: Processing a Transaction & Charging a Saved Card

Triggered during billing runs or instant checkouts to charge a customer's registered card.

import io.milesoft.square.services.SquareService;
import io.milesoft.square.domain.CardResult;
import io.milesoft.square.domain.Money;
import org.springframework.stereotype.Service;

@Service
public class TransactionService {

    private final SquareService squareService;

    public TransactionService(SquareService squareService) {
        this.squareService = squareService;
    }

    public boolean processPayment(String customerId, String cardId, long centsToCharge) {
        // Convert long cents value into Money domain model
        Money chargeAmount = Money.of(centsToCharge);

        // Execute payment transaction via Square service
        CardResult result = squareService.chargeCard(customerId, cardId, chargeAmount);

        if (result.isSuccess()) {
            System.out.println("Payment processed successfully. Receipt: " + result.getReceiptUrl());
            return true;
        } else {
            result.getErrors().forEach(error -> {
                System.err.println("Charge error [" + error.getCode() + "]: " + error.getDetail());
            });
            return false;
        }
    }
}