Repository navigation
Releases
Releases · sasateguh/bayarkit
bayarkit 0.3.1
Changed
- The "Need help installing?" offer in the READMEs and the demo now names hello@sasateguh.com, the studio's public address,
instead of admin@sasateguh.com. The contact guard intools/support.mjsaccepts only the new address. - The repository README opens with the Sasa Teguh open-source header: logo (
brand/), CI, npm and licence badges, and links to
the demo, npm, Packagist and the Malay README.
bayarkit 0.3.0
Added
- The demo is hosted at https://bayarkit.sasateguh.com, built from
examples/demo/Dockerfile(non-root, read-only
file system). A fourth scenario sends the genuine paid callback of another, cheaper order: only the match on
reference and amount refuses it. @bayarkit/paddleandBayarkit\Gateways\Paddle: Paddle Billing, for selling abroad with Paddle as merchant of
record. One-time transactions sent tax-inclusive,Paddle-Signaturewebhooks, refunds. Paddle has no MYR: the
adapter charges in one configured currency (USD by default). Measured on the sandbox: card payments paid and
declined, real signed webhooks (retries re-signed), full and partial refunds. The price is sent with its quantity
fixed at 1, because the checkout otherwise lets the buyer pick 1 to 100.@bayarkit/paypalandBayarkit\Gateways\PayPal: PayPal Orders v2.capturePaymentcaptures an approved order
(safe to retry);getPaymentonly reads, and an order is paid only when its capture isCOMPLETED. Webhooks are
verified by PayPal'sverify-webhook-signature. Refunds. Measured on the sandbox: a sandbox buyer approving, captures
in USD and MYR (JPY, a currency the account did not hold, stayedPENDING), real webhooks verified, full and partial
refunds.- Refunds, an optional capability:
refundPayment({ paymentId, amount })returns aRefundRecord(pending,
succeeded,failed,unknown); an amount above what is left is refused before any request
(checkRefundAmount). PHP:Bayarkit\Refunds,RefundInput,RefundRecord. The conformance suite runs its refund
cases only for adapters that implement it. No existing adapter changes. verifyCallbackmay return a Promise (a gateway that verifies by an API call);confirmPaymentawaits it, and a
rejected Promise isgateway_error, notinvalid_signature. Synchronous adapters are unchanged.GatewayInfo.currency: the currency an adapter charges in, whichconfirmPaymentexpects when the order names
none (MYR when absent).@bayarkit/testing:startFakePaddleandstartFakePayPal, andbayarkit-fake paddle/bayarkit-fake paypal.
The control API gainsPOST /approve/<id>(PayPal: the buyer approves, nothing captured).
Changed
scripts/npm-trust.shtakes package names, andNPM_TRUST_PLACEHOLDER=yanswers its question in advance: a new
adapter's trust costs one 2FA approval per step, not one per package.- README, "After
confirmPayment": two bugs a single-customer test never finds. Concurrent payments (one
transaction, a lock on the order row, a unique index on the gateway's payment id, a retry on deadlock) and a key
rotated while a bill is open (save the environment and a key fingerprint with the payment, verify with those
credentials, keep the old key until its last bill expires). The generated skills carry the same two rules. - Every README (the repository, the PHP package and each npm package) ends with a "Need help installing?" section in
English and Malay, generated from one file,docs/support.json, bynode tools/rujukan.mjs skills. It replaces the
"Work with Sasa Teguh" section and names three contacts: the contact page, admin@sasateguh.com and WhatsApp. The
generator refuses a config that names another contact, a price or a ranking claim;"enabled": falseremoves the
section everywhere. - Demo: a new look in light and dark themes (switched with a link, no JavaScript) and self-hosted fonts. The
simulated payment page is drawn as a taped "fake gateway" panel apart from the demo, each check on the order page
shows a drawn mark and its state in words, and 404 / 503 are pages with a way back. scripts/demo-webkit.pyalso checks the demo at 1440 px, the theme switch, that the install offer stays in the flow
of every page without covering a button, form or the simulated gateway, and that WebKit has not crashed 8 s after
loading. The README pictures (docs/demo/) show the new look.scripts/demo-webkit.pyfirst checks what every visitor receives, without a browser: the security headers and a
CSP that allows no script, no Cloudflare analytics beacon, the install offer linking to exactly the contacts in
docs/support.json, and on the hosted demo HTML and CSS compressed.
Fixed
-
Demo: on the order page, checks that did not run were moved off screen by the skip link's style.
-
Demo memory: each rotated set of fakes now uses its own HTTP agent, closed with the set. Global
fetchkept a
connection pool for every port it had seen (~1.7 MB per 1 000 checkouts). Smoke setups accept an optionalfetch.
bayarkit 0.2.2
Changed
- Package descriptions on npm say what each adapter does for Node.js and TypeScript (create payments, verify callbacks,
confirm with a fresh status query), and every package carries thenodejskeyword. No code changes.
bayarkit 0.2.1
Billplz measured on its sandbox.
Changed
- Evidence:
@bayarkit/billplzis nowmeasured(www.billplz-sandbox.com, 27 Oct 2026): a paid and a failed payment
through the simulator, with the real server-to-server callback and the redirect both verified by the adapter. Changing
one field fails the X Signature. A failed payment still sends a callback (paid=false,state=due), and neither the
callback nor the redirect carriesreference_1: find the order by the bill id (seeexamples/web-standard). @bayarkit/testing: the Billplz fake upper-cases the collection and payer name from creation on, as the sandbox does,
and keepspaid_at(nulluntil paid) on create and get.
bayarkit 0.2.0
Three gateways measured on their sandboxes, and a security fix in @bayarkit/bayarcash that the measurement found.
Security
@bayarkit/bayarcash: the check that a transaction belongs to your portal never ran. The API secret key covers every
portal in a Bayarcash account, so a correctly signed return or callback from another portal could confirm your order
when the order numbers matched. The adapter looked forportal_keyon the transaction; the sandbox shows the portal
only by name (portal). It now compares that name with the name of your configured portal key (GET /portals, read
once) and refuses anything else withFOREIGN_PORTAL. Give each portal in an account a unique name.
Changed
@bayarkit/bayarcash:getPaymentalso takes the payment intent id thatcreatePaymentreturns (pi_…). An unpaid
intent reads aspending; a paid one is read through its transaction. Before, it answered HTTP 404.- Evidence: Bayarcash and Stripe are now
measured. toyyibPay's status query was measured again with and without
userSecretKey. @bayarkit/testing: the toyyibPay fake empties only the payer fields withoutuserSecretKey(the sandbox keeps the
fee); the Bayarcash fake answers with the measured shapes (201 for intents,portalby name,GET /payment-intents/{id}).
Added
- Gotchas: a wrong toyyibPay secret key looks like an unpaid bill; Bayarcash portals share one secret; a Bayarcash checkout
id is an intent, not a transaction; a failed Stripe FPX payment sends no Checkout event; Stripe's error below RM2.00
names the payment method types, not the amount.
bayarkit 0.1.0
First release: @bayarkit/core, @bayarkit/testing, fourteen gateway adapters on npm, and bayarkit/bayarkit
(PHP/Laravel: toyyibPay and CHIP).
Added
@bayarkit/core: theGatewayinterface,confirmPayment(signature → fresh gateway query → exact reference,
amount and currency match),readCallback(fields + raw body + headers, so both body-signed and header-signed
gateways verify; JSON, url-encoded, multipart), integer-sen money helpers.@bayarkit/core:checkoutResponse(payment, label?)sends the customer to pay — a 303 redirect, or, for
gateways that take a POSTed form (CreatedPayment.form: iPay88, eGHL, Fiuu), an HTML-escaped self-submitting page
with a visible button,no-store,noindexand a hash-pinned CSP.autoPostHtmland thePaymentFormtype are
exported; iPay88's ownautoPostHtml/IPay88Formnow point at them.@bayarkit/testing: fake toyyibPay, Bayarcash and CHIP servers, and the conformance suite every adapter runs.bayarkit-fakeCLI in@bayarkit/testing: any fake as a standalone server with an HTTP control API, for tests in
other languages (no vitest needed; vitest is now an optional peer).@bayarkit/toyyibpay: measured on the toyyibPay sandbox (10 October 2026).@bayarkit/bayarcash: Bayarcash API v3, from the official docs and PHP SDK (not yet measured).@bayarkit/billplz: Billplz API V3 Bills with X Signature verification of both the callback and the redirect
query, from the official docs and WooCommerce plugin (not yet measured); reproduces all three official X Signature
test vectors.startFakeBillplzin@bayarkit/testing.@bayarkit/chip: CHIP Collect, from the official OpenAPI (not yet measured): RSAX-Signaturechecked over the
raw callback body,fetchPublicKey()(decodes CHIP's JSON-encoded PEM), callback URLs with a port refused before
calling CHIP, a requiredenvironmentthat refuses a purchase from the other mode.@bayarkit/stripe: Stripe (Malaysia) Checkout Sessions over raw REST, card + FPX in MYR, from the official docs
(not yet measured): pinsStripe-Version: 2026-09-30.endiveand sendsallowed_payment_method_types[](endive
rejectspayment_method_typeswith 400); webhooks verified like stripe-node (${t}.${rawBody}, everyv1, 300 s
tolerance).@bayarkit/testinggainsstartFakeStripe.@bayarkit/hitpay: HitPay Payment Requests, from the official docs, OpenAPI and PHP SDK (not yet measured): sen ↔
ringgit at the edge; BOTH callback schemes verified as separate paths with separate salts (event webhook
Hitpay-Signatureover the RAW body with the webhook salt; legacy per-requesthmacover sorted key+value fields
with the API salt); re-serialised JSON is refused.startFakeHitPayin@bayarkit/testing.@bayarkit/2c2p: 2C2P PGW Payment API v4.x (Payment Token + Payment Inquiry), from the official docs (not yet
measured): HS256 JWT with node:crypto (secret key used as ASCII text, anyalgbut HS256 refused), all three
official JWT test vectors reproduced, backend notification verified, the unsigned frontend return never trusted,
decimal ringgit converted to sen without rounding.startFake2C2Pin@bayarkit/testing.@bayarkit/senangpay: senangPay on the DOKU platform (Checkout API), from the official DOKU Malaysia API Reference
(not yet measured):Signature: HMACSHA256=over Client-Id, Request-Timestamp, path and the base64 SHA-256 digest of
the RAW body, per the written rules and the Java/Ruby samples (the Python/NodeDigest:form is refused);
notifications verified over the merchant's notification path; sen ↔ ringgit only at the edge.startFakeSenangPayin
@bayarkit/testing.@bayarkit/curlec: Razorpay Curlec, from the official docs (not yet measured): Payment Links (hosted page) whose
getPaymentreports the order's state,createOrderfor Standard Checkout; webhooks verified over the RAW body with
the webhook secret (the official Node sample'sJSON.stringify(parsed)fails), Payment Link and Checkout callbacks
with the key secret; all five official test vectors reproduced.startFakeCurlecin@bayarkit/testing.@bayarkit/gkash: Gkash host-to-host checkout + transaction query, from the official docs, WooCommerce plugin and
gkashmy SDKs (not yet measured): SHA-512 over the whole upper-cased string (AMT keeps the leading zero,0.10→
010), every official test vector reproduced, the signed checkout response verified, the unsigned query checked
against the cart id asked for, only status88is paid; DOC's own callback example (wrong CID) is refused.
startFakeGkashin@bayarkit/testing.@bayarkit/ipay88: iPay88 Malaysia (OPSG, now NTT DATA Payment Services), from the official public tools on the
gateway host (no developer docs are public; not yet measured): HMAC-SHA512 keyed with the MerchantKey over a message
that also starts with it, all three official HMAC vectors reproduced, every "." and "," stripped from the signed
amount (the official tool strips only the first of each); the checkout is a form POST (form+html, not a
redirect); re-query throughenquiry.aspwhere only the exact text00is paid;BACKEND_ACK=RECEIVEOK.
startFakeIPay88in@bayarkit/testing.@bayarkit/xendit: Xendit (Malaysia) Payment Sessions plus the v3 payment request lookup, from the official docs (not
yet measured): Basic auth, the key prefix picks test or live; amounts in ringgit at the API, sen at the interface.
Webhooks carry NO signature, only a staticx-callback-token, compared in constant time before the RAW body is
parsed;confirmPayment's fresh GET is the real protection. Readspayment_session.*andpayment.*events.
startFakeXenditin@bayarkit/testing.@bayarkit/fiuu: Fiuu (formerly Razer Merchant Services / MOLPay) hosted payment page, from the official API spec
PDF v13.97, WooCommerce plugin and PHP SDK (not yet measured):vcodestandard or extended (config flag; the official
vector reproduced), callbackskeydouble MD5 over the strings exactly as posted (RM→MYRafterwards), requery
q_by_tid / q_by_oid with the answer'sVrfKeychecked, one request pergetPayment(never polled). Ids carry the
order, amount andtranIDbecause Fiuu has no create API and every requery hashes the amount.startFakeFiuuin
@bayarkit/testing.@bayarkit/eghl: eGHL (ADAPTIS e-Commerce), from the official public code only (no public API guide; not yet
measured): GET/POST redirect topayment.aspxwith the SALE HashValue, callbacks verified with the 10-field
HashValue2 of the eGHL_HASH class (the official Omnipay vector reproduced; the shorter Magento/OpenCart variants are
refused), status from a signed QUERY that repeats the original amount, sen ↔ ringgit at the edge.
startFakeEghlin@bayarkit/testing.php/—bayarkit/bayarkitfor PHP 8.2+ and Laravel 11/12: the core interface,Confirm::payment,Checkout,
toyyibPay (measured) and CHIP (docs) adapters, a service provider configured from.env,Callback::from($request),
Checkout::response(),php artisan bayarkit:chip-public-key. Tested against the samebayarkit-fakegateways as
the TypeScript adapters (scripts/php-gate.sh, in the gate), plus phpstan level 8.scripts/php-compat.shproves
the composer promises: PHP 8.2 with the lowest dependencies (Laravel 11) and PHP 8.4 with the newest (Laravel 12).- Every package has a generated README (EN + BM: install, the same quick start, the gateway's sourced traps, a link
to its spec) and full npm metadata (repository with directory, homepage, bugs, keywords, provenance), from the root
package.jsonand the registry;rujukan skills --checkkeeps all 16 from drifting. docs/demo/demo.png+demo.ms.pngin the READMEs, regenerated from the running demo byscripts/demo-shot.py.- Issue forms (bug, gateway request; secrets warned against) and a pull-request checklist from CONTRIBUTING.
examples/demo(npm run demo): every adapter against its fake, BM + EN, no browser JavaScript; pay / unpaid
signed callback / forged callback, with eachconfirmPaymentcheck shown. Tested for all gateways in vitest and on
WebKit iPhone 13 (scripts/demo-webkit.py).examples/web-standard: checkout and callback handlers that take anyGateway; its test runs them unchanged against the fake of every gateway. The README testing snippet runs as written in the suite.- Release tooling:
.github/workflows/release.ymlpublishes every package on av*tag through npm Trusted Publishing with provenance;scripts/release-check.mjs(in the gate) keeps all packages on one version with exact internal dependencies.
Fixed before release
Found by the toyyibPay sandbox measurement, the daily docs check and independent AI review rounds; each fix has a test
that failed first.
- CHIP (both twins):
environmentis required and a purchase that does not stateis_testis refused, so a test
payment does not confirm a live order: passenvironment: 'test' | 'live'(CHIP_ENVin PHP). - Gkash: references are upper-case only (
[A-Z0-9._-]); Gkash's signature does not cover letter case, so
a lower-case reference is refused. - senangPay: compares
payment.amountwhen the retrieve carries it. - demo: memory bound holds under parallel load (100 at once), and a failing rotation cools down and answers 503.
- Billplz: a bill from ANOTHER collection of the same account is refused (
FOREIGN_COLLECTION; the X Signature
key is account-wide), and a paid bill is compared onpaid_amount, the money received. - Bayarcash: a transaction reported for another portal is refused (
FOREIGN_PORTAL). - toyyibPay, Bayarcash:
verifyCallbackno longer throws on a missing body; the conformance suite now feeds every
adapter{}, nulls and array values. confirmPayment(both twins): averifyCallbackthat throws is a `gateway_err...