# PrestaShop module

Native payment module for PrestaShop 1.7 & 8.x

# PrestaShop module

The `southbillpay` module registers **Southbill** as a payment option in PrestaShop. It supports PrestaShop **1.7.6+** and **8.0 – 8.99**, works with guest and account checkout, forwards billing & shipping data, product images, and reconciles orders via signed webhooks — even when the buyer closes the tab.

Current version: **1.0.5** — download it from **Dashboard → Integrations → PrestaShop**.

## Requirements

- PrestaShop 1.7.6 – 8.99
- PHP 7.4+ (8.x recommended)
- Friendly URLs enabled (needed for controller routes)
- Outbound HTTPS to `api.southbill.com`

## Install

1. **Dashboard → Integrations → PrestaShop → Download module** — a fresh `southbillpay-1.0.5.zip` is generated for your account.
2. **Back Office → Modules → Module Manager → Upload a module** → pick the ZIP.
3. After install, click **Configure** on the Southbill tile.

## Configure

| Field | What to paste |
|---|---|
| **Live API key** | An `sk_live_...` key from Dashboard → Developers → API keys. |
| **Test API key** | An `sk_test_...` key. |
| **Mode** | `Live` in production, `Test` while integrating. |
| **Webhook signing secret** | The `whsec_...` shown when you create the webhook endpoint below. |
| **Display name** | Label shown to customers at checkout (default: `Southbill`). |

## Register the webhook

In **Dashboard → Developers → Webhooks → Add endpoint** set:

- **URL:** `https://your-store.com/module/southbillpay/webhook`
- **Events:** `checkout.session.completed`, `payment.succeeded`, `payment.failed`, `charge.refunded`, `charge.dispute.created`

Copy the signing secret into the module's **Webhook signing secret** field. Without it every incoming call is rejected with `401 invalid signature` and orders stay in `Awaiting payment`.

## Order flow

1. Customer selects **Southbill** on the payment step → module calls `POST /v1/checkout/sessions` with cart total, currency, line items (name + image + qty), billing/shipping address, phone, email and `metadata.ps_cart_id`.
2. Session state (cart id, secure key, customer id) is persisted in the dedicated `{PREFIX}southbillpay_session` table — no misuse of the global `configuration` table.
3. Customer is redirected to Southbill Hosted Checkout.
4. On success:
   - Buyer is sent to `/module/southbillpay/validate` which auto-refreshes every 6 seconds until the webhook lands.
   - Southbill sends `payment.succeeded` to `/module/southbillpay/webhook`.
   - Module verifies HMAC (5-minute replay window), matches the cart via `metadata.ps_cart_id`, promotes an existing **Preparation** order to **Paid** and attaches an `OrderPayment`.
5. Terminal states (**Paid, Shipped, Delivered**) are protected — late `expired` or `canceled` events cannot downgrade them.

## Refunds

Create a **credit slip** in the PS back office (Orders → click order → Partial refund / Standard refund). The module hooks `actionOrderSlipAdd` and calls `POST /api-refunds` with the exact slip amount (proportional per line item, matching the original discount distribution).

## Discounts, rounding & multi-currency

- Cart-level discounts are distributed proportionally across line items so Stripe never receives negative amounts.
- Rounding drift between PrestaShop's cart total and the summed line items is absorbed on the largest line item (max ±1 minor unit).
- Zero-decimal currencies (JPY, KRW, HUF, etc.) are scaled correctly; three-decimal currencies (BHD, JOD, KWD, OMR, TND) send the fractional minor units.

## Idempotency

The module stores the last 50 processed `event.id`s per cart in the `southbillpay_session` row. Duplicate deliveries are dropped without side effects. The `Idempotency-Key` on create is the PS cart id — retries never create a second Stripe session.

## Testing

Switch the module to **Test**, place a real order, pay with `4242 4242 4242 4242` (any future expiry, any CVC). The order should move to **Paid** within a few seconds of the redirect. Use `4000 0000 0000 9995` to trigger `payment.failed`.

## Troubleshooting

| Symptom | Fix |
|---|---|
| `Payment could not be initialised. Please try again.` | The API key is wrong for the selected mode, or the store cannot reach `api.southbill.com`. Check **Back Office → Advanced Parameters → Logs**. |
| Module missing at checkout | Reset the module in Module Manager and check that the currency is enabled in **Payment → Preferences → Currency restrictions**. |
| `401 invalid signature` in server log | Signing secret in module ≠ secret shown in dashboard. Regenerate the endpoint and re-paste. |
| Order stuck at `Awaiting payment` | Webhook URL wrong or blocked. `curl -X POST https://your-store.com/module/southbillpay/webhook -d '{}'` must return `401` (signature missing), not `404`. |
| Huge logo on payment step | Older versions shipped the raw logo. Update to 1.0.2+ — the logo is normalised to 124 px width. |
| Product images missing in the Southbill dashboard | Update to 1.0.5+ — earlier versions sent the wrong JSON key. |
| Chrome asks about "access to other apps and services" | That is the Chrome **Payment Handler** prompt for Google Pay / Link on a first-seen domain. One-time "Allow" — Chrome remembers it per origin. |

## Uninstall

Module Manager → Southbill → **Uninstall**. This drops the `southbillpay_session` table and removes all configuration entries. Orders and credit slips are preserved.
