# Social Media Guru Pay — Merchant API

Collect **card, Capitec Pay and Instant EFT** in South Africa **without putting PayFast on your website**.

```
Your server                 Buyer browser                 socialmediaguru.co.za
─────────────               ─────────────                 ────────────────────
POST /api/v1/payments  →    SmgPay.open(payment_url)  →   PayFast popup (SMG only)
                            (never load engine.js)
Webhook payment.paid   ←                                  ITN from PayFast
```

- **Checkout:** create a payment on *your server*, then open our popup (`SmgPay.open`)
- **Gateway:** PayFast only ever sees `socialmediaguru.co.za` — never your domain, URLs, or product names
- **You get:** a signed webhook when money clears, then request an EFT payout in the [merchant dashboard](https://www.socialmediaguru.co.za/merchant)

| Resource | URL |
| --- | --- |
| Live docs | https://www.socialmediaguru.co.za/developers |
| OpenAPI explorer | https://www.socialmediaguru.co.za/developers/reference |
| OpenAPI spec | https://www.socialmediaguru.co.za/developers/openapi.yaml |
| JS SDK | https://www.socialmediaguru.co.za/js/smg-pay.js |
| This kit (zip) | https://www.socialmediaguru.co.za/developers/smg-pay-api.zip |
| Postman | `postman/SMG-Pay.postman_collection.json` |
| Apply | https://www.socialmediaguru.co.za/merchant/register |

## Fees (ZAR)

All fees are **exclusive of VAT**, then **15% VAT is added on the fee**.

| Method | Fee excl. VAT | VAT (15%) | Example |
| --- | --- | --- | --- |
| **All methods** (card, Capitec Pay, Instant EFT, SnapScan, Zapper, …) | 6% + **R2.00** | on the fee | R50 → R5.75 fee → **R44.25 net**; R100 → R9.20 fee → **R90.80 net** |

- Minimum amount **R50.00**, maximum **R50 000.00**
- The same amount cannot be charged again within **15 minutes** (anti-fraud)
- Pending payments expire after **30 minutes**
- We still record PayFast’s `payment_method` for reporting. Fee is the same for every method.

## 1. Create a merchant account

1. Apply at https://www.socialmediaguru.co.za/merchant/register
2. Wait for SMG to approve you
3. Open **API keys** and generate `sk_live_…` + webhook secret
4. Store both on your **server** only — never in JavaScript, apps, or git

```
Authorization: Bearer sk_live_…
```

## 2. Create a payment (server)

```http
POST https://www.socialmediaguru.co.za/api/v1/payments
Authorization: Bearer sk_live_…
Content-Type: application/json
```

```json
{
  "amount": 199.00,
  "merchant_ref": "ORD-1001",
  "customer": {
    "name": "Jane Doe",
    "email": "jane@example.com",
    "phone": "0713743360"
  },
  "return_url": "https://yourstore.co.za/order/1001/thanks",
  "cancel_url": "https://yourstore.co.za/order/1001/cancel"
}
```

`201` response (trimmed):

```json
{
  "data": {
    "id": "clx…",
    "number": "SMG-P-20260823-ABC12",
    "merchant_ref": "ORD-1001",
    "status": "pending",
    "amount": 199.00,
    "payment_url": "https://www.socialmediaguru.co.za/pay/…",
    "js_sdk": "https://www.socialmediaguru.co.za/js/smg-pay.js",
    "token": "hex…",
    "fees": { "card": { "net": 180.85 }, "other": { "net": 185.27 } }
  }
}
```

`merchant_ref` is unique per merchant — sending the same ref again returns the existing payment (`reused: true`).

**Do not** send `sk_live_` from the browser. Your backend creates the payment and returns only `payment_url` (or `token`) to the page.

## 3. Open checkout (browser)

Do **not** load `https://www.payfast.co.za/onsite/engine.js` on your site and do **not** iframe our pay page.

```html
<script src="https://www.socialmediaguru.co.za/js/smg-pay.js"></script>
<script>
  SmgPay.open({
    payment_url: data.payment_url,
    onSuccess: function () { window.location = "/thanks"; },
    onCancel: function () {}
  });
</script>
```

The popup is a top-level `socialmediaguru.co.za` window. PayFast runs only there.

If the popup is blocked, the SDK falls back to a same-tab redirect on our domain.

## 4. Webhook (source of truth)

We `POST` JSON to the webhook URL you saved on **API keys**:

```
Content-Type: application/json
X-SMG-Signature: hex HMAC-SHA256 of the raw body (webhook secret)
X-SMG-Event: payment.paid
User-Agent: SMG-Merchant-Webhook/1.0
```

```json
{
  "event": "payment.paid",
  "data": {
    "number": "SMG-P-20260823-ABC12",
    "merchant_ref": "ORD-1001",
    "status": "paid",
    "amount": 199.00,
    "amount_cents": 19900,
    "net": 184.93,
    "net_cents": 18493,
    "method": "cc"
  }
}
```

`amount` / `amount_cents` is what the buyer paid (ZAR). Use `amount_cents` for integer compares — do not credit from a float. `net` is after SMG fees.

Verify the signature **before** fulfilling the order. `onSuccess` in the browser is a convenience — the webhook is authoritative.

Respond with HTTP `2xx`. We record the attempt; treat the webhook as idempotent (same `merchant_ref` may be delivered more than once).

### PHP

```php
$raw = file_get_contents('php://input');
$sig = $_SERVER['HTTP_X_SMG_SIGNATURE'] ?? '';
if (!hash_equals(hash_hmac('sha256', $raw, $webhookSecret), $sig)) {
  http_response_code(401);
  exit;
}
```

### Node

```js
import crypto from "node:crypto";
const expected = crypto.createHmac("sha256", webhookSecret).update(rawBody).digest("hex");
if (!crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(req.headers["x-smg-signature"] || ""))) {
  throw new Error("bad sig");
}
```

### Python

```python
import hmac, hashlib
expected = hmac.new(webhook_secret.encode(), raw_body, hashlib.sha256).hexdigest()
if not hmac.compare_digest(expected, signature_header or ""):
    raise ValueError("bad sig")
```

## Other endpoints

All require `Authorization: Bearer sk_live_…`.

| Method | Path | Notes |
| --- | --- | --- |
| GET | `/api/v1/payments` | `?status=paid&limit=25` (max 100) |
| GET | `/api/v1/payments/{id\|number\|merchant_ref}` | Retrieve one |
| POST | `/api/v1/payments/{id}` | `{ "action": "cancel" }` — pending only |
| GET | `/api/v1/balance` | Available balance + totals |

Errors:

| HTTP | Meaning |
| --- | --- |
| 400 | Validation (`amount`, `merchant_ref`, `customer`, URLs) |
| 401 | Missing / invalid API key |
| 403 | Account pending, suspended, or no secret generated |
| 409 | Same amount charged too recently (`code: velocity_limit`, `retry_after` seconds) |
| 429 | Rate limit (120 req / min / IP) |

## What’s in this kit

| File | Purpose |
| --- | --- |
| `openapi.yaml` | Import into Postman, Insomnia, Bruno, or Swagger |
| `postman/SMG-Pay.postman_collection.json` | Ready-made Postman collection |
| `examples/server.php` | Create payment + webhook verify (PHP) |
| `examples/webhook.php` | Drop-in HTTPS webhook endpoint |
| `examples/server.mjs` | Node.js (run: `node examples/server.mjs`) |
| `examples/server.py` | Python 3 |
| `examples/checkout.html` | Browser popup |
| `examples/woocommerce-gateway.php` | WooCommerce starter gateway |

```bash
# Try creating a payment (replace the secret)
export SMG_SECRET=sk_live_…
node examples/server.mjs
# or
php examples/server.php
# or
python3 examples/server.py
```

Import `openapi.yaml` or the Postman collection, set the bearer token, hit **Create payment**.

## Support

WhatsApp [+27 71 374 3360](https://wa.me/27713743360) · support@socialmediaguru.co.za · Potchefstroom, South Africa
