# Checkout and Payment Flow

The checkout and payment flow handles the complete payment lifecycle, from order creation to payment completion.

## Overview

The Clip Redirect plugin uses a redirect-based payment flow where customers are taken to Clip's hosted checkout page to complete their payment. The flow involves creating a payment checkout, redirecting the customer, and handling the return/webhook notifications.

## Complete Payment Flow Diagram

```mermaid
sequenceDiagram
    participant Customer
    participant WooCommerce as WooCommerce Store
    participant Gateway as WC_ClipRedirect Gateway
    participant SDK as ClipRedirectSdk
    participant ClipAPI as Clip API
    participant ClipCheckout as Clip Hosted Checkout
    participant Webhook as Webhook Listener

    Note over Customer,Webhook: 1. Order Creation & Checkout Selection
    Customer->>WooCommerce: Browse products & add to cart
    Customer->>WooCommerce: Proceed to checkout
    WooCommerce->>Customer: Display payment methods
    Customer->>WooCommerce: Select "Clip" payment method
    WooCommerce->>Gateway: Display payment fields
    Gateway->>Customer: Show Clip banner & description
    
    Note over Customer,Webhook: 2. Order Placement
    Customer->>WooCommerce: Place order
    WooCommerce->>Gateway: process_payment(order_id)
    Gateway->>Gateway: Validate order & currency
    
    alt Currency Supported (MXN/USD)
        Gateway->>WooCommerce: Set order status to pending
        Gateway->>WooCommerce: Redirect to receipt page
        WooCommerce->>Customer: Redirect to /order-received/ page
    else Currency Not Supported
        Gateway-->>Customer: Error: Currency not supported
    end

    Note over Customer,Webhook: 3. Post-Checkout Page
    Customer->>WooCommerce: Land on order-received page
    WooCommerce->>Gateway: PostCheckout::render(order_id)
    Gateway->>Customer: Display "Pay with Clip" button
    Customer->>Customer: Clicks "Pay with Clip" button
    
    Note over Customer,Webhook: 4. Payment Link Creation (AJAX)
    Customer->>WooCommerce: AJAX request (clip_request_deposit_action)
    WooCommerce->>Gateway: RequestDepositAction::ajax_callback_wp()
    Gateway->>Gateway: Validate nonce & order_id
    Gateway->>Gateway: RequestDepositAction::run(order_id)
    Gateway->>SDK: request_deposit(order_id)
    
    SDK->>SDK: get_deposit_data(order_id)
    Note right of SDK: Builds payload with:<br/>- Order amount & currency<br/>- Customer info<br/>- Billing/Shipping address<br/>- Redirection URLs<br/>- Custom payment options<br/>- Webhook URL<br/>- Expiration time
    
    SDK->>ClipAPI: POST /v2/checkout
    Note right of ClipAPI: Creates hosted checkout
    ClipAPI-->>SDK: Return checkout data
    Note right of SDK: Response includes:<br/>- payment_request_id<br/>- payment_request_url<br/>- status: CHECKOUT_CREATED
    
    SDK-->>Gateway: Return checkout data
    Gateway->>WooCommerce: Save payment_request_id to order meta
    Gateway->>WooCommerce: Add order note with checkout ID
    Gateway-->>Customer: Return payment_request_url (JSON)
    
    Note over Customer,Webhook: 5. Redirect to Clip Checkout
    Customer->>Customer: JavaScript redirects to payment_request_url
    Customer->>ClipCheckout: Opens Clip hosted checkout page
    ClipCheckout->>Customer: Display payment form
    Note right of ClipCheckout: Shows:<br/>- Payment methods (cards/cash)<br/>- Card brands<br/>- Installment options<br/>- Amount & merchant info

    Note over Customer,Webhook: 6. Payment Processing
    alt Payment Successful
        Customer->>ClipCheckout: Completes payment
        ClipCheckout->>ClipAPI: Process payment
        ClipAPI->>ClipCheckout: Payment approved
        ClipCheckout->>Customer: Redirect to success_url
        Customer->>WooCommerce: Lands on order-received page
        
        par Webhook Notification
            ClipAPI->>Webhook: POST /wc-api/wc-clip
            Note right of Webhook: Webhook payload:<br/>- payment_request_id<br/>- event_type or resource_status<br/>- payment details
            Webhook->>Webhook: Validate webhook data
            Webhook->>Webhook: Find order by payment_request_id
            Webhook->>Gateway: Helper::handle_payment(order_id)
            Gateway->>SDK: get_payment_data(payment_request_id)
            SDK->>ClipAPI: GET /v2/checkout/{payment_request_id}
            ClipAPI-->>SDK: Return payment status
            SDK-->>Gateway: Return payment data
            Gateway->>Gateway: Check status: CHECKOUT_COMPLETED
            Gateway->>WooCommerce: Mark order as payment_complete()
            Gateway->>WooCommerce: Add order note with receipt
            Gateway->>WooCommerce: Save receipt_no & status to meta
            Webhook-->>ClipAPI: Return 200 OK
        end
        
    else Payment Cancelled
        Customer->>ClipCheckout: Cancels payment
        ClipCheckout->>Customer: Redirect to error_url
        Customer->>WooCommerce: Lands on order-received page
        
        ClipAPI->>Webhook: POST /wc-api/wc-clip (CANCELLED)
        Webhook->>Gateway: Helper::handle_payment(order_id)
        Gateway->>Gateway: Check status: CHECKOUT_CANCELLED
        Gateway->>WooCommerce: Add order note: Cancelled
        Gateway->>WooCommerce: Update meta with status
        Webhook-->>ClipAPI: Return 200 OK
        
    else Payment Expired
        Note right of ClipCheckout: Checkout link expires<br/>after configured hours (default 72h)
        ClipAPI->>Webhook: POST /wc-api/wc-clip (EXPIRED)
        Webhook->>Gateway: Helper::handle_payment(order_id)
        Gateway->>Gateway: Check status: CHECKOUT_EXPIRED
        Gateway->>WooCommerce: Add order note: Expired
        Gateway->>WooCommerce: Update meta with status
        Webhook-->>ClipAPI: Return 200 OK
    end

    Note over Customer,Webhook: 7. Order Completion
    alt Payment Successful
        WooCommerce->>Customer: Display order confirmation
        WooCommerce->>WooCommerce: Trigger order complete actions
        WooCommerce->>Customer: Send order confirmation email
    else Payment Failed/Cancelled
        WooCommerce->>Customer: Display order status (pending payment)
        Customer->>Customer: Can retry payment from My Account
    end
```

## Detailed Component Breakdown

### 1. Payment Method Registration

**File**: `hooks.php` and `clip-for-woocommerce.php`

The gateway is registered with WooCommerce:

```php
add_filter( 'woocommerce_payment_gateways', 
    array( 'ClipRedirect', 'add_payment_method' ) 
);

public static function add_payment_method( $gateways ) {
    $gateways[] = '\Conexa\ClipRedirect\Gateway\WC_ClipRedirect';
    return $gateways;
}
```

### 2. Gateway Configuration

**File**: `src/gateway/class-wc-clip-redirect.php`

The `WC_ClipRedirect` class extends `WC_Payment_Gateway`:

```php
public function __construct() {
    $this->id = \ClipRedirect::GATEWAY_ID; // 'wc_clipredirect'
    $this->has_fields = false; // No credit card fields
    $this->method_title = __( 'Clip', 'clip-for-woocommerce' );
    $this->method_description = __( 'Accept payments using Clip.', 'clip-for-woocommerce' );
    
    // Initialize SDK
    $this->api_key = $this->get_option( 'wc_clipredirect_api_key' );
    $this->api_secret = $this->get_option( 'wc_clipredirect_api_secret' );
    $this->sdk = new ClipRedirectSdk( $this->api_key, $this->api_secret );
    
    // Register hooks
    add_action(
        'woocommerce_update_options_payment_gateways_' . $this->id,
        array( $this, 'process_admin_options' )
    );
    add_action(
        'woocommerce_thankyou_' . $this->id,
        array( $this, 'thankyou_page' )
    );
}
```

### 3. Payment Fields Display

**File**: `src/gateway/class-wc-clip-redirect.php`

Displays payment information on checkout page:

```php
public function payment_fields() {
    // Display description
    echo esc_html( $this->description );
    
    // Display banner if enabled
    if ( 'yes' === $this->banner_enabled ) {
        $banner_url = $this->get_clip_banner_url();
        echo '<img src="' . esc_url( $banner_url ) . '" alt="">';
    }
}
```

**Banner selection logic**: The plugin dynamically selects banners based on enabled payment methods (cards, cash) and device type (mobile/desktop).

### 4. Order Processing

**File**: `src/gateway/class-wc-clip-redirect.php`

When customer places order:

```php
public function process_payment( $order_id ) {
    $order = wc_get_order( $order_id );
    
    // Mark order as pending
    $order->update_status( 'pending', __( 'Awaiting Clip payment', 'clip-for-woocommerce' ) );
    
    // Return success and redirect to receipt page
    return array(
        'result'   => 'success',
        'redirect' => $order->get_checkout_order_received_url(),
    );
}
```

### 5. Post-Checkout Page

**File**: `src/gateway/class-post-checkout.php`

Renders the "Pay with Clip" button on order-received page:

```php
public static function render( $order_id ) {
    $order = wc_get_order( $order_id );
    
    if ( \ClipRedirect::GATEWAY_ID !== $order->get_payment_method() ) {
        return false;
    }
    
    // Display button and JavaScript configuration
    ?>
    <a id="clip-cta" class="button">
        Pay with <img src="clip-logo.svg">
    </a>
    <script>
        var wc_clipredirect_settings = {
            action: "clip_request_deposit_action",
            ajax_url: "<?php echo admin_url( 'admin-ajax.php' ); ?>",
            order_id: "<?php echo $order_id; ?>",
            ajax_nonce: "<?php echo wp_create_nonce( \ClipRedirect::GATEWAY_ID ); ?>"
        };
    </script>
    <?php
    
    wp_enqueue_script( 'clipredirect-gateway' );
}
```

### 6. Payment Link Creation (AJAX)

**File**: `src/gateway/class-request-deposit-action.php`

Handles AJAX request to create payment checkout:

```php
public static function ajax_callback_wp() {
    // Validate request
    $ret_validate = static::validate_ajax_request();
    if ( true !== $ret_validate ) {
        wp_send_json_error( $ret_validate );
    }
    
    // Get order ID
    $order_id = filter_var( $_POST['order_id'], FILTER_SANITIZE_NUMBER_INT );
    
    // Create payment checkout
    $ret = static::run( $order_id );
    
    if ( $ret ) {
        wp_send_json_success( $ret ); // Returns payment_request_url
    } else {
        wp_send_json_error( __( 'Order invalid', 'clip-for-woocommerce' ) );
    }
}

public static function run( $order_id ) {
    $order = wc_get_order( $order_id );
    
    $options = Helper::get_options( \ClipRedirect::GATEWAY_ID );
    $sdk = new ClipRedirectSdk( $options['api_key'], $options['api_secret'] );
    
    // Request deposit/checkout creation
    $response = $sdk->request_deposit( $order_id );
    
    if ( isset( $response['status'] ) && 'CHECKOUT_CREATED' === $response['status'] ) {
        // Save payment ID to order meta
        $order->update_meta_data(
            \ClipRedirect::META_ORDER_PAYMENT_ID,
            $response['payment_request_id']
        );
        $order->save();
        
        // Add order notes
        $order->add_order_note(
            sprintf( __( 'Clip payment_request_id created. ID %s', 'clip-for-woocommerce' ),
                $response['payment_request_id'] )
        );
        
        return $response['payment_request_url'];
    }
    
    return false;
}
```

### 7. Building Checkout Payload

**File**: `src/sdk/class-clip-redirect-sdk.php`

The SDK builds the checkout payload:

```php
public function request_deposit( $order_id ) {
    $data_to_send = $this->get_deposit_data( $order_id );
    
    $res = $this->api->post(
        '/v2/checkout',
        $data_to_send,
        array(
            'content-type' => 'application/json',
            'accept' => 'application/json',
            'Authorization' => $this->api_token,
        )
    );
    
    return $this->handle_response( $res, __FUNCTION__ );
}

public function get_deposit_data( $order_id ) {
    $order = wc_get_order( $order_id );
    
    // Get configuration
    $expiration = Helper::get_option( 'wc_clipredirect_expiration_hours', 72 );
    $clip_override = $this->get_option( 'wc_clipredirect_payment_override', 'no' );
    
    // Calculate expiration time
    $current_datetime = new \DateTime( 'now', new \DateTimeZone( 'UTC' ) );
    $new_datetime = $current_datetime->modify( "+$expiration hours" );
    $expires_at = $new_datetime->format( 'Y-m-d\TH:i:s\Z' );
    
    // Build payload
    $data = array(
        'amount' => floatval( $order->get_total() ),
        'currency' => $order->get_currency(),
        'purchase_description' => __( 'Compra en tienda', 'clip-for-woocommerce' ),
        'redirection_url' => array(
            'success' => $order->get_checkout_order_received_url(),
            'error' => $order->get_checkout_order_received_url(),
            'default' => $order->get_checkout_order_received_url(),
        ),
        'expires_at' => $expires_at,
        'metadata' => array(
            'me_reference_id' => 'WC' . $order_id . '-' . wp_generate_uuid4(),
            'customer_info' => array(
                'name' => $order->get_billing_first_name(),
                'email' => $order->get_billing_email(),
                'phone' => $order->get_billing_phone(),
            ),
            'source' => 'woocommerce',
            'source_version' => \ClipRedirect::VERSION,
            'billing_address' => [ /* ... */ ],
            'shipping_address' => [ /* ... */ ],
        ),
        'webhook_url' => get_site_url() . '/wc-api/wc-clip',
    );
    
    // Add custom payment options if enabled
    if ( 'yes' === $clip_override ) {
        $custom_payment_options = $this->payment_options( 
            $order->get_total(), 
            $order->get_currency() 
        );
        if ( $custom_payment_options ) {
            $data['custom_payment_options'] = $custom_payment_options;
        }
    }
    
    return $data;
}
```

### 8. Custom Payment Options

**File**: `src/sdk/class-clip-redirect-sdk.php`

Configures payment methods, brands, and installments:

```php
public static function payment_options( $order_total, $order_currency ) {
    $option = get_option( 'woocommerce_wc_clipredirect_settings', array() );
    
    $custom_payment_options = array();
    
    // Card brands (visa, mastercard, amex, etc.)
    $clip_brands = $option['wc_clipredirect_payment_card_brands'];
    $enabled_brands = array();
    foreach ( $clip_brands as $brand => $status ) {
        if ( 'yes' === $status ) {
            $enabled_brands[] = $brand;
        }
    }
    if ( ! empty( $enabled_brands ) ) {
        $custom_payment_options['payment_method_brands'] = $enabled_brands;
    }
    
    // Payment types (credit, debit, cash)
    $clip_payments = $option['wc_clipredirect_payment_type'];
    $enabled_types = array();
    foreach ( $clip_payments as $type => $status ) {
        if ( 'yes' === $status && 'cards' !== $type ) {
            $enabled_types[] = $type;
        }
    }
    if ( ! empty( $enabled_types ) ) {
        $custom_payment_options['payment_method_types'] = $enabled_types;
    }
    
    // Installments (MSI - Meses Sin Intereses)
    $clip_installments_enabled = $option['wc_clipredirect_payment_installments_enabled'];
    $clip_installments = $option['wc_clipredirect_payment_installments'];
    
    $installments_msi = array();
    if ( 'yes' === $clip_installments_enabled ) {
        foreach ( $clip_installments as $installment => $details ) {
            $is_enabled = $details['enabled'] ?? 'no';
            $min_amount = (float) $details['value'] ?? 0;
            
            if ( 'yes' === $is_enabled && $order_total >= $min_amount ) {
                $installments_msi[] = (int) $installment;
            }
        }
    }
    if ( ! empty( $installments_msi ) ) {
        $custom_payment_options['installments_msi'] = $installments_msi;
    }
    
    // International payments for USD
    if ( 'USD' === $order_currency ) {
        $custom_payment_options['international_enabled'] = true;
    }
    
    return ! empty( $custom_payment_options ) ? $custom_payment_options : null;
}
```

### 9. Frontend JavaScript

**File**: `assets/js/gateway.js`

Handles button click and AJAX request:

```javascript
jQuery('#clip-cta').on('click', function(e) {
    e.preventDefault();
    
    // Show loading spinner
    showSpinner();
    
    jQuery.ajax({
        url: wc_clipredirect_settings.ajax_url,
        type: 'POST',
        data: {
            action: wc_clipredirect_settings.action,
            order_id: wc_clipredirect_settings.order_id,
            nonce: wc_clipredirect_settings.ajax_nonce
        },
        success: function(response) {
            if (response.success && response.data) {
                // Redirect to Clip checkout
                window.location.href = response.data;
            } else {
                showError(response.data);
            }
        },
        error: function() {
            showError('Error creating payment link');
        }
    });
});

// Auto-trigger if coming from checkout redirect
if (wc_clipredirect_settings.clip_cta_flag === true) {
    jQuery('#clip-cta').trigger('click');
}
```

## Order Meta Data

The plugin stores the following meta data in each order:

| Meta Key | Description | Example Value |
|----------|-------------|---------------|
| `_CLIPREDIRECT_PAYMENT_ID` | Clip payment request ID | `"pr_abc123xyz"` |
| `_CLIPREDIRECT_PAYMENT_STATUS` | Current payment status | `"CHECKOUT_COMPLETED"` |
| `_CLIPREDIRECT_RECEIPT_NO` | Clip receipt number | `"5mUV5Dt"` |

## Payment Statuses

| Status | Description | Order Action |
|--------|-------------|--------------|
| `CHECKOUT_CREATED` | Checkout link created | Order stays pending |
| `CHECKOUT_PENDING` | Payment initiated but not completed | Order stays pending |
| `CHECKOUT_COMPLETED` | Payment successful | Order marked as complete |
| `CHECKOUT_CANCELLED` | Payment cancelled by user | Order stays pending |
| `CHECKOUT_EXPIRED` | Checkout link expired | Order stays pending |

## Checkout Expiration

- **Default expiration**: 72 hours
- **Configurable**: Admin can set 0-72 hours in settings
- **Calculation**: From checkout creation time in UTC

## Currency Support

The gateway validates currency before processing:

```php
public static function available_payment_method( $available_gateways ) {
    if ( ! Helper::is_clip_currency_supported() && 
         isset( $available_gateways[ \ClipRedirect::GATEWAY_ID ] ) ) {
        unset( $available_gateways[ \ClipRedirect::GATEWAY_ID ] );
    }
    return $available_gateways;
}
```

**Supported currencies**: MXN (Mexican Peso), USD (US Dollar)

## Error Handling

### Common Errors

| Error | Cause | Resolution |
|-------|-------|------------|
| `missing nonce` | AJAX security token missing | Check JavaScript configuration |
| `missing order_id` | Order ID not provided | Check AJAX payload |
| `not order` | Order doesn't exist | Verify order ID is valid |
| `not clip` | Order uses different payment method | User shouldn't see Clip button |
| `004` | Maximum payment limit reached | Contact Clip support |

### Maximum Payment Limit

If merchant reaches their transaction limit:

```php
if ( isset( $response['code_message'] ) && '004' === $response['code_message'] ) {
    $order->add_order_note(
        __( 'Clip: Requested maximum payment limit reached.', 'clip-for-woocommerce' )
    );
}
```

## Hooks and Actions

| Hook | Type | Description |
|------|------|-------------|
| `woocommerce_receipt_{gateway_id}` | Action | Renders post-checkout page |
| `wp_ajax_clip_request_deposit_action` | Action | Creates payment checkout (logged in) |
| `wp_ajax_nopriv_clip_request_deposit_action` | Action | Creates payment checkout (guest) |
| `woocommerce_thankyou_{gateway_id}` | Action | Triggers payment status check |

## Related Files

- `src/gateway/class-wc-clip-redirect.php` - Main gateway class
- `src/gateway/class-post-checkout.php` - Post-checkout page renderer
- `src/gateway/class-request-deposit-action.php` - AJAX handler for checkout creation
- `src/sdk/class-clip-redirect-sdk.php` - API communication and payload building
- `src/helper/class-handle-payment-trait.php` - Payment status handling
- `assets/js/gateway.js` - Frontend JavaScript for button handling
- `hooks.php` - WordPress action and filter registrations
