Current File : /var/www/pediatribu/wp-content/plugins/wpforms-lite/src/Integrations/Square/Api/Api.php |
<?php
namespace WPForms\Integrations\Square\Api;
use stdClass;
use WPForms\Integrations\Square\Helpers;
use WPForms\Vendor\Square\Models\Invoice;
use WPForms\Vendor\Square\Exceptions\ApiException;
use WPForms\Vendor\Square\Http\ApiResponse;
use WPForms\Vendor\Square\Models\Address;
use WPForms\Vendor\Square\Models\Card;
use WPForms\Vendor\Square\Models\CatalogObject;
use WPForms\Vendor\Square\Models\CatalogObjectType;
use WPForms\Vendor\Square\Models\CatalogQuery;
use WPForms\Vendor\Square\Models\CatalogQueryExact;
use WPForms\Vendor\Square\Models\CatalogSubscriptionPlan;
use WPForms\Vendor\Square\Models\UpdateSubscriptionResponse;
use WPForms\Vendor\Square\Models\CatalogSubscriptionPlanVariation;
use WPForms\Vendor\Square\Models\CreateCardRequest;
use WPForms\Vendor\Square\Models\CreateCustomerRequest;
use WPForms\Vendor\Square\Models\CreateOrderRequest;
use WPForms\Vendor\Square\Models\CreatePaymentRequest;
use WPForms\Vendor\Square\Models\CreatePaymentResponse;
use WPForms\Vendor\Square\Models\CreateSubscriptionRequest;
use WPForms\Vendor\Square\Models\CreateSubscriptionResponse;
use WPForms\Vendor\Square\Models\Money;
use WPForms\Vendor\Square\Models\Order;
use WPForms\Vendor\Square\Models\OrderLineItem;
use WPForms\Vendor\Square\Models\OrderLineItemDiscount;
use WPForms\Vendor\Square\Models\OrderSource;
use WPForms\Vendor\Square\Models\OrderState;
use WPForms\Vendor\Square\Models\Payment;
use WPForms\Vendor\Square\Models\RefundPaymentRequest;
use WPForms\Vendor\Square\Models\SearchCatalogObjectsRequest;
use WPForms\Vendor\Square\Models\Subscription;
use WPForms\Vendor\Square\Models\SubscriptionPhase;
use WPForms\Vendor\Square\Models\SubscriptionPricing;
use WPForms\Vendor\Square\Models\SubscriptionSource;
use WPForms\Vendor\Square\Models\UpdateOrderRequest;
use WPForms\Vendor\Square\Models\UpdateSubscriptionRequest;
use WPForms\Vendor\Square\Models\UpsertCatalogObjectRequest;
use WPForms\Vendor\Square\Models\Customer;
use WPForms\Vendor\Square\SquareClient;
use WPForms\Integrations\Square\Connection;
use WPForms\Integrations\Square\Square;
/**
* WPForms Square API class.
*
* @since 1.9.5
*/
class Api {
/**
* Square API client instance.
*
* @since 1.9.5
*
* @var SquareClient
*/
private $client;
/**
* Payment token (card nonce) generated by the Web Payments SDK.
*
* @since 1.9.5
*
* @var string
*/
private $source_id;
/**
* Last API call response.
*
* @since 1.9.5
*
* @var ApiResponse
*/
private $response;
/**
* Last API call exception.
*
* @since 1.9.5
*
* @var ApiException
*/
private $exception;
/**
* API errors.
*
* @since 1.9.5
*
* @var array
*/
private $errors;
/**
* Constructs the main Square API wrapper class.
*
* @since 1.9.5
*
* @param Connection $connection Connection object.
*/
public function __construct( $connection ) {
$this->client = new SquareClient(
[
'accessToken' => $connection->get_access_token(),
'environment' => $connection->get_mode(),
]
);
}
/**
* Set tokens from a submitted form data.
*
* @since 1.9.5
*
* @param array $entry Copy of original $_POST.
*/
public function set_payment_tokens( array $entry ) {
if ( ! empty( $entry['square']['source_id'] ) ) {
$this->source_id = $entry['square']['source_id'];
}
}
/**
* Check if OAuth connection is present and ready to use.
*
* @since 1.9.5
*/
private function check_connection() {
$connection = Connection::get();
if ( ! $connection || ! $connection->is_configured() ) {
$this->errors[] = esc_html__( 'Square account connection is missing.', 'wpforms-lite' );
return;
}
if ( ! $connection->is_valid() ) {
$this->errors[] = esc_html__( 'Square account connection is invalid.', 'wpforms-lite' );
return;
}
if ( ! $connection->is_currency_matched() ) {
$this->errors[] = esc_html__( 'The currency associated with the payment is not valid for the provided business location.', 'wpforms-lite' );
}
}
/**
* Check if single payment tokens are present.
*
* @since 1.9.5
*/
private function check_payment_tokens() {
if ( empty( $this->source_id ) ) {
$this->errors[] = esc_html__( 'Square payment stopped, missing card tokens.', 'wpforms-lite' );
}
}
/**
* Check if all required general arguments are present.
*
* @since 1.9.5
*
* @param array $args Arguments to check.
*/
private function check_required_args_general( array $args ) {
if ( empty( $args['location_id'] ) ) {
$this->errors[] = esc_html__( 'Missing location ID.', 'wpforms-lite' );
}
if ( empty( $args['currency'] ) ) {
$this->errors[] = esc_html__( 'Missing currency.', 'wpforms-lite' );
}
if ( empty( $args['amount'] ) && ! is_numeric( $args['amount'] ) ) {
$this->errors[] = esc_html__( 'Missing amount.', 'wpforms-lite' );
}
}
/**
* Check if all required single payment arguments are present.
*
* @since 1.9.5
*
* @param array $args Arguments to check.
*/
private function check_required_args_single( array $args ) {
if ( empty( $args['order_items'] ) ) {
$this->errors[] = esc_html__( 'Missing order/payment items.', 'wpforms-lite' );
}
}
/**
* Process single transaction.
*
* @since 1.9.5
*
* @param array $args Payment arguments.
*/
public function process_single_transaction( array $args ) {
$this->check_connection();
$this->check_payment_tokens();
$this->check_required_args_general( $args );
$this->check_required_args_single( $args );
if ( $this->errors ) {
return;
}
$result = $this->perform_single_transaction( $args );
/**
* Fire when a single transaction is performed.
*
* @since 1.9.5
*
* @param array $result Single transaction result.
* @param array $args Payment arguments.
* @param Api $api Api class instance.
*/
do_action( 'wpforms_square_api_process_single_transaction_after', $result, $args, $this ); // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName
}
/**
* Process subscription transaction.
*
* @since 1.9.5
*
* @param array $args Payment arguments.
*/
public function process_subscription_transaction( array $args ) {
$this->check_connection();
$this->check_payment_tokens();
$this->check_required_args_general( $args );
$this->check_required_args_subscription( $args );
if ( $this->errors ) {
return;
}
$this->perform_subscription_transaction( $args );
}
/**
* Refund payment.
*
* @since 1.9.5
*
* @param string $payment_id Payment ID.
* @param array $args Payment data.
*/
public function refund_payment( string $payment_id, array $args ): bool {
try {
$request = new RefundPaymentRequest( $this->get_idempotency_key(), new Money() );
$request->setPaymentId( $payment_id );
$request->setReason( $args['reason'] );
$request->getAmountMoney()->setAmount( $args['amount'] );
$request->getAmountMoney()->setCurrency( $args['currency'] );
$this->response = $this->client->getRefundsApi()->refundPayment( $request );
if ( ! $this->response->isSuccess() ) {
return false;
}
} catch ( ApiException $e ) {
$this->exception = $e;
return false;
}
return true;
}
/**
* Update subscription.
*
* @since 1.9.5
*
* @param array $args Subscription arguments.
*/
public function update_subscription( array $args ) {
$subscription = $this->retrieve_subscription( $args['id'] );
if ( ! $subscription instanceof Subscription ) {
return;
}
$request = $this->get_update_subscription_request_object( $subscription, $args );
$this->send_update_subscription_request( $args['id'], $request );
}
/**
* Cancel subscription.
*
* @since 1.9.5
*
* @param string $subscription_id Subscription id.
*
* @return bool
*/
public function cancel_subscription( string $subscription_id ): bool {
try {
$this->response = $this->client->getSubscriptionsApi()->cancelSubscription( $subscription_id );
if ( ! $this->response->isSuccess() ) {
return false;
}
} catch ( ApiException $e ) {
$this->exception = $e;
return false;
}
return true;
}
/**
* Retrieve a Card object.
*
* @since 1.9.5
*
* @param Subscription $subscription Subscription object.
*
* @return Card|null
*/
public function get_subscription_card( $subscription ) {
if ( ! $subscription instanceof Subscription ) {
return null;
}
$card_id = $subscription->getCardId();
if ( empty( $card_id ) ) {
return null;
}
// Get a customer.
$card = $this->send_retrieve_card_request( $card_id );
if ( ! $card instanceof Card ) {
return null;
}
return $card;
}
/**
* Send a retrieve subscription request to API.
*
* @since 1.9.5
*
* @param string $id The ID of the subscription to retrieve.
*
* @return Subscription|null
*/
public function retrieve_subscription( string $id ) {
try {
$response = $this->client->getSubscriptionsApi()->retrieveSubscription( $id );
if ( ! $response->isSuccess() ) {
return null;
}
return $response->getResult()->getSubscription();
} catch ( ApiException $e ) {
return null;
}
}
/**
* Retrieve a Card object.
*
* @since 1.9.5
*
* @param Subscription $subscription Subscription object.
*
* @return Invoice|null
*/
public function get_latest_subscription_invoice( $subscription ) {
if ( ! $subscription instanceof Subscription ) {
return null;
}
try {
$invoices = $subscription->getInvoiceIds();
if ( empty( $invoices ) ) {
return null;
}
// Get a customer.
$response = $this->client->getInvoicesApi()->getInvoice( reset( $invoices ) ); // Get the latest invoice by using the first ID in the array as the subscription's invoice IDs are sorted by date in ascending order.
if ( ! $response->isSuccess() ) {
return null;
}
return $response->getResult()->getInvoice();
} catch ( ApiException $e ) {
return null;
}
}
/**
* Perform a single transaction.
*
* @since 1.9.5
*
* @param array $args Payment arguments.
*
* @return array
*/
private function perform_single_transaction( array $args ): array {
$result = [];
// Create an order.
$order_request = $this->prepare_create_order_request( $args );
$order = $this->send_create_order_request( $order_request );
if ( ! $order instanceof Order ) {
return $result;
}
$result['order'] = $order;
// Create a payment.
$payment_request = $this->prepare_create_payment_request( $order, $args );
$payment = $this->send_create_payment_request( $payment_request );
// In this case we should cancel an order.
if ( ! $payment instanceof Payment ) {
$update_order_request = $this->prepare_update_order_request( $order );
$updated_order = $this->send_update_order_request( $order->getId(), $update_order_request );
return $updated_order instanceof Order ? [ 'order' => $updated_order ] : $result;
}
$result['payment'] = $payment;
return $result;
}
/**
* Prepare a create order request object for sending to API.
*
* @since 1.9.5
*
* @param array $args Single payment arguments.
*
* @return CreateOrderRequest
*/
private function prepare_create_order_request( array $args ): CreateOrderRequest {
$request = $this->get_create_order_request_object( $args );
$request = $this->create_order_request_set_line_items( $request, $args );
$request = $this->create_order_request_set_discounts( $request, $args );
/**
* Filter a create order request object.
*
* @since 1.9.5
*
* @param CreateOrderRequest $request Create order request object.
* @param array $args Payment arguments.
* @param Api $api Api class instance.
*/
return apply_filters( 'wpforms_square_api_prepare_create_order_request', $request, $args, $this ); // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName
}
/**
* Prepare an update order request object for sending to API.
*
* @since 1.9.5
*
* @param Order $order Order object.
*
* @return UpdateOrderRequest
*/
private function prepare_update_order_request( $order ): UpdateOrderRequest {
$request = $this->get_update_order_request_object( $order );
/**
* Filter an update order request object.
*
* @since 1.9.5
*
* @param UpdateOrderRequest $request Update order request object.
* @param Order $order Order object.
* @param Api $api Api class instance.
*/
return apply_filters( 'wpforms_square_api_prepare_update_order_request', $request, $order, $this ); // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName
}
/**
* Prepare a create payment request object for sending to API.
*
* @since 1.9.5
*
* @param Order $order Order object.
* @param array $args Payment arguments.
*
* @return CreatePaymentRequest
*/
private function prepare_create_payment_request( $order, array $args ): CreatePaymentRequest {
$request = $this->get_create_payment_request_object();
$request = $this->create_payment_request_set_order( $request, $order );
if ( ! empty( $args['billing'] ) ) {
$address = $this->get_address_object( $args['billing'] );
$request->setBillingAddress( $address );
}
if ( ! empty( $args['buyer_email'] ) ) {
$request->setBuyerEmailAddress( $args['buyer_email'] );
}
if ( ! empty( $args['note'] ) ) {
$request->setNote( $args['note'] );
}
/**
* Filter a create payment request object.
*
* @since 1.9.5
*
* @param CreateOrderRequest $request Create payment request object.
* @param array $args Payment arguments.
* @param Api $api Api class instance.
*/
return apply_filters( 'wpforms_square_api_prepare_create_payment_request', $request, $args, $this ); // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName
}
/**
* Retrieve a create order request object.
*
* @since 1.9.5
*
* @param array $args Payment arguments.
*
* @return CreateOrderRequest
*/
private function get_create_order_request_object( array $args ): CreateOrderRequest {
$request = new CreateOrderRequest();
$request->setIdempotencyKey( $this->get_idempotency_key() );
$request->setOrder( new Order( $args['location_id'] ) );
$request->getOrder()->setSource( new OrderSource() );
$request->getOrder()->getSource()->setName( Square::APP_NAME );
return $request;
}
/**
* Retrieve an update order request object.
*
* @since 1.9.5
*
* @param Order $order Order object.
*
* @return UpdateOrderRequest
*/
private function get_update_order_request_object( $order ): UpdateOrderRequest {
$request = new UpdateOrderRequest();
$request->setIdempotencyKey( $this->get_idempotency_key() );
$request->setOrder( $order );
$request->getOrder()->setState( OrderState::CANCELED );
return $request;
}
/**
* Retrieve a create payment request object.
*
* @since 1.9.5
*
* @return CreatePaymentRequest
*/
private function get_create_payment_request_object(): CreatePaymentRequest {
$request = new CreatePaymentRequest( $this->source_id, $this->get_idempotency_key() );
$request->setAmountMoney( new Money() );
return $request;
}
/**
* Send a create order request object to API.
*
* @since 1.9.5
*
* @param CreateOrderRequest $request Create order request object.
*
* @return Order|null
*/
private function send_create_order_request( $request ) {
try {
$this->response = $this->client->getOrdersApi()->createOrder( $request );
if ( ! $this->response->isSuccess() ) {
$this->errors[] = esc_html__( 'Square fail: order was not created.', 'wpforms-lite' );
return null;
}
return $this->response->getResult()->getOrder();
} catch ( ApiException $e ) {
$this->exception = $e;
$this->errors[] = esc_html__( 'Square fail: order was not created.', 'wpforms-lite' );
return null;
}
}
/**
* Send an update order request object to API.
*
* @since 1.9.5
*
* @param string $order_id The ID of the order to update.
* @param UpdateOrderRequest $request Update order request object.
*
* @return Order|null
*/
private function send_update_order_request( string $order_id, $request ) {
try {
$response = $this->client->getOrdersApi()->updateOrder( $order_id, $request );
if ( ! $response->isSuccess() ) {
return null;
}
return $response->getResult()->getOrder();
} catch ( ApiException $e ) {
return null;
}
}
/**
* Send a create payment request to API.
*
* @since 1.9.5
*
* @param CreatePaymentRequest $request Create payment request object.
*
* @return Payment|null
*/
private function send_create_payment_request( $request ) {
try {
$this->response = $this->client->getPaymentsApi()->createPayment( $request );
if ( ! $this->response->isSuccess() ) {
$this->errors[] = esc_html__( 'Square fail: payment was not processed.', 'wpforms-lite' );
$this->add_response_errors_message();
return null;
}
return $this->response->getResult()->getPayment();
} catch ( ApiException $e ) {
$this->exception = $e;
$this->errors[] = esc_html__( 'Square fail: payment was not processed.', 'wpforms-lite' );
$this->add_response_errors_message( 'Exception' );
return null;
}
}
/**
* Set line items to a create order request object.
*
* @since 1.9.5
*
* @param CreateOrderRequest $request Request to create an order.
* @param array $args Payment arguments.
*
* @return CreateOrderRequest
*/
private function create_order_request_set_line_items( $request, array $args ): CreateOrderRequest {
$line_items = [];
foreach ( (array) $args['order_items'] as $item ) {
// Order item without variations.
if ( empty( $item['variations'] ) ) {
$line_items[] = $this->get_order_line_item_object( $item, $args );
continue;
}
// Order item with variations (e.g. Small, Medium and Large).
foreach ( (array) $item['variations'] as $variation ) {
$order_line_item = $this->get_order_line_item_object( $variation, $args );
$order_line_item->setVariationName( $variation['variation_name'] );
$line_items[] = $order_line_item;
}
}
$request->getOrder()->setLineItems( $line_items );
return $request;
}
/**
* Set discounts to a create order request object.
*
* @since 1.9.5
*
* @param CreateOrderRequest $request Request to create an order.
* @param array $args Payment arguments.
*
* @return CreateOrderRequest
*/
private function create_order_request_set_discounts( $request, array $args ): CreateOrderRequest {
$discounts = [];
if ( empty( $args['discounts'] ) ) {
return $request;
}
foreach ( (array) $args['discounts'] as $discount ) {
$discounts[] = $this->get_order_discount_object( $discount, $args );
}
if ( ! empty( $discounts ) ) {
$request->getOrder()->setDiscounts( $discounts );
}
return $request;
}
/**
* Set order to a create payment request object.
*
* @since 1.9.5
*
* @param CreatePaymentRequest $request Create payment request object.
* @param Order $order Order object.
*
* @return CreatePaymentRequest
*/
private function create_payment_request_set_order( $request, $order ): CreatePaymentRequest {
$request->setOrderId( $order->getId() );
$request->setLocationId( $order->getLocationId() );
$amount = $order->getTotalMoney()->getAmount();
$currency = $order->getTotalMoney()->getCurrency();
$request->getAmountMoney()->setAmount( $amount );
$request->getAmountMoney()->setCurrency( $currency );
if ( ! Helpers::is_license_ok() && Helpers::is_application_fee_supported( $currency ) ) {
$request->setAppFeeMoney( new Money() );
$request->getAppFeeMoney()->setAmount( (int) ( round( $amount * 0.03 ) ) );
$request->getAppFeeMoney()->setCurrency( $currency );
}
return $request;
}
/**
* Retrieve a single order line item object.
*
* @since 1.9.5
*
* @param array $item Order item data.
* @param array $args Payment arguments.
*
* @return OrderLineItem
*/
private function get_order_line_item_object( array $item, array $args ): OrderLineItem {
$order_line_items = new OrderLineItem( $item['quantity'] );
$order_line_items->setName( $item['name'] );
$order_line_items->setBasePriceMoney( new Money() );
// Round to the nearest whole number because $item['amount'] can contain a number close to,
// but slightly under it, due to how it is stored in the memory.
$order_line_items->getBasePriceMoney()->setAmount( round( $item['amount'] ) );
$order_line_items->getBasePriceMoney()->setCurrency( $args['currency'] );
return $order_line_items;
}
/**
* Retrieve a single order discount object.
*
* @since 1.9.5
*
* @param array $discount Discount data.
* @param array $args Payment arguments.
*
* @return OrderLineItemDiscount
*/
private function get_order_discount_object( array $discount, array $args ): OrderLineItemDiscount {
$order_discounts = new OrderLineItemDiscount();
$order_discounts->setName( $discount['name'] );
$order_discounts->setAmountMoney( new Money() );
$order_discounts->getAmountMoney()->setAmount( $discount['amount'] );
$order_discounts->getAmountMoney()->setCurrency( $args['currency'] );
return $order_discounts;
}
/**
* Prepare and retrieve an address object.
*
* @since 1.9.5
*
* @param array $data Address data.
*
* @return Address
*/
private function get_address_object( array $data ): Address {
$address = new Address();
// The empty country value may occur API errors.
if ( ! empty( $data['address']['country'] ) ) {
$address->setAddressLine1( $data['address']['address1'] );
$address->setAddressLine2( $data['address']['address2'] );
$address->setLocality( $data['address']['city'] );
$address->setAdministrativeDistrictLevel1( $data['address']['state'] );
$address->setPostalCode( $data['address']['postal'] );
$address->setCountry( $data['address']['country'] );
}
if ( ! empty( $data['first_name'] ) ) {
$address->setFirstName( $data['first_name'] );
}
if ( ! empty( $data['last_name'] ) ) {
$address->setLastName( $data['last_name'] );
}
return $address;
}
/**
* Retrieve information of all locations of a business.
*
* @since 1.9.5
*
* @return array|Location|null
*/
public function get_locations() {
try {
$this->response = $this->client->getLocationsApi()->listLocations();
if ( ! $this->response->isSuccess() ) {
return null;
}
return $this->response->getResult()->getLocations();
} catch ( ApiException $e ) {
$this->exception = $e;
return null;
}
}
/**
* Retrieve a Merchant object with details.
*
* @since 1.9.5
*
* @param string $id The Merchant ID.
*
* @return Merchant|null
*/
public function get_merchant( string $id ) {
try {
$this->response = $this->client->getMerchantsApi()->retrieveMerchant( $id );
if ( ! $this->response->isSuccess() ) {
return null;
}
return $this->response->getResult()->getMerchant();
} catch ( ApiException $e ) {
$this->exception = $e;
return null;
}
}
/**
* Retrieve an idempotency key.
*
* @since 1.9.5
*
* @link https://developer.squareup.com/docs/working-with-apis/idempotency
*
* @return string
*/
private function get_idempotency_key(): string {
return uniqid( '', false );
}
/**
* Retrieve API errors.
*
* @since 1.9.5
*
* @return array|null
*/
public function get_errors() {
return $this->errors;
}
/**
* Retrieve last API call errors if are exist.
*
* @since 1.9.5
*
* @return array
*/
public function get_response_errors(): array {
if ( $this->response instanceof ApiResponse && ! $this->response->isSuccess() ) {
$errors = [];
foreach ( (array) $this->response->getErrors() as $error ) {
$errors[] = $error->jsonSerialize();
}
return $errors;
}
if ( $this->exception instanceof ApiException ) {
return [
'code' => $this->exception->getCode(),
'message' => $this->exception->getMessage(),
];
}
return [];
}
/**
* Retrieve last API call response resource.
*
* @since 1.9.5
*
* @return array
*/
public function get_response_resource(): array {
if ( ! $this->response instanceof ApiResponse ) {
return [];
}
$result = $this->response->getResult();
if ( $result instanceof CreatePaymentResponse ) {
return [ 'payment' => $this->response->getResult()->getPayment() ];
}
if ( $result instanceof CreateSubscriptionResponse || $result instanceof UpdateSubscriptionResponse ) {
return [ 'subscription' => $this->response->getResult()->getSubscription() ];
}
return [];
}
/**
* Retrieve last API call response and display error messages.
*
* @since 1.14.0
*
* @param string $type Type of error message.
*/
private function add_response_errors_message( string $type = 'API' ) {
$errors = $this->get_response_errors();
if ( empty( $errors ) ) {
return;
}
$key = ( $type === 'Exception' ) ? 'message' : 'detail';
foreach ( $errors as $error ) {
$message = $error[ $key ] ?? '';
if ( empty( $error['code'] ) || empty( $message ) ) {
return;
}
$this->errors[] = esc_html( $type ) . ': (' . esc_html( $error['code'] ) . ') ' . esc_html( $message );
}
}
/**
* Perform a subscription transaction.
*
* @since 1.9.5
*
* @param array $args Payment arguments.
*/
private function perform_subscription_transaction( array $args ) {
// Create a customer.
$customer_request = $this->prepare_create_customer_request( $args );
$customer = $this->send_create_customer_request( $customer_request );
if ( ! $customer instanceof Customer ) {
return;
}
// Create a customer card.
$card_request = $this->prepare_create_customer_card_request( $customer->getId(), $args );
$card = $this->send_create_customer_card_request( $card_request );
if ( ! $card instanceof Card ) {
return;
}
// Get a subscription plan.
$plan = $this->get_plan( $args );
if ( ! $plan instanceof CatalogObject ) {
return;
}
// Get a subscription plan variation.
$plan_variation = $this->get_plan_variation( $args, $plan );
if ( ! $plan_variation instanceof CatalogObject ) {
return;
}
// Create a subscription.
$subscription_request = $this->prepare_create_subscription_request( $plan_variation->getId(), $customer->getId(), $card->getId(), $args );
$this->send_create_subscription_request( $subscription_request );
}
/**
* Check if all required subscription arguments are present.
*
* @since 1.9.5
*
* @param array $args Arguments to check.
*/
private function check_required_args_subscription( array $args ) {
if ( empty( $args['subscription']['plan_name'] ) ) {
$this->errors[] = esc_html__( 'Missing subscription plan name.', 'wpforms-lite' );
}
if ( empty( $args['subscription']['plan_variation_name'] ) ) {
$this->errors[] = esc_html__( 'Missing subscription plan variation name.', 'wpforms-lite' );
}
if ( empty( $args['subscription']['phase_cadence'] ) ) {
$this->errors[] = esc_html__( 'Missing subscription cadence.', 'wpforms-lite' );
}
if ( empty( $args['subscription']['customer']['first_name'] ) && empty( $args['subscription']['customer']['last_name'] ) ) {
$this->errors[] = esc_html__( 'Missing customer name.', 'wpforms-lite' );
}
if ( empty( $args['subscription']['customer']['email'] ) ) {
$this->errors[] = esc_html__( 'Missing customer email.', 'wpforms-lite' );
}
}
/**
* Prepare a create customer request object for sending to API.
*
* @since 1.9.5
*
* @param array $args Payment arguments.
*
* @return CreateCustomerRequest
*/
private function prepare_create_customer_request( array $args ): CreateCustomerRequest {
$request = $this->get_create_customer_request_object();
$request->setEmailAddress( $args['subscription']['customer']['email'] );
if ( ! empty( $args['subscription']['customer']['first_name'] ) ) {
$request->setGivenName( $args['subscription']['customer']['first_name'] );
}
if ( ! empty( $args['subscription']['customer']['last_name'] ) ) {
$request->setFamilyName( $args['subscription']['customer']['last_name'] );
}
if ( ! empty( $args['subscription']['customer']['address'] ) ) {
$address = $this->get_address_object( $args['subscription']['customer'] );
$request->setAddress( $address );
}
return $request;
}
/**
* Prepare a create customer card request object for sending to API.
*
* @since 1.9.5
*
* @param string $customer_id Customer ID.
* @param array $args Payment arguments.
*
* @return CreateCardRequest
*/
private function prepare_create_customer_card_request( string $customer_id, array $args ): CreateCardRequest {
$request = new CreateCardRequest( $this->get_idempotency_key(), $this->source_id, new Card() );
$request->getCard()->setCustomerId( $customer_id );
if ( ! empty( $args['subscription']['customer']['address'] ) ) {
$address = $this->get_address_object( $args['subscription']['customer'] );
// For subscriptions: make sure that a postal code is not empty.
// Otherwise, API error "The postal code doesn't match the one used for card nonce creation" is occur here.
if ( ! empty( $address->getPostalCode() ) ) {
$request->getCard()->setBillingAddress( $address );
}
}
if ( ! empty( $args['subscription']['card_name'] ) ) {
$request->getCard()->setCardholderName( $args['subscription']['card_name'] );
}
return $request;
}
/**
* Prepare a create subscription request object for sending to API.
*
* @since 1.9.5
*
* @param string $plan_id Plan ID.
* @param string $customer_id Customer ID.
* @param string $card_id Customer ID.
* @param array $args Payment arguments.
*
* @return CreateSubscriptionRequest
*/
private function prepare_create_subscription_request( string $plan_id, string $customer_id, string $card_id, array $args ): CreateSubscriptionRequest {
$request = $this->get_create_subscription_request_object( $plan_id, $customer_id, $args );
$request->setCardId( $card_id );
$request->setSource( new SubscriptionSource() );
$request->getSource()->setName( Square::APP_NAME );
return $request;
}
/**
* Retrieve a create customer request object.
*
* @since 1.9.5
*
* @return CreateCustomerRequest
*/
private function get_create_customer_request_object(): CreateCustomerRequest {
$request = new CreateCustomerRequest();
$request->setIdempotencyKey( $this->get_idempotency_key() );
return $request;
}
/**
* Retrieve a search catalog request object.
*
* @since 1.9.5
*
* @param string $type Object type.
* @param string $name Object name.
*
* @return SearchCatalogObjectsRequest
*/
private function get_search_catalog_request_object( string $type, string $name ): SearchCatalogObjectsRequest {
$request = new SearchCatalogObjectsRequest();
$request->setObjectTypes( [ $type ] );
$request->setLimit( 1 );
$request->setQuery( new CatalogQuery() );
$request->getQuery()->setExactQuery( new CatalogQueryExact( 'name', $name ) );
return $request;
}
/**
* Retrieve a create plan request object.
*
* @since 1.9.5
*
* @param array $args Payment arguments.
*
* @return UpsertCatalogObjectRequest
*/
private function get_create_plan_request_object( array $args ): UpsertCatalogObjectRequest {
$request = new UpsertCatalogObjectRequest( $this->get_idempotency_key(), new CatalogObject( CatalogObjectType::SUBSCRIPTION_PLAN, '#plan' ) );
$plan_data = new CatalogSubscriptionPlan( $args['subscription']['plan_name'] );
$plan_data->setAllItems( true );
$request->getObject()->setSubscriptionPlanData( $plan_data );
return $request;
}
/**
* Get subscription plan variation.
*
* @since 1.9.5
*
* @param array $args Payment arguments.
* @param CatalogObject $plan Plan object.
*
* @return CatalogObject|null
*/
private function get_plan_variation( array $args, $plan ) {
// Search a subscription plan.
$search_plan_request = $this->get_search_catalog_request_object( CatalogObjectType::SUBSCRIPTION_PLAN_VARIATION, $args['subscription']['plan_name'] );
$plan_variation = $this->send_search_catalog_request( $search_plan_request );
// Create a subscription plan if it's not exists.
if ( $plan_variation !== null ) {
return $plan_variation;
}
$plan_variations_request = $this->get_create_plan_variations_request_object( $args, $plan );
$plan_variation = $this->send_create_plan_variations_request( $plan_variations_request );
if ( ! $plan_variation instanceof CatalogObject ) {
return null;
}
return $plan_variation;
}
/**
* Retrieve a create plan variation request object.
*
* @since 1.9.5
*
* @param array $args Payment arguments.
* @param CatalogObject $plan Plan object.
*
* @return UpsertCatalogObjectRequest
*/
private function get_create_plan_variations_request_object( array $args, $plan ): UpsertCatalogObjectRequest {
$request = new UpsertCatalogObjectRequest( $this->get_idempotency_key(), new CatalogObject( CatalogObjectType::SUBSCRIPTION_PLAN_VARIATION, '#plan_variation' ) );
$phase = new SubscriptionPhase( $args['subscription']['phase_cadence']['value'] );
$phase->setPricing( new SubscriptionPricing() );
$phase->getPricing()->setType( 'STATIC' );
$phase->getPricing()->setPriceMoney( new Money() );
$phase->getPricing()->getPriceMoney()->setAmount( $args['amount'] );
$phase->getPricing()->getPriceMoney()->setCurrency( $args['currency'] );
$request->getObject()->setSubscriptionPlanVariationData( new CatalogSubscriptionPlanVariation( $args['subscription']['plan_variation_name'], [ $phase ] ) );
$request->getObject()->getSubscriptionPlanVariationData()->setSubscriptionPlanId( $plan->getId() );
return $request;
}
/**
* Retrieve a create subscription request object.
*
* @since 1.9.5
*
* @param string $plan_id Plan ID.
* @param string $customer_id Customer ID.
* @param array $args Payment arguments.
*
* @return CreateSubscriptionRequest
*/
private function get_create_subscription_request_object( string $plan_id, string $customer_id, array $args ): CreateSubscriptionRequest {
$request = new CreateSubscriptionRequest( $args['location_id'], $customer_id );
$request->setIdempotencyKey( $this->get_idempotency_key() );
$request->setPlanVariationId( $plan_id );
return $request;
}
/**
* Send a create customer request to API.
*
* @since 1.9.5
*
* @param CreateCustomerRequest $request Create customer request object.
*
* @return Customer|null
*/
private function send_create_customer_request( $request ) {
try {
$this->response = $this->client->getCustomersApi()->createCustomer( $request );
if ( ! $this->response->isSuccess() ) {
$this->errors[] = esc_html__( 'Square fail: customer was not created.', 'wpforms-lite' );
return null;
}
return $this->response->getResult()->getCustomer();
} catch ( ApiException $e ) {
$this->exception = $e;
$this->errors[] = esc_html__( 'Square fail: customer was not created.', 'wpforms-lite' );
return null;
}
}
/**
* Send a retrieve card request to API.
*
* @since 1.9.5
*
* @param string $id The ID of the customer to retrieve.
*
* @return Card|null
*/
private function send_retrieve_card_request( string $id ) {
try {
$response = $this->client->getCardsApi()->retrieveCard( $id );
if ( ! $response->isSuccess() ) {
return null;
}
return $response->getResult()->getCard();
} catch ( ApiException $e ) {
return null;
}
}
/**
* Send a create customer card request to API.
*
* @since 1.9.5
*
* @param CreateCardRequest $request Create card request object.
*
* @return Card|null
*/
private function send_create_customer_card_request( $request ) {
try {
$this->response = $this->client->getCardsApi()->createCard( $request );
if ( ! $this->response->isSuccess() ) {
$this->errors[] = esc_html__( 'Square fail: customer card was not created.', 'wpforms-lite' );
return null;
}
return $this->response->getResult()->getCard();
} catch ( ApiException $e ) {
$this->exception = $e;
$this->errors[] = esc_html__( 'Square fail: customer card was not created.', 'wpforms-lite' );
return null;
}
}
/**
* Send a search catalog request to API.
*
* @since 1.9.5
*
* @param SearchCatalogObjectsRequest $request Search subscription plan request object.
*
* @return CatalogObject|null
*/
private function send_search_catalog_request( $request ) {
try {
$this->response = $this->client->getCatalogApi()->searchCatalogObjects( $request );
if ( ! $this->response->isSuccess() ) {
return null;
}
$objects = $this->response->getResult()->getObjects();
if ( ! is_array( $objects ) || empty( $objects[0] ) ) {
return null;
}
return $objects[0];
} catch ( ApiException $e ) {
$this->exception = $e;
return null;
}
}
/**
* Get subscription plan.
*
* @since 1.9.5
*
* @param array $args Payment arguments.
*
* @return CatalogObject|null
*/
private function get_plan( array $args ) {
// Search a subscription plan.
$search_plan_request = $this->get_search_catalog_request_object( CatalogObjectType::SUBSCRIPTION_PLAN, $args['subscription']['plan_variation_name'] );
$plan = $this->send_search_catalog_request( $search_plan_request );
// Create a subscription plan if it's not exists.
if ( $plan === null ) {
$create_plan_request = $this->get_create_plan_request_object( $args );
$plan = $this->send_create_plan_request( $create_plan_request );
}
if ( ! $plan instanceof CatalogObject ) {
return null;
}
return $plan;
}
/**
* Send a create subscription plan request to API.
*
* @since 1.9.5
*
* @param UpsertCatalogObjectRequest $request Create subscription plan request object.
*
* @return CatalogObject|null
*/
private function send_create_plan_request( $request ) {
try {
$this->response = $this->client->getCatalogApi()->upsertCatalogObject( $request );
if ( ! $this->response->isSuccess() ) {
$this->errors[] = esc_html__( 'Square fail: subscription plan was not created.', 'wpforms-lite' );
return null;
}
return $this->response->getResult()->getCatalogObject();
} catch ( ApiException $e ) {
$this->exception = $e;
$this->errors[] = esc_html__( 'Square fail: subscription plan was not created.', 'wpforms-lite' );
return null;
}
}
/**
* Send a create subscription plan variations request to API.
*
* @since 1.9.5
*
* @param UpsertCatalogObjectRequest $request Create subscription plan request object.
*
* @return CatalogObject|null
*/
private function send_create_plan_variations_request( $request ) {
try {
$this->response = $this->client->getCatalogApi()->upsertCatalogObject( $request );
if ( ! $this->response->isSuccess() ) {
$this->errors[] = esc_html__( 'Square fail: subscription plan variation was not created.', 'wpforms-lite' );
return null;
}
return $this->response->getResult()->getCatalogObject();
} catch ( ApiException $e ) {
$this->exception = $e;
$this->errors[] = esc_html__( 'Square fail: subscription plan variation was not created.', 'wpforms-lite' );
return null;
}
}
/**
* Send a create subscription request to API.
*
* @since 1.9.5
*
* @param CreateSubscriptionRequest $request Create subscription request object.
*
* @return Subscription|null
*/
private function send_create_subscription_request( $request ) {
try {
$this->response = $this->client->getSubscriptionsApi()->createSubscription( $request );
if ( ! $this->response->isSuccess() ) {
$this->errors[] = esc_html__( 'Square fail: something went wrong during subscription process.', 'wpforms-lite' );
return null;
}
return $this->response->getResult()->getSubscription();
} catch ( ApiException $e ) {
$this->exception = $e;
$this->errors[] = esc_html__( 'Square fail: something went wrong during subscription process.', 'wpforms-lite' );
return null;
}
}
/**
* Retrieve a update subscription request object.
*
* @since 1.9.5
*
* @param Subscription $subscription Subscription object.
* @param array $args Subscription arguments.
*
* @return UpdateSubscriptionRequest|null
*/
private function get_update_subscription_request_object( $subscription, array $args ) {
if ( ! $subscription instanceof Subscription ) {
return null;
}
$subscription->setSource( new SubscriptionSource() );
$subscription->getSource()->setName( Square::APP_NAME . ' Payment #' . $args['payment_id'] );
$request = new UpdateSubscriptionRequest();
$request->setSubscription( $subscription );
return $request;
}
/**
* Send a create subscription request to API.
*
* @since 1.9.5
*
* @param string $subscription_id Subscription id.
* @param UpdateSubscriptionRequest $request Update subscription request object.
*
* @return Subscription|null
*/
private function send_update_subscription_request( string $subscription_id, $request ) {
try {
$this->response = $this->client->getSubscriptionsApi()->updateSubscription( $subscription_id, $request );
if ( ! $this->response->isSuccess() ) {
$this->errors[] = esc_html__( 'Square fail: something went wrong during subscription update.', 'wpforms-lite' );
return null;
}
return $this->response->getResult()->getSubscription();
} catch ( ApiException $e ) {
$this->exception = $e;
$this->errors[] = esc_html__( 'Square fail: something went wrong during subscription update.', 'wpforms-lite' );
return null;
}
}
/**
* Retrieve card details from specific transaction in object format.
*
* @since 1.9.5
*
* @param string $transaction_id The ID of the order to retrieve card details from.
*
* @return stdClass|void
*/
public function get_card_details_from_transaction_id( string $transaction_id ) {
$response = $this->client->getPaymentsApi()->getPayment( $transaction_id );
if ( ! $response->isSuccess() ) {
return;
}
$payment = $response->getResult()->getPayment();
$card_details = $payment->getCardDetails();
if ( ! $card_details ) {
return;
}
$card = $card_details->getCard();
if ( ! $card ) {
return;
}
// Create a temporary object to mimic Square's payment_method_details structure.
$details = new stdClass();
$details->source_type = 'card';
$details->card_details = new stdClass();
$details->card_details->card = new stdClass();
$details->card_details->card->last_4 = $card->getLast4();
$details->card_details->card->card_brand = $card->getCardBrand();
$details->card_details->card->exp_month = $card->getExpMonth();
$details->card_details->card->exp_year = $card->getExpYear();
return $details;
}
/**
* Retrieve the latest invoice transaction_id.
*
* @since 1.9.5
*
* @param Invoice $invoice Invoice object.
*
* @return string
*/
public function get_latest_invoice_transaction_id( $invoice ): string {
$order_id = $invoice->getOrderId();
if ( ! $order_id ) {
return '';
}
$order_response = $this->client->getOrdersApi()->retrieveOrder( $order_id );
if ( ! $order_response->isSuccess() ) {
return '';
}
$order = $order_response->getResult()->getOrder();
$tenders = $order->getTenders();
if ( empty( $tenders ) ) {
return '';
}
// Assuming the last tender represents the final transaction.
return end( $tenders )->getPaymentId();
}
/**
* Retrieve the invoice by order ID.
*
* @since 1.9.5
*
* @param string $order_id Order ID.
*
* @return Invoice|null
*/
public function get_invoice_by_order_id( string $order_id ) {
$invoices_api = $this->client->getInvoicesApi();
try {
$response = $invoices_api->listInvoices( Helpers::get_location_id() );
if ( ! $response->isSuccess() ) {
return null;
}
$invoices = $response->getResult()->getInvoices();
if ( empty( $invoices ) ) {
return null;
}
foreach ( $invoices as $invoice ) {
if ( $invoice->getOrderId() === $order_id ) {
return $invoice;
}
}
} catch ( ApiException $e ) {
return null;
}
return null;
}
}