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
CustomersApiandPaymentsApi) into a clean, thread-safe, and dependency-injection friendlySquareService. - Type-Safe Currency Handling: Implements a custom
Moneydomain model representing currency securely asdollars(long) andcents(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
ApiExceptiontypes and IO communication errors are handled by mapping them to a platform-friendly, unifiedCardErrorstructure.
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 structuredMoneyinstance. - 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(): Returnstrueiferrorsis empty.isFailure(): Returnstrueif anyCardErroris 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 genericCardError.
5. Jackson JSON Serialization
To serialize and deserialize Money smoothly, square-ext provides MoneyModule which registers custom serializer and deserializer logic.
MoneyModule: A standard JacksonSimpleModuleregisteringMoneySerializerandMoneyDeserializer.- 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 toMoney.FREEduring 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;
}
}
}