Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 82 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,70 @@ All notable changes to the Two Payment module for PrestaShop will be documented
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [2.3.0] - 2025-11-21

### Added
- **End-of-Month (EOM) Payment Terms**: New payment term type for B2B invoicing
- Supports EOM+30, EOM+45, and EOM+60 day terms
- Payment calculated from end of current month at fulfillment, plus selected days
- Example: Order fulfilled Jan 15 with EOM+30 = Payment due Feb 28 (end of Jan + 30 days)
- **Payment Term Type Configuration**: Radio button selection in admin (Standard vs EOM)
- Dynamic UI: EOM mode shows only 30/45/60 day options
- Standard mode shows all available terms (7/15/20/30/45/60/90 days)
- Clear explanations with real-world examples for each type
- **API Integration**: `duration_days_calculated_from: "END_OF_MONTH"` field added to order payload for EOM terms
- **Database Schema**: Added `two_payment_term_type` column to store term type per order
- **Enhanced Buyer Display**:
- Standard terms: "Pay in 30 days" (multilingual)
- EOM terms: "Pay in 30 days from end of month" (clear, localized)
- Dynamic description text changes based on term type
- **Admin Order View**: Shows "End of Month + 30 days" with EOM badge for clarity
- **Upgrade Script**: `upgrade-2.3.0.php` with backward-compatible defaults
- **Debug Mode**: Admin toggle for detailed diagnostic logging (Other Settings → Enable Debug Mode)
- Logs tax calculations, rate fields, and gross/net amounts per product
- Only enable when requested by Two support for troubleshooting
- **Phone Number Fallback**: Automatic fallback from `phone` to `phone_mobile` field
- Handles cases where customers only provide mobile number
- Graceful handling when no phone provided (Two API validates)

### Changed
- **Checkout Display**: Smart label/unit hiding for EOM terms (no verbose "Pay in EOM+30 days")
- **Payment Terms Selector**: Term format changes based on type (tooltips explain EOM)
- **API Payload Builder**: New `buildTermsPayload()` method conditionally adds EOM field
- **Available Terms Logic**: `getAvailablePaymentTerms()` filters based on term type
- **Tax Rate Calculation**: Now validates tax rate from actual amounts (gross - net / net)
- Handles edge case where PrestaShop `rate` field is 0 but tax is applied
- Logs anomalies when rate field doesn't match calculated rate
- Uses calculated rate as source of truth (what customer actually pays)
- **Company Messaging**: Clearer guidance when company data is missing
- "Go back to your billing address and enter your company name in the Company field"
- "Go back to your billing address and search for your company name. Select your company from the results"
- Specific status codes: `no_company`, `incomplete_company` for better UX

### Fixed
- Invoice upload feature temporarily disabled (will be re-enabled after further testing)
- **Tax Rate 0% Issue**: Fixed edge case where tax rate was sent as 0 despite tax being applied
- Now calculates rate from PrestaShop's actual gross/net amounts
- **Phone Validation Errors**: User-friendly messages for invalid phone numbers
- "The phone number in your billing address appears to be invalid. Please go back and ensure you have entered a valid phone number for your country."
- **API Validation Errors**: Comprehensive parsing of Two API validation errors
- Phone, email, address, and company validation errors now show user-friendly messages
- Generic fallback for unknown validation errors

### Technical
- **PHP 7.1+ Compatible**: No spread operators, arrow functions, or typed properties
- **Backward Compatible**: Existing merchants default to STANDARD type
- **Historical Orders**: Upgrade script marks existing orders as STANDARD
- **ES5 JavaScript**: Uses `function()` syntax instead of arrow functions in loops
- **Security**: Whitelist validation for term type (only STANDARD or EOM accepted)

### User Experience
- Clear admin explanations with fulfillment date examples
- Language-friendly checkout display (works in ES, EN, DE, FR, etc.)
- Tooltips on EOM term options
- EOM badge in admin order view with explanation
- Concise term display without verbose text

## [2.2.0] - 2025-11-14

### Added
Expand Down Expand Up @@ -91,6 +155,24 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## Upgrade Notes

### Upgrading to 2.3.0

1. **Backup**: Always backup your database before upgrading
2. **Automatic Migration**: Upgrade script automatically:
- Adds `two_payment_term_type` column (VARCHAR(20), default 'STANDARD')
- Sets `PS_TWO_PAYMENT_TERM_TYPE` configuration to 'STANDARD'
- Updates existing orders to STANDARD type (no visible change)
3. **Backward Compatible**: Existing merchants see no changes
- Payment terms continue to work exactly as before
- All existing orders display correctly as standard terms
4. **New Feature**: EOM payment terms available as opt-in
- Configure in module admin: Payment Term Type radio button
- Only affects new orders after enabling EOM
5. **API Compatibility**: EOM requires Two backend support
- Test on staging environment first
- Verify `duration_days_calculated_from` field is accepted
6. **No Breaking Changes**: Standard terms unchanged, EOM is additive

### Upgrading to 2.2.0

1. **Backup**: Always backup your database before upgrading
Expand Down
109 changes: 103 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ Two is a B2B payment method that lets your business customers pay by invoice wit
- **Organization Number Capture**: Hidden field (`companyid`) automatically populated from company selection
- **Order Intent Check**: Frontend validation before payment confirmation
- **Server-Side Verification**: Defense-in-depth security with server-side Order Intent verification
- **Payment Terms UI**: Configurable payment terms (7/15/20/30/45/60/90 days) with user selection
- **Payment Terms UI**: Configurable payment terms with user selection
- **Standard Terms**: 7/15/20/30/45/60/90 days from fulfillment date
- **End-of-Month (EOM) Terms**: 30/45/60 days from end of current month at fulfillment
- **Admin Integration**: Two order ID, state, status, and invoice URL displayed in order pages
- **Invoice Upload**: Automatic upload of PrestaShop-generated invoices to Two (optional feature)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The 'Invoice Upload' feature is listed as a core functionality, but the code explicitly disables it temporarily (as noted in the changelog and code comments). This creates a discrepancy between the documentation and the current module behavior. It would be clearer to update the README to reflect its temporary disabled status or remove it from the 'Features' list until it's re-enabled.


Expand All @@ -26,8 +28,11 @@ Two is a B2B payment method that lets your business customers pay by invoice wit
- Cross-version compatibility (PrestaShop 1.7.6 - 9.x)
- Theme-agnostic implementation
- jQuery compatibility handling for older PrestaShop versions
- Comprehensive error logging
- Comprehensive error logging with optional debug mode
- Order payload validation ensuring exact PrestaShop invoice matching
- Robust tax rate calculation with fallback validation
- User-friendly error messages for API validation failures
- Phone number fallback (phone → phone_mobile)

## Requirements

Expand All @@ -54,8 +59,11 @@ Two is a B2B payment method that lets your business customers pay by invoice wit
3. **API Key**: Enter your Two API key for the selected environment
- The module validates the API key on save
- Invalid keys will show an error message
4. **Payment Terms**: Configure available payment terms
- Enable/disable individual terms: 7, 15, 20, 30, 45, 60, 90 days
4. **Payment Terms**: Configure payment term type and available terms
- **Term Type**: Choose Standard or End-of-Month (EOM) terms
- **Standard**: Payment due X days from fulfillment date (all durations available)
- **EOM**: Payment due at end of current month + X days (30/45/60 only)
- Enable/disable individual terms based on selected type
- Set default payment term (defaults to 30 days if available)
5. **Optional Features**:
- Enable/disable company name field requirement
Expand All @@ -73,7 +81,8 @@ Two is a B2B payment method that lets your business customers pay by invoice wit
|--------|-------------|---------|
| Environment | Sandbox or Production | Sandbox |
| API Key | Two merchant API key | Required |
| Payment Terms | Available terms (7-90 days) | 30 days enabled |
| Payment Term Type | Standard or End-of-Month (EOM) | Standard |
| Payment Terms | Available terms based on type | 30 days enabled |
| Default Payment Term | Default term when multiple available | 30 days |
| Company Name | Require company name field | Enabled |
| Organization Number | Require organization number | Enabled |
Expand All @@ -84,6 +93,55 @@ Two is a B2B payment method that lets your business customers pay by invoice wit
| Auto Fulfill Orders | Automatically fulfill orders with Two when status changes | Enabled |
| Invoice Upload | Auto-upload invoices to Two | Disabled |
| SSL Verification | Verify SSL certificates | Enabled |
| Debug Mode | Enable detailed diagnostic logging | Disabled |

## Payment Terms: Standard vs End-of-Month (EOM)

The module supports two types of payment terms to match your B2B invoicing practices:

### Standard Payment Terms

Payment is due **X days from the fulfillment date**.

**Example:**
- Order fulfilled: January 15
- Payment term: 30 days
- **Payment due: February 14** (Jan 15 + 30 days)

**Available durations:** 7, 15, 20, 30, 45, 60, 90 days

**When to use:**
- Simple, straightforward payment terms
- Common for B2B transactions
- Easy for buyers to understand

### End-of-Month (EOM) Payment Terms

Payment is due at the **end of the current month (at fulfillment) plus X days**.

**Example:**
- Order fulfilled: January 15
- Payment term: EOM+30
- Calculation: End of January (Jan 31) + 30 days
- **Payment due: February 28** (or Feb 29 in leap years)

**Available durations:** 30, 45, 60 days only

**When to use:**
- Aligns with monthly accounting cycles
- Common in industries with monthly billing
- Simplifies payment tracking for buyers with multiple orders

**Display:**
- Admin: "End of Month + 30 days"
- Checkout: "Pay in 30 days from end of month"

**How it works:**
1. Two's backend calculates the end of the month when the order is fulfilled
2. Adds the specified days to that date
3. Buyer receives invoice with the calculated due date

---

## How It Works

Expand Down Expand Up @@ -331,6 +389,45 @@ The module builds order payloads that exactly match PrestaShop invoices:
- Check Order Intent was approved
- Verify JavaScript loaded correctly
- Check browser console for errors
- Ensure company is selected (not just typed) - search and click a result

### "Invalid Phone Number" Error
- **Symptom**: Order fails with phone validation error
- **Solutions**:
- Ensure customer has entered a valid phone number in billing address
- Module tries both `phone` and `phone_mobile` fields automatically
- Phone must be valid for the selected country
- Check billing address has a phone number filled in

### "Company Details Required" Message
- **Symptom**: Two payment shows message asking to provide company details
- **Solutions**:
- Customer must enter company name in the billing address Company field
- Customer must search and **select** their company from the dropdown results
- Simply typing a company name is not enough - must click to select from search
- If using an existing address, customer should edit it to add/verify company

### Tax Rate Issues (0% Tax)
- **Symptom**: Two API rejects order with tax rate error
- **Solutions**:
- Enable Debug Mode in module settings (Other Settings → Enable Debug Mode)
- Check PrestaShop logs for "TwoPayment: Product tax debug" entries
- Verify products have correct tax rules assigned in PrestaShop
- Module now calculates tax from actual amounts as fallback
- Contact Two support with debug logs if issue persists

### Debug Mode
- **When to use**: Only enable when requested by Two support for troubleshooting
- **What it logs**:
- Tax calculations per product (rate field, net/gross amounts, calculated rate)
- Helps diagnose tax rate discrepancies between PrestaShop and Two API
- **How to enable**:
1. Go to Module Configuration → Other Settings
2. Toggle "Enable Debug Mode" to Yes
3. Save settings
4. Reproduce the issue
5. Check PrestaShop logs (`var/logs/`)
6. Disable Debug Mode when done

## Security

Expand Down Expand Up @@ -379,4 +476,4 @@ Two Commercial License

## Copyright

© 2021-2025 Two Team
© 2021-2026 Two Team
2 changes: 1 addition & 1 deletion config.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<module>
<name>twopayment</name>
<displayName><![CDATA[Two - BNPL for businesses]]></displayName>
<version><![CDATA[2.2.0]]></version>
<version><![CDATA[2.3.0]]></version>
<description><![CDATA[This module allows any merchant to accept payments with Two payment gateway.]]></description>
<author><![CDATA[Two]]></author>
<tab><![CDATA[payments_gateways]]></tab>
Expand Down
1 change: 1 addition & 0 deletions controllers/front/confirmation.php
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ public function postProcess()
'two_order_state' => $final_state,
'two_order_status' => $final_status,
'two_day_on_invoice' => (string)$this->module->getSelectedPaymentTerm(), // Selected payment term
'two_payment_term_type' => Configuration::get('PS_TWO_PAYMENT_TERM_TYPE'), // Term type (STANDARD or EOM)
'two_invoice_url' => $response['invoice_url'],
);
$this->module->setTwoOrderPaymentData($order->id, $payment_data);
Expand Down
15 changes: 10 additions & 5 deletions controllers/front/orderintent.php
Original file line number Diff line number Diff line change
Expand Up @@ -234,22 +234,27 @@ public function ajaxProcessCheckOrderIntent()
// Store company data in PrestaShop session for future use
$this->storeCompanyDataInSession($companyData);

// ENHANCED VALIDATION: Provide clear status codes for different company data scenarios
// This allows frontend to show specific guidance to users

// Simple validation - require both company name and organization number
// Case 1: No company name at all - user hasn't entered company details
if (empty($companyName)) {
PrestaShopLogger::addLog('TwoPayment: ERROR - No company name provided in form data', 3);
PrestaShopLogger::addLog('TwoPayment: No company name provided - prompting user', 2);
$this->sendJsonResponse(json_encode([
'success' => false,
'error' => 'Company name is required for business accounts'
'status' => 'no_company',
'error' => $this->module->l('To pay with Two, go back to your billing address and enter your company name in the Company field.')
]));
return;
}

// Case 2: Has company name but no org number - common with existing addresses
if (empty($companyId)) {
PrestaShopLogger::addLog('TwoPayment: ERROR - No organization number provided in form data', 3);
PrestaShopLogger::addLog('TwoPayment: Company name exists but no org number - prompting user to search', 2);
$this->sendJsonResponse(json_encode([
'success' => false,
'error' => 'Organization number is required. Please select your company from the search results.'
'status' => 'incomplete_company',
'error' => $this->module->l('To pay with Two, go back to your billing address and search for your company name. Select your company from the results to verify your business.')
]));
return;
}
Expand Down
1 change: 1 addition & 0 deletions controllers/front/payment.php
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,7 @@ public function postProcess()
'two_order_state' => $response['state'],
'two_order_status' => $response['status'],
'two_day_on_invoice' => (string)$this->module->getSelectedPaymentTerm(), // Selected payment term
'two_payment_term_type' => Configuration::get('PS_TWO_PAYMENT_TERM_TYPE'), // Term type (STANDARD or EOM)
'two_invoice_url' => $response['invoice_url'],
'two_invoice_id' => $invoice_id,
);
Expand Down
Loading