@ciwergrp/nestjs-midtrans
v0.1.0
Published
NestJS wrapper for Midtrans configuration and injectable services.
Readme
@ciwergrp/nestjs-midtrans
NestJS module for calling Midtrans APIs from injectable services.
The package currently supports Midtrans Snap transaction creation and payment URL generation. It also supports static configuration, async configuration, per-call runtime configuration, custom fetch wrappers, request/response interceptors, and request timeouts.
Installation
npm install @ciwergrp/nestjs-midtransRegister the module
Use forRoot() when the Midtrans keys are known when the app starts.
import { Module } from '@nestjs/common';
import { MidtransModule } from '@ciwergrp/nestjs-midtrans';
@Module({
imports: [
MidtransModule.forRoot({
serverKey: process.env.MIDTRANS_SERVER_KEY!,
clientKey: process.env.MIDTRANS_CLIENT_KEY!,
environment: 'sandbox',
}),
],
})
export class AppModule {}Async configuration
Use forRootAsync() when keys come from another Nest provider, such as ConfigService.
import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { MidtransModule } from '@ciwergrp/nestjs-midtrans';
@Module({
imports: [
ConfigModule.forRoot(),
MidtransModule.forRootAsync({
imports: [ConfigModule],
inject: [ConfigService],
useFactory: (config: ConfigService) => ({
serverKey: config.getOrThrow<string>('MIDTRANS_SERVER_KEY'),
clientKey: config.getOrThrow<string>('MIDTRANS_CLIENT_KEY'),
environment: config.get<'sandbox' | 'production'>('MIDTRANS_ENVIRONMENT', 'sandbox'),
}),
}),
],
})
export class AppModule {}Runtime configuration
Use resolveConfig when the Midtrans keys must be resolved for each API call. This is useful when the keys come from a database or another runtime source and changes should take effect without restarting the app.
import { Module } from '@nestjs/common';
import { MidtransModule } from '@ciwergrp/nestjs-midtrans';
import { PaymentConfigModule } from './payment-config.module';
import { PaymentConfigService } from './payment-config.service';
@Module({
imports: [
PaymentConfigModule,
MidtransModule.forRootAsync({
imports: [PaymentConfigModule],
inject: [PaymentConfigService],
useFactory: (paymentConfig: PaymentConfigService) => ({
resolveConfig: async (context) => {
const config = await paymentConfig.getMidtransConfig(context);
return {
serverKey: config.serverKey,
clientKey: config.clientKey,
environment: config.environment,
};
},
}),
}),
],
})
export class AppModule {}Pass any project-specific context when calling a Midtrans method:
await this.midtrans.createSnapTransaction(payload, {
context: {
configKey: 'store-1',
},
});The library does not inspect or require any specific context fields. It only passes the object to resolveConfig.
Per-call configuration
You can also override the Midtrans config for a single call.
await this.midtrans.generateSnapPaymentUrl(payload, {
config: {
serverKey: runtimeConfig.serverKey,
clientKey: runtimeConfig.clientKey,
environment: runtimeConfig.environment,
},
});Per-call config has the highest priority, then resolveConfig, then the static config from forRoot() or forRootAsync().
Default Snap callback URLs
Set snapCallbacks when you want default redirect URLs for Snap transactions.
MidtransModule.forRoot({
serverKey: process.env.MIDTRANS_SERVER_KEY!,
clientKey: process.env.MIDTRANS_CLIENT_KEY,
environment: 'sandbox',
snapCallbacks: {
finish: 'https://your-app.com/payment/finish',
error: 'https://your-app.com/payment/error',
},
});These defaults are merged into every Snap transaction payload. Per-transaction callbacks still win, so you can add custom query strings for a specific payment.
await this.midtrans.createSnapTransaction({
transaction_details: {
order_id: 'ORDER-101',
gross_amount: 10000,
},
callbacks: {
finish: `https://your-app.com/payment/finish?order_id=ORDER-101`,
},
});Inject in a service
import { Injectable } from '@nestjs/common';
import { MidtransService } from '@ciwergrp/nestjs-midtrans';
@Injectable()
export class PaymentsService {
constructor(private readonly midtrans: MidtransService) {}
getMidtransEnvironment() {
return {
environment: this.midtrans.environment,
baseUrl: this.midtrans.baseUrl,
snapBaseUrl: this.midtrans.snapBaseUrl,
};
}
}Generate a Snap payment URL
import { Injectable } from '@nestjs/common';
import { MidtransService } from '@ciwergrp/nestjs-midtrans';
@Injectable()
export class PaymentsService {
constructor(private readonly midtrans: MidtransService) {}
async createPaymentUrl() {
return this.midtrans.generateSnapPaymentUrl({
transaction_details: {
order_id: 'ORDER-101',
gross_amount: 10000,
},
credit_card: {
secure: true,
},
});
}
}Use createSnapTransaction() when you need both values returned by Midtrans:
const transaction = await this.midtrans.createSnapTransaction({
transaction_details: {
order_id: 'ORDER-101',
gross_amount: 10000,
},
});
console.log(transaction.token);
console.log(transaction.redirect_url);Both Snap methods accept optional request options:
await this.midtrans.createSnapTransaction(payload, {
context: {
configKey: 'store-1',
},
timeoutMs: 10000,
});Charge a Core transaction
Core Charge currently supports Bank Transfer and E-Wallet payload types.
Core Charge payloads are validated before the request is sent to Midtrans. The package does not rely on Midtrans fallback behavior for payment_type, bank_transfer.bank, QRIS acquirer, or required e-wallet fields.
Validation also checks shared fields such as transaction_details, item_details, customer_details, and custom_expiry, and rejects payment-specific objects that do not belong to the selected payment_type.
Bank Transfer examples:
await this.midtrans.chargeCoreTransaction({
payment_type: 'bank_transfer',
transaction_details: {
order_id: 'ORDER-101',
gross_amount: 10000,
},
bank_transfer: {
bank: 'bca',
},
});
await this.midtrans.chargeCoreTransaction({
payment_type: 'permata',
transaction_details: {
order_id: 'ORDER-102',
gross_amount: 10000,
},
});
await this.midtrans.chargeCoreTransaction({
payment_type: 'echannel',
transaction_details: {
order_id: 'ORDER-103',
gross_amount: 10000,
},
echannel: {
bill_info1: 'Payment:',
bill_info2: 'Online purchase',
},
});E-Wallet examples:
await this.midtrans.chargeCoreTransaction({
payment_type: 'gopay',
transaction_details: {
order_id: 'ORDER-104',
gross_amount: 10000,
},
gopay: {
enable_callback: true,
callback_url: 'https://your-app.com/payment/gopay',
},
});
await this.midtrans.chargeCoreTransaction({
payment_type: 'qris',
transaction_details: {
order_id: 'ORDER-105',
gross_amount: 10000,
},
qris: {
acquirer: 'gopay',
},
});
await this.midtrans.chargeCoreTransaction({
payment_type: 'shopeepay',
transaction_details: {
order_id: 'ORDER-106',
gross_amount: 10000,
},
shopeepay: {
callback_url: 'https://your-app.com/payment/shopeepay',
},
});Core Charge accepts the same optional request options as Snap methods.
Transaction operations
Transaction operations use the Core API base URL and work for both Snap and Core transactions where Midtrans supports the operation.
const status = await this.midtrans.getTransactionStatus('ORDER-101');
console.log(status.transaction_status);Available methods:
await this.midtrans.getTransactionStatus('ORDER-101');
await this.midtrans.getB2bTransactionStatus('ORDER-101', {
page: 0,
perPage: 10,
});
await this.midtrans.approveTransaction('ORDER-101');
await this.midtrans.denyTransaction('ORDER-101');
await this.midtrans.cancelTransaction('ORDER-101');
await this.midtrans.expireTransaction('ORDER-101');
await this.midtrans.refundTransaction('ORDER-101', {
refund_key: 'ORDER-101-refund-1',
amount: 5000,
reason: 'Item out of stock',
});
await this.midtrans.captureTransaction({
transaction_id: 'transaction-id',
gross_amount: 10000,
});These methods accept the same optional request options as Snap methods:
await this.midtrans.getTransactionStatus('ORDER-101', {
context: {
configKey: 'store-1',
},
timeoutMs: 10000,
});Custom fetch and interceptors
Use fetch, requestInterceptor, and responseInterceptor to add logging, tracing, retries, custom headers, proxies, or tests around outgoing Midtrans requests.
MidtransModule.forRoot({
serverKey: process.env.MIDTRANS_SERVER_KEY!,
clientKey: process.env.MIDTRANS_CLIENT_KEY,
environment: 'sandbox',
timeoutMs: 10000,
fetch: async (url, init) => fetch(url, init),
requestInterceptor: (request) => {
console.log('Midtrans request:', request.method, request.url);
return request;
},
responseInterceptor: (response, request) => {
console.log('Midtrans response:', request.url, response.status);
return response;
},
});The default timeout is 30000 milliseconds. Set timeoutMs: false to disable the package timeout.
Options
| Option | Required | Default | Description |
| --- | --- | --- | --- |
| serverKey | Yes, unless resolveConfig is used | - | Midtrans server key for backend operations. |
| clientKey | No | - | Midtrans client key for frontend/Snap configuration. |
| environment | No | sandbox | Either sandbox or production. |
| baseUrl | No | Based on environment | Midtrans API base URL override. |
| snapBaseUrl | No | Based on environment | Midtrans Snap base URL override. |
| snapCallbacks | No | - | Default Snap redirect callback URLs. Payload callbacks override these values. |
| isProduction | No | Derived from environment | Compatibility flag matching Midtrans Node.js client config style. |
| isGlobal | No | false | Makes the Nest module global when set to true. |
| fetch | No | globalThis.fetch | Custom fetch implementation or wrapper. |
| requestInterceptor | No | - | Hook called before the request is sent. |
| responseInterceptor | No | - | Hook called after a response is received. |
| resolveConfig | No | - | Function used to resolve Midtrans config for every API call. |
| timeoutMs | No | 30000 | Request timeout in milliseconds. Use false to disable. |
Default URLs:
| Environment | baseUrl | snapBaseUrl |
| --- | --- | --- |
| sandbox | https://api.sandbox.midtrans.com | https://app.sandbox.midtrans.com/snap |
| production | https://api.midtrans.com | https://app.midtrans.com/snap |
