CanxJS's payment package provides a unified API for payment processing. It offers secure payment gateway integration with Midtrans (Indonesia's leading payment gateway). Built with security best practices.
Full Snap API support for seamless checkout experience.
SHA-512 signature with timing-safe comparison prevents attacks.
Standalone driver with no core framework dependency required.
Auto-switches between sandbox and production endpoints.
The payment module is an optional package to keep the core framework lightweight.
1npm install @canxjs/payment2# or3bun add @canxjs/payment
Add your Midtrans credentials to your environment variables.
1# .env2MIDTRANS_SERVER_KEY=SB-Mid-server-xxxx3MIDTRANS_CLIENT_KEY=SB-Mid-client-xxxx4MIDTRANS_ENV=sandbox # or 'production'
Use the MidtransDriver to create a Snap checkout session.
1import { MidtransDriver } from '@canxjs/payment';23export class CheckoutController {45async create(req: Request) {6const driver = new MidtransDriver();78const { url, id } = await driver.checkout({9amount: 150000,10currency: 'IDR',11successUrl: 'https://myshop.com/thankyou',12customer: req.user.email,13orderId: 'ORDER-001',14metadata: {15items: [16{ id: 'SKU-001', name: 'Product Name', price: 150000, quantity: 1 }17]18}19});2021// Redirect user to Midtrans payment page22return { redirect_url: url };23}24}
Securely verify incoming webhooks using SHA-512 signature verification with timing-safe comparison.
1import { MidtransDriver } from '@canxjs/payment';23export class WebhookController {45async handle(req: Request) {6const driver = new MidtransDriver();7const payload = await req.json();89// 1. Verify Signature (SHA-512 with timing-safe comparison)10const isValid = await driver.verifySignature(payload);11if (!isValid) {12throw new Error("Invalid Signature");13}1415// 2. Process based on status16const { order_id, transaction_status } = payload;1718switch (transaction_status) {19case 'capture':20case 'settlement':21await Order.markPaid(order_id);22break;23case 'pending':24// Waiting for payment25break;26case 'deny':27case 'cancel':28case 'expire':29await Order.markFailed(order_id);30break;31}3233return new Response('OK');34}35}