定制 myopensoft/ipayment 二次开发

按需修改功能、优化性能、对接业务系统,提供一站式技术支持

邮箱:yvsm@zunyunkeji.com | QQ:316430983 | 微信:yvsm316

myopensoft/ipayment

Composer 安装命令:

composer require myopensoft/ipayment

包简介

Laravel integration with JANM iPayment (Sistem Terimaan Elektronik Kerajaan Persekutuan)

README 文档

README

Laravel integration with JANM's iPayment (Sistem Terimaan Elektronik Kerajaan Persekutuan).

The package owns the conversation with iPayment — envelopes, retries, SFTP, GPG, audit log, idempotency. Your application owns the domain — bills, payments, reconciliation. They meet at events and DTOs.

For the full picture see:

Supported transactions

CodeDirectionChannelPurpose
BILEXT001outHTTPS realtimeSubmit / cancel / increase / decrease / update a bill
RECEXT201inHTTPS realtimeiPayment posts a receipt to your callback
RECEXT302inSFTP batchEnd-of-day receipts (GPG-encrypted)
ERROR9999inSFTP batchAsync error notifications (GPG-encrypted)

Architecture at a glance

flowchart LR
    H[Your Laravel app] -->|sendBill| F[Ipayment facade]
    F --> AC[ApiClient]
    AC -->|POST BILEXT001| API[(JANM API)]
    AC --> AL[(ipayment_messages<br/>audit log)]

    API -->|POST RECEXT201| CB[ReceiptCallbackController]
    CB --> AL
    CB -. ReceiptReceived .-> H

    SFTP[(JANM SFTP)] -->|.gpg files| BP[BatchPuller]
    BP --> AL
    BP -. BatchReceiptParsed / IpaymentErrorReceived .-> H

The host application listens to events and writes to its own tables; it never has to know about envelopes, GPG, or pipe delimiters.

Installation

composer require myopensoft/ipayment
php artisan vendor:publish --tag=ipayment-config
php artisan migrate

Optional models (only if your app does not already model bills / payments):

php artisan vendor:publish --tag=ipayment-models
php artisan migrate

Configuration

Required .env keys:

IPAYMENT_AGENCY_CODE=0100010001
IPAYMENT_SERVICE_CODE=S00001
IPAYMENT_ENV=sit

IPAYMENT_API_URL=https://ipayment.test/api
IPAYMENT_API_USER=...
IPAYMENT_API_PASS=...

IPAYMENT_SFTP_HOST=esb-ipayment.anm.gov.my
IPAYMENT_SFTP_PORT=2222
IPAYMENT_SFTP_USER=...
IPAYMENT_SFTP_KEY=/path/to/private_key

IPAYMENT_GPG_HOME=/path/to/gnupg-home
IPAYMENT_GPG_RECIPIENT=ipayment@anm.gov.my
IPAYMENT_GPG_SIGNING_KEY=your-key-id
IPAYMENT_GPG_PASSPHRASE=...

Verify everything is wired:

php artisan ipayment:check

Sending a bill (BILEXT001)

use Carbon\Carbon;
use MyOpensoft\Ipayment\Data\AddressDto;
use MyOpensoft\Ipayment\Data\BillDto;
use MyOpensoft\Ipayment\Data\ChargeLineDto;
use MyOpensoft\Ipayment\Data\CustomerDto;
use MyOpensoft\Ipayment\Facades\Ipayment;

$bill = BillDto::newBill(
    reference:   'INV-2026-0001',
    amount:      150.00,
    billDate:    Carbon::now(),
    customer:    CustomerDto::individual(
        ic:      '900101011234',
        name:    'Ahmad Bin Ali',
        address: new AddressDto('No 1 Jln A', null, null, '50100', '14', 'MY'),
        phone:   '0123456789',
        email:   'ahmad@example.com',
    ),
    chargeLines: [
        ChargeLineDto::make(
            classificationCode: 'INV_H0161199',
            ptjGroup:           '28020101',
            voteFund:           'B1024',
            programActivity:    'B10010104',
            accountCode:        'H0171101',
            amount:             150.00,
        ),
    ],
);

$response = Ipayment::sendBill($bill);

if ($response->isSuccess()) {
    // Persist $response->messageRef alongside your bill.
}

The bill carries a jenis_maklumat_terimaan ("info type") code which describes what kind of charge it represents. BillDto::newBill() defaults to "01" — a fixed-amount bill with one or more charge lines. The other three info types are encoded as constants on BillDto:

CodeEnglish labelConstantCharge lines
01Bill (with amount)INFO_TYPE_BILLMultiple
02Bill without amountINFO_TYPE_BILL_NO_AMOUNTSingle
03Payment without bill or amountINFO_TYPE_PAYMENT_NO_BILL_AMOUNTSingle
04Payment without rate (open amount)INFO_TYPE_PAYMENT_NO_RATEMultiple

Mutations are first-class — each returns an ApiResponseDto:

Ipayment::cancelBill('INV-2026-0001', 'Paid at counter', now());
Ipayment::addBillAmount('INV-2026-0001', 25.00, now(), 'Surcharge');
Ipayment::reduceBillAmount('INV-2026-0001', 10.00, now(), 'Adjustment');
Ipayment::updateBill($bill);

Listen for outcomes:

use MyOpensoft\Ipayment\Events\BillSent;
use MyOpensoft\Ipayment\Events\BillSendFailed;

Event::listen(BillSent::class,       fn ($e) => /* mark sent */ null);
Event::listen(BillSendFailed::class, fn ($e) => /* alert / retry queue */ null);

Receiving receipts (RECEXT201)

The package registers POST /ipayment/receipt (route prefix configurable). Listen for the event and write to your own tables:

use MyOpensoft\Ipayment\Events\ReceiptReceived;

Event::listen(ReceiptReceived::class, function (ReceiptReceived $e) {
    Payment::create([
        'gateway'        => 'ipayment',
        'reference'      => $e->receipt->billReference,
        'receipt_number' => $e->receipt->receiptNumber,
        'amount'         => $e->receipt->amount,
        'payment_mode'   => $e->receipt->paymentMode,
        // ...
    ]);
});

Lock the callback down via middleware:

// config/ipayment.php
'callback' => [
    'prefix'     => 'ipayment',
    'middleware' => ['api', 'auth.basic'],
],

Idempotency

JANM may retry callbacks. To dedupe, point the package at a resolver:

'receipt_resolver' => \MyOpensoft\Ipayment\Support\AuditLogReceiptResolver::class,

The bundled resolver checks the audit log for a settled (response_code='00') prior row with the same message_ref or receipt_number. Write your own implementing MyOpensoft\Ipayment\Contracts\ReceiptResolver to consult your domain tables instead.

Batch SFTP (RECEXT302, ERROR9999)

Schedule the pull (typical cadence: every 15 minutes):

// app/Console/Kernel.php
$schedule->command('ipayment:pull-batch')->everyFifteenMinutes();

Then listen for the per-row events:

use MyOpensoft\Ipayment\Events\BatchReceiptParsed;
use MyOpensoft\Ipayment\Events\IpaymentErrorReceived;
use MyOpensoft\Ipayment\Events\BatchFileProcessed;

Event::listen(BatchReceiptParsed::class,    fn ($e) => /* reconcile */);
Event::listen(IpaymentErrorReceived::class, fn ($e) => /* alert */);
Event::listen(BatchFileProcessed::class,    fn ($e) => /* metrics */);

Decrypted files are archived under storage/app/ipayment/archive; files that fail to decrypt or parse go to quarantine/.

Audit log

Every message the package sends or receives is written to ipayment_messages before any event fires. Schema:

ColumnPurpose
directionin / out
process_codeBILEXT001, RECEXT201, RECEXT302, ERROR9999
message_ref40-char nombor_rujukan_message (indexed)
receipt_numbernombor_resit for RECEXT201 / RECEXT302 (indexed)
response_codekod_respond00 / 01 / 02
request_payloadthe JSON envelope sent or received
response_payloadthe JSON sent back, or exception details
source_filebatch filename for SFTP rows
http_statusfor outgoing rows

This table is the single source of truth for "did we see this message" and powers the default idempotency resolver.

Host responsibilities (out of scope)

  • Master data — services, charge lines, PTJ groups, vot/dana mapping
  • Bill / payment / receipt domain model (or use the optional models)
  • Reconciliation policy
  • IP whitelisting, SFTP key exchange, GPG key exchange (operational)
  • Scheduling ipayment:pull-batch

Language

The iPayment protocol's JSON keys are in Malay (mesej, kod_proses, kod_respond, maklumat_resit, …) and the package keeps them that way on the wire — that's not negotiable. Everything else — PHP identifiers, audit log columns, status enum values — is English.

License

MIT

统计信息

  • 总下载量: 0
  • 月度下载量: 0
  • 日度下载量: 0
  • 收藏数: 0
  • 点击次数: 4
  • 依赖项目数: 0
  • 推荐数: 0

GitHub 信息

  • Stars: 0
  • Watchers: 0
  • Forks: 0
  • 开发语言: PHP

其他信息

  • 授权协议: MIT
  • 更新时间: 2026-05-29

承接程序开发

PHP开发

VUE

Vue开发

前端开发

小程序开发

公众号开发

系统定制

数据库设计

云部署

网站建设

安全加固