This guide provides comprehensive Java implementations for integrating with Square Payments API, including payment processing, customer management, subscription billing, and webhook handling.
Square Payments Overview
Key Features:
- Payment processing (credit cards, digital wallets)
- Customer and card management
- Subscription billing
- Invoice management
- Webhook handling for real-time notifications
Common Use Cases:
- E-commerce payments
- Subscription services
- Point-of-sale systems
- Mobile payments
- Invoice-based billing
Dependencies and Setup
Maven Dependencies
<properties> <spring-boot.version>3.1.0</spring-boot.version> <square-java-sdk.version>26.0.0</square-java-sdk.version> <jackson.version>2.15.2</jackson.version> </properties> <dependencies> <!-- Spring Boot --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <version>${spring-boot.version}</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> <version>${spring-boot.version}</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> <version>${spring-boot.version}</version> </dependency> <!-- Square SDK --> <dependency> <groupId>com.squareup.sdk</groupId> <artifactId>square</artifactId> <version>${square-java-sdk.version}</version> </dependency> <!-- JSON Processing --> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>${jackson.version}</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.datatype</groupId> <artifactId>jackson-datatype-jsr310</artifactId> <version>${jackson.version}</version> </dependency> <!-- Database --> <dependency> <groupId>org.postgresql</groupId> <artifactId>postgresql</artifactId> <scope>runtime</scope> </dependency> <!-- Utilities --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <scope>provided</scope> </dependency> <!-- Testing --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <version>${spring-boot.version}</version> <scope>test</scope> </dependency> </dependencies> Application Configuration
# application.yml app: square: # Square API Configuration environment: "production" # sandbox or production access-token: "${SQUARE_ACCESS_TOKEN:your_sandbox_token}" location-id: "${SQUARE_LOCATION_ID:your_location_id}" application-id: "${SQUARE_APPLICATION_ID:your_app_id}" # Webhook Configuration webhook: signature-key: "${SQUARE_WEBHOOK_SIGNATURE:your_webhook_signature}" enabled: true # Payment Configuration payment: timeout: 30000 max-retries: 3 currency: "USD" # Subscription Configuration subscription: plan-prefix: "plan_" trial-period-days: 0 spring: datasource: url: jdbc:postgresql://localhost:5432/square_payments username: postgres password: password driver-class-name: org.postgresql.Driver jpa: hibernate: ddl-auto: update show-sql: true properties: hibernate: dialect: org.hibernate.dialect.PostgreSQLDialect server: port: 8080 logging: level: com.example.squarepayments: DEBUG com.squareup.sdk: INFO Core Models
1. Payment Models
// PaymentRequest.java package com.example.squarepayments.model; import lombok.Data; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Positive; import java.math.BigDecimal; import java.util.Map; @Data public class PaymentRequest { @NotBlank(message = "Source ID is required") private String sourceId; // card nonce, customer ID, or card ID @NotNull(message = "Amount is required") @Positive(message = "Amount must be positive") private BigDecimal amount; private String customerId; private String locationId; private String referenceId; private String note; private String idempotencyKey; // Additional payment details private Boolean autocomplete = true; private String currency = "USD"; private Map<String, String> metadata; // Tip amount private BigDecimal tipAmount; // Shipping address private Address shippingAddress; // Buyer information private Buyer buyer; @Data public static class Address { private String firstName; private String lastName; private String addressLine1; private String addressLine2; private String locality; private String administrativeDistrictLevel1; private String postalCode; private String country; } @Data public static class Buyer { private String emailAddress; private String phoneNumber; } } // PaymentResponse.java package com.example.squarepayments.model; import lombok.Data; import lombok.Builder; import java.time.LocalDateTime; import java.math.BigDecimal; import java.util.Map; @Data @Builder public class PaymentResponse { private boolean success; private String message; private LocalDateTime timestamp; private PaymentData payment; private String squarePaymentId; private String receiptUrl; private String status; @Data @Builder public static class PaymentData { private String id; private BigDecimal amount; private String currency; private String status; private String sourceType; private String customerId; private String orderId; private String referenceId; private String note; private LocalDateTime createdAt; private LocalDateTime updatedAt; private Map<String, String> metadata; } public static PaymentResponse success(PaymentData payment) { return PaymentResponse.builder() .success(true) .message("Payment processed successfully") .timestamp(LocalDateTime.now()) .payment(payment) .squarePaymentId(payment.getId()) .status(payment.getStatus()) .build(); } public static PaymentResponse error(String message) { return PaymentResponse.builder() .success(false) .message(message) .timestamp(LocalDateTime.now()) .build(); } } // RefundRequest.java package com.example.squarepayments.model; import lombok.Data; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Positive; import java.math.BigDecimal; @Data public class RefundRequest { @NotBlank(message = "Payment ID is required") private String paymentId; @NotBlank(message = "Idempotency key is required") private String idempotencyKey; @NotNull(message = "Amount is required") @Positive(message = "Amount must be positive") private BigDecimal amount; private String reason; private String tenderId; } // RefundResponse.java package com.example.squarepayments.model; import lombok.Data; import lombok.Builder; import java.time.LocalDateTime; import java.math.BigDecimal; @Data @Builder public class RefundResponse { private boolean success; private String message; private LocalDateTime timestamp; private String refundId; private String status; private BigDecimal amount; private String currency; public static RefundResponse success(String refundId, String status, BigDecimal amount, String currency) { return RefundResponse.builder() .success(true) .message("Refund processed successfully") .timestamp(LocalDateTime.now()) .refundId(refundId) .status(status) .amount(amount) .currency(currency) .build(); } public static RefundResponse error(String message) { return RefundResponse.builder() .success(false) .message(message) .timestamp(LocalDateTime.now()) .build(); } } 2. Customer Models
// CustomerRequest.java package com.example.squarepayments.model; import lombok.Data; import jakarta.validation.constraints.Email; import java.util.Map; @Data public class CustomerRequest { private String givenName; private String familyName; @Email(message = "Invalid email format") private String emailAddress; private String phoneNumber; private String companyName; private String referenceId; private String note; private Address address; private Map<String, String> metadata; @Data public static class Address { private String addressLine1; private String addressLine2; private String locality; private String administrativeDistrictLevel1; private String postalCode; private String country; } } // CustomerResponse.java package com.example.squarepayments.model; import lombok.Data; import lombok.Builder; import java.time.LocalDateTime; import java.util.Map; @Data @Builder public class CustomerResponse { private boolean success; private String message; private LocalDateTime timestamp; private CustomerData customer; @Data @Builder public static class CustomerData { private String id; private String givenName; private String familyName; private String emailAddress; private String phoneNumber; private String companyName; private String referenceId; private String note; private LocalDateTime createdAt; private LocalDateTime updatedAt; private Map<String, String> metadata; private Integer version; } public static CustomerResponse success(CustomerData customer) { return CustomerResponse.builder() .success(true) .message("Customer operation successful") .timestamp(LocalDateTime.now()) .customer(customer) .build(); } public static CustomerResponse error(String message) { return CustomerResponse.builder() .success(false) .message(message) .timestamp(LocalDateTime.now()) .build(); } } // CustomerCardRequest.java package com.example.squarepayments.model; import lombok.Data; import jakarta.validation.constraints.NotBlank; @Data public class CustomerCardRequest { @NotBlank(message = "Customer ID is required") private String customerId; @NotBlank(message = "Card nonce is required") private String cardNonce; private String cardholderName; private String billingAddress; private String verificationToken; private Boolean isDefault = false; } 3. Subscription Models
// SubscriptionPlanRequest.java package com.example.squarepayments.model; import lombok.Data; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Positive; import java.math.BigDecimal; import java.util.Map; @Data public class SubscriptionPlanRequest { @NotBlank(message = "Plan name is required") private String name; @NotBlank(message = "Plan description is required") private String description; @NotNull(message = "Amount is required") @Positive(message = "Amount must be positive") private BigDecimal amount; @NotBlank(message = "Currency is required") private String currency = "USD"; @NotBlank(message = "Interval is required") private String interval; // DAILY, WEEKLY, MONTHLY, YEARLY private Integer intervalCount = 1; private Map<String, String> metadata; private Boolean active = true; } // SubscriptionRequest.java package com.example.squarepayments.model; import lombok.Data; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import java.time.LocalDate; import java.util.Map; @Data public class SubscriptionRequest { @NotBlank(message = "Customer ID is required") private String customerId; @NotBlank(message = "Plan ID is required") private String planId; @NotBlank(message = "Card ID is required") private String cardId; private LocalDate startDate; private String timezone; private Map<String, String> metadata; private String idempotencyKey; // Trial period private Integer trialPeriodDays; // Phases for complex subscriptions private java.util.List<Phase> phases; @Data public static class Phase { private String planId; private Integer ordinal; private Integer periods; private BigDecimal recurringPrice; private String cadence; } } // SubscriptionResponse.java package com.example.squarepayments.model; import lombok.Data; import lombok.Builder; import java.time.LocalDateTime; import java.math.BigDecimal; import java.util.Map; @Data @Builder public class SubscriptionResponse { private boolean success; private String message; private LocalDateTime timestamp; private SubscriptionData subscription; @Data @Builder public static class SubscriptionData { private String id; private String planId; private String customerId; private String status; private BigDecimal amount; private String currency; private String interval; private LocalDateTime startDate; private LocalDateTime createdAt; private LocalDateTime updatedAt; private Map<String, String> metadata; private String currentPhase; } public static SubscriptionResponse success(SubscriptionData subscription) { return SubscriptionResponse.builder() .success(true) .message("Subscription created successfully") .timestamp(LocalDateTime.now()) .subscription(subscription) .build(); } public static SubscriptionResponse error(String message) { return SubscriptionResponse.builder() .success(false) .message(message) .timestamp(LocalDateTime.now()) .build(); } } 4. Webhook Models
// WebhookEvent.java package com.example.squarepayments.model; import lombok.Data; import java.time.LocalDateTime; import java.util.Map; @Data public class WebhookEvent { private String merchantId; private String type; private String eventId; private LocalDateTime createdAt; private Map<String, Object> data; // Common webhook event types public static class EventType { public static final String PAYMENT_UPDATED = "payment.updated"; public static final String REFUND_UPDATED = "refund.updated"; public static final String SUBSCRIPTION_UPDATED = "subscription.updated"; public static final String CUSTOMER_CREATED = "customer.created"; public static final String CUSTOMER_UPDATED = "customer.updated"; public static final String INVOICE_UPDATED = "invoice.updated"; } } // WebhookNotification.java package com.example.squarepayments.model; import lombok.Data; import java.util.List; @Data public class WebhookNotification { private String merchantId; private String locationId; private String type; private String eventId; private String createdAt; private List<WebhookData> data; @Data public static class WebhookData { private String type; private String id; private Object object; } } Square Configuration and Client
1. Square Configuration
// SquareConfig.java package com.example.squarepayments.config; import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; @Data @Component @ConfigurationProperties(prefix = "app.square") public class SquareConfig { private String environment; private String accessToken; private String locationId; private String applicationId; private WebhookConfig webhook = new WebhookConfig(); private PaymentConfig payment = new PaymentConfig(); private SubscriptionConfig subscription = new SubscriptionConfig(); @Data public static class WebhookConfig { private String signatureKey; private boolean enabled; } @Data public static class PaymentConfig { private int timeout; private int maxRetries; private String currency; } @Data public static class SubscriptionConfig { private String planPrefix; private int trialPeriodDays; } } 2. Square Client Service
// SquareClientService.java package com.example.squarepayments.service; import com.squareup.sdk.*; import com.squareup.sdk.api.*; import com.squareup.sdk.models.*; import com.example.squarepayments.config.SquareConfig; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @Slf4j @Service public class SquareClientService { private final SquareClient squareClient; private final PaymentsApi paymentsApi; private final CustomersApi customersApi; private final SubscriptionsApi subscriptionsApi; private final CardsApi cardsApi; private final RefundsApi refundsApi; private final LocationsApi locationsApi; private final ExecutorService executorService; public SquareClientService(SquareConfig squareConfig) { Environment environment = squareConfig.getEnvironment().equals("production") ? Environment.PRODUCTION : Environment.SANDBOX; this.squareClient = new SquareClient.Builder() .environment(environment) .accessToken(squareConfig.getAccessToken()) .userAgentDetail("spring-boot-square-integration/1.0") .build(); this.paymentsApi = squareClient.getPaymentsApi(); this.customersApi = squareClient.getCustomersApi(); this.subscriptionsApi = squareClient.getSubscriptionsApi(); this.cardsApi = squareClient.getCardsApi(); this.refundsApi = squareClient.getRefundsApi(); this.locationsApi = squareClient.getLocationsApi(); this.executorService = Executors.newFixedThreadPool(10); log.info("Square client initialized for environment: {}", environment); } /** * Get Square client instance */ public SquareClient getSquareClient() { return squareClient; } /** * Get Payments API */ public PaymentsApi getPaymentsApi() { return paymentsApi; } /** * Get Customers API */ public CustomersApi getCustomersApi() { return customersApi; } /** * Get Subscriptions API */ public SubscriptionsApi getSubscriptionsApi() { return subscriptionsApi; } /** * Get Cards API */ public CardsApi getCardsApi() { return cardsApi; } /** * Get Refunds API */ public RefundsApi getRefundsApi() { return refundsApi; } /** * Get Locations API */ public LocationsApi getLocationsApi() { return locationsApi; } /** * Execute Square API call asynchronously */ public <T> CompletableFuture<T> executeAsync(java.util.concurrent.Callable<T> task) { return CompletableFuture.supplyAsync(() -> { try { return task.call(); } catch (Exception e) { log.error("Async Square API execution failed", e); throw new RuntimeException("Square API call failed", e); } }, executorService); } /** * Convert Money amount to Square Money object */ public Money toSquareMoney(java.math.BigDecimal amount, String currency) { return new Money.Builder() .amount(amount.movePointRight(2).longValue()) // Convert to cents .currency(currency) .build(); } /** * Convert Square Money to BigDecimal */ public java.math.BigDecimal fromSquareMoney(Money money) { if (money == null) return java.math.BigDecimal.ZERO; return java.math.BigDecimal.valueOf(money.getAmount()).movePointLeft(2); } /** * Generate idempotency key */ public String generateIdempotencyKey() { return java.util.UUID.randomUUID().toString(); } /** * Validate webhook signature */ public boolean validateWebhookSignature(String payload, String signature, String signatureKey) { try { // Square webhook signature validation logic // This is a simplified version - implement proper validation return true; } catch (Exception e) { log.error("Webhook signature validation failed", e); return false; } } } Payment Service
// PaymentService.java package com.example.squarepayments.service; import com.squareup.sdk.models.*; import com.example.squarepayments.model.*; import com.example.squarepayments.config.SquareConfig; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.math.BigDecimal; import java.util.concurrent.CompletableFuture; @Slf4j @Service @Transactional public class PaymentService { private final SquareClientService squareClientService; private final SquareConfig squareConfig; public PaymentService(SquareClientService squareClientService, SquareConfig squareConfig) { this.squareClientService = squareClientService; this.squareConfig = squareConfig; } /** * Process payment */ public PaymentResponse processPayment(PaymentRequest paymentRequest) { try { String idempotencyKey = paymentRequest.getIdempotencyKey() != null ? paymentRequest.getIdempotencyKey() : squareClientService.generateIdempotencyKey(); String locationId = paymentRequest.getLocationId() != null ? paymentRequest.getLocationId() : squareConfig.getLocationId(); // Create payment request CreatePaymentRequest request = new CreatePaymentRequest.Builder( paymentRequest.getSourceId(), idempotencyKey, squareClientService.toSquareMoney(paymentRequest.getAmount(), paymentRequest.getCurrency()) ) .locationId(locationId) .customerId(paymentRequest.getCustomerId()) .referenceId(paymentRequest.getReferenceId()) .note(paymentRequest.getNote()) .autocomplete(paymentRequest.getAutocomplete()) .build(); // Add tip if provided if (paymentRequest.getTipAmount() != null && paymentRequest.getTipAmount().compareTo(BigDecimal.ZERO) > 0) { request = request.toBuilder() .tipMoney(squareClientService.toSquareMoney(paymentRequest.getTipAmount(), paymentRequest.getCurrency())) .build(); } // Add metadata if provided if (paymentRequest.getMetadata() != null && !paymentRequest.getMetadata().isEmpty()) { request = request.toBuilder() .metadata(paymentRequest.getMetadata()) .build(); } // Add shipping address if provided if (paymentRequest.getShippingAddress() != null) { Address address = convertToSquareAddress(paymentRequest.getShippingAddress()); request = request.toBuilder() .shippingAddress(address) .build(); } // Add buyer information if provided if (paymentRequest.getBuyer() != null) { request = request.toBuilder() .buyerEmailAddress(paymentRequest.getBuyer().getEmailAddress()) .build(); } // Execute payment CompletableFuture<CreatePaymentResponse> future = squareClientService.executeAsync(() -> squareClientService.getPaymentsApi().createPaymentAsync(request).get() ); CreatePaymentResponse response = future.get(); if (response.isSuccess()) { Payment payment = response.getPayment(); log.info("Payment processed successfully: {}", payment.getId()); return PaymentResponse.success(convertToPaymentData(payment)); } else { log.error("Payment failed: {}", response.getErrors()); return PaymentResponse.error("Payment processing failed: " + response.getErrors().get(0).getDetail()); } } catch (Exception e) { log.error("Payment processing failed", e); return PaymentResponse.error("Payment processing failed: " + e.getMessage()); } } /** * Get payment by ID */ public PaymentResponse getPayment(String paymentId) { try { CompletableFuture<GetPaymentResponse> future = squareClientService.executeAsync(() -> squareClientService.getPaymentsApi().getPaymentAsync(paymentId).get() ); GetPaymentResponse response = future.get(); if (response.isSuccess()) { Payment payment = response.getPayment(); return PaymentResponse.success(convertToPaymentData(payment)); } else { log.error("Failed to get payment: {}", response.getErrors()); return PaymentResponse.error("Failed to retrieve payment: " + response.getErrors().get(0).getDetail()); } } catch (Exception e) { log.error("Failed to get payment: {}", paymentId, e); return PaymentResponse.error("Failed to retrieve payment: " + e.getMessage()); } } /** * List payments with filters */ public java.util.List<PaymentResponse.PaymentData> listPayments(String customerId, java.time.LocalDateTime beginTime, java.time.LocalDateTime endTime, String status) { try { ListPaymentsRequest.Builder requestBuilder = new ListPaymentsRequest.Builder(); if (customerId != null) { requestBuilder.customerId(customerId); } if (beginTime != null) { requestBuilder.beginTime(beginTime.toString()); } if (endTime != null) { requestBuilder.endTime(endTime.toString()); } if (status != null) { requestBuilder.status(status); } CompletableFuture<ListPaymentsResponse> future = squareClientService.executeAsync(() -> squareClientService.getPaymentsApi().listPaymentsAsync(requestBuilder.build()).get() ); ListPaymentsResponse response = future.get(); if (response.isSuccess()) { return response.getPayments().stream() .map(this::convertToPaymentData) .collect(java.util.stream.Collectors.toList()); } else { log.error("Failed to list payments: {}", response.getErrors()); throw new RuntimeException("Failed to list payments: " + response.getErrors().get(0).getDetail()); } } catch (Exception e) { log.error("Failed to list payments", e); throw new RuntimeException("Failed to list payments", e); } } /** * Process refund */ public RefundResponse processRefund(RefundRequest refundRequest) { try { String idempotencyKey = refundRequest.getIdempotencyKey() != null ? refundRequest.getIdempotencyKey() : squareClientService.generateIdempotencyKey(); CreateRefundRequest request = new CreateRefundRequest.Builder( idempotencyKey, refundRequest.getPaymentId(), squareClientService.toSquareMoney(refundRequest.getAmount(), "USD") ) .reason(refundRequest.getReason()) .tenderId(refundRequest.getTenderId()) .build(); CompletableFuture<CreateRefundResponse> future = squareClientService.executeAsync(() -> squareClientService.getRefundsApi().createRefundAsync(request).get() ); CreateRefundResponse response = future.get(); if (response.isSuccess()) { Refund refund = response.getRefund(); log.info("Refund processed successfully: {}", refund.getId()); return RefundResponse.success( refund.getId(), refund.getStatus(), squareClientService.fromSquareMoney(refund.getAmountMoney()), refund.getAmountMoney().getCurrency() ); } else { log.error("Refund failed: {}", response.getErrors()); return RefundResponse.error("Refund processing failed: " + response.getErrors().get(0).getDetail()); } } catch (Exception e) { log.error("Refund processing failed", e); return RefundResponse.error("Refund processing failed: " + e.getMessage()); } } /** * Convert Square Payment to PaymentData */ private PaymentResponse.PaymentData convertToPaymentData(Payment payment) { return PaymentResponse.PaymentData.builder() .id(payment.getId()) .amount(squareClientService.fromSquareMoney(payment.getAmountMoney())) .currency(payment.getAmountMoney().getCurrency()) .status(payment.getStatus()) .sourceType(payment.getSourceType()) .customerId(payment.getCustomerId()) .orderId(payment.getOrderId()) .referenceId(payment.getReferenceId()) .note(payment.getNote()) .createdAt(payment.getCreatedAt() != null ? payment.getCreatedAt().toLocalDateTime() : null) .updatedAt(payment.getUpdatedAt() != null ? payment.getUpdatedAt().toLocalDateTime() : null) .metadata(payment.getMetadata()) .build(); } /** * Convert application Address to Square Address */ private Address convertToSquareAddress(PaymentRequest.Address appAddress) { return new Address.Builder() .firstName(appAddress.getFirstName()) .lastName(appAddress.getLastName()) .addressLine1(appAddress.getAddressLine1()) .addressLine2(appAddress.getAddressLine2()) .locality(appAddress.getLocality()) .administrativeDistrictLevel1(appAddress.getAdministrativeDistrictLevel1()) .postalCode(appAddress.getPostalCode()) .country(appAddress.getCountry()) .build(); } /** * Complete partial payment */ public PaymentResponse completePayment(String paymentId) { try { CompletableFuture<CompletePaymentResponse> future = squareClientService.executeAsync(() -> squareClientService.getPaymentsApi().completePaymentAsync(paymentId).get() ); CompletePaymentResponse response = future.get(); if (response.isSuccess()) { Payment payment = response.getPayment(); return PaymentResponse.success(convertToPaymentData(payment)); } else { log.error("Failed to complete payment: {}", response.getErrors()); return PaymentResponse.error("Failed to complete payment: " + response.getErrors().get(0).getDetail()); } } catch (Exception e) { log.error("Failed to complete payment: {}", paymentId, e); return PaymentResponse.error("Failed to complete payment: " + e.getMessage()); } } /** * Cancel payment */ public PaymentResponse cancelPayment(String paymentId) { try { CompletableFuture<CancelPaymentResponse> future = squareClientService.executeAsync(() -> squareClientService.getPaymentsApi().cancelPaymentAsync(paymentId).get() ); CancelPaymentResponse response = future.get(); if (response.isSuccess()) { Payment payment = response.getPayment(); return PaymentResponse.success(convertToPaymentData(payment)); } else { log.error("Failed to cancel payment: {}", response.getErrors()); return PaymentResponse.error("Failed to cancel payment: " + response.getErrors().get(0).getDetail()); } } catch (Exception e) { log.error("Failed to cancel payment: {}", paymentId, e); return PaymentResponse.error("Failed to cancel payment: " + e.getMessage()); } } } Customer Service
```java
// CustomerService.java
package com.example.squarepayments.service;
import com.squareup.sdk.models.; import com.example.squarepayments.model.;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.concurrent.CompletableFuture;
@Slf4j
@Service
@Transactional
public class CustomerService {
private final SquareClientService squareClientService;
public CustomerService(SquareClientService squareClientService) { this.squareClientService = squareClientService; } /** * Create customer */ public CustomerResponse createCustomer(CustomerRequest customerRequest) { try { CreateCustomerRequest.Builder requestBuilder = new CreateCustomerRequest.Builder(); if (customerRequest.getGivenName() != null) { requestBuilder.givenName(customerRequest.getGivenName()); } if (customerRequest.getFamilyName() != null) { requestBuilder.familyName(customerRequest.getFamilyName()); } if (customerRequest.getEmailAddress() != null) { requestBuilder.emailAddress(customerRequest.getEmailAddress()); } if (customerRequest.getPhoneNumber() != null) { requestBuilder.phoneNumber(customerRequest.getPhoneNumber()); } if (customerRequest.getCompanyName() != null) { requestBuilder.companyName(customerRequest.getCompanyName()); } if (customerRequest.getReferenceId() != null) { requestBuilder.referenceId(customerRequest.getReferenceId()); } if (customerRequest.getNote() != null) { requestBuilder.note(customerRequest.getNote()); } if (customerRequest.getAddress() != null) { Address address = convertToSquareAddress(customerRequest.getAddress()); requestBuilder.address(address); } if (customerRequest.getMetadata() != null) { requestBuilder.metadata(customerRequest.getMetadata()); } String idempotencyKey = squareClientService.generateIdempotencyKey(); requestBuilder.idempotencyKey(idempotencyKey); CompletableFuture<CreateCustomerResponse> future = squareClientService.executeAsync(() -> squareClientService.getCustomersApi().createCustomerAsync(requestBuilder.build()).get() ); CreateCustomerResponse response = future.get(); if (response.isSuccess()) { Customer customer = response.getCustomer(); log.info("Customer created successfully: {}", customer.getId()); return CustomerResponse.success(convertToCustomerData(customer)); } else { log.error("Failed to create customer: {}", response.getErrors()); return CustomerResponse.error("Failed to create customer: " + response.getErrors().get(0).getDetail()); } } catch (Exception e) { log.error("Failed to create customer", e); return CustomerResponse.error("Failed to create customer: " + e.getMessage()); } } /** * Get customer by ID */ public CustomerResponse getCustomer(String customerId) { try { CompletableFuture<RetrieveCustomerResponse> future = squareClientService.executeAsync(() -> squareClientService.getCustomersApi().retrieveCustomerAsync(customerId).get() ); RetrieveCustomerResponse response = future.get(); if (response.isSuccess()) { Customer customer = response.getCustomer(); return CustomerResponse.success(convertToCustomerData(customer)); } else { log.error("Failed to get customer: {}", response.getErrors()); return CustomerResponse.error("Failed to retrieve customer: " + response.getErrors().get(0).getDetail()); } } catch (Exception e) { log.error("Failed to get customer: {}", customerId, e); return CustomerResponse.error("Failed to retrieve customer: " + e.getMessage()); } } /** * Update customer */ public CustomerResponse updateCustomer(String customerId, CustomerRequest customerRequest) { try { UpdateCustomerRequest.Builder requestBuilder = new UpdateCustomerRequest.Builder(); if (customerRequest.getGivenName() != null) { requestBuilder.givenName(customerRequest.getGivenName()); } if (customerRequest.getFamilyName() != null) { requestBuilder.familyName(customerRequest.getFamilyName()); } if (customerRequest.getEmailAddress() != null) { requestBuilder.emailAddress(customerRequest.getEmailAddress()); } if (customerRequest.getPhoneNumber() != null) { requestBuilder.phoneNumber(customerRequest.getPhoneNumber()); } if (customerRequest.getCompanyName() != null) { requestBuilder.companyName(customerRequest.getCompanyName()); } if (customerRequest.getReferenceId() != null) { requestBuilder.referenceId(customerRequest.getReferenceId()); } if (customerRequest.getNote() != null) { requestBuilder.note(customerRequest.getNote()); } if (customerRequest.getAddress() != null) { Address address = convertToSquareAddress(customerRequest.getAddress()); requestBuilder.address(address); } if (customerRequest.getMetadata() != null) { requestBuilder.metadata(customerRequest.getMetadata()); } CompletableFuture<UpdateCustomerResponse> future = squareClientService.executeAsync(() -> squareClientService.getCustomersApi().updateCustomerAsync(customerId, requestBuilder.build()).get() ); UpdateCustomerResponse response = future.get(); if (response.isSuccess()) { Customer customer = response.getCustomer(); log.info("Customer updated successfully: {}", customer.getId()); return CustomerResponse.success(convertToCustomerData(customer)); } else { log