Remote IT staffing & payroll · Engineering since 2016.
Business applications · Fintech / APAC · RedDot Payment
Sevendyne built a remote PHP and Android team for RedDot Payment (PayU-group APAC), then that team worked with their product and gateway leads to design and ship the CodeIgniter CRM — EMI plans, payment channels, payment forms, and companion Android for merchant operations in Singapore and Indonesia.
3-minute CTO brief
We staffed a dedicated PayU-group pod for RedDot Payment and embedded them with client engineering — daily standups, shared PR reviews, and pairing on tenant boundaries. They needed one CRM that could serve Singapore and Indonesia tenants without cross-leaking merchant data — while EMI plans, wallet channels, and QR flows kept shipping on live traffic. The team isolated tenants at the DB boundary, wrapped each payment provider behind adapters, and staged schema changes with feature flags.
Multi-tenant fintech · EMI · payment adapters
Every request carries tenant_id resolved at the edge. CRM entities, ledger views, and reconciliation exports are scoped queries — no shared tables without tenant prefix. Mobile Android clients use the same REST contracts with device-bound credentials.
| Area | Design choice | Rationale |
|---|---|---|
| EMI schedules | State machine per plan (draft→active→delinquent→closed) | Operators see auditable transitions; cron jobs idempotent per installment. |
| Payment channels | Adapter interface per provider | New wallet/QR provider = new adapter, not forked CRM. |
| Schema changes | Shadow columns + dual-write window | Live merchants never see broken reconciliation during deploy. |
| Android app | Thin client, server-side validation | QR payloads validated server-side; app handles offline queue only. |
// Channel adapter contract — each provider implements
interface PaymentChannelAdapter {
public function initiate(PaymentIntent $intent): ChannelResponse;
public function capture(string $providerRef): CaptureResult;
public function reconcile(ReconBatch $batch): ReconSummary;
}
// Tenant guard on every CRM query
$merchant = MerchantModel::forTenant($tenantId)->find($id);