A module for Braintree reoccurring payments and transactions built for the Nestjs framework.
Using the Braintree node SDK.
NOTE! Currently building
$ yarn add nestjs-braintree
import { Module } from '@nestjs/common';
import { BraintreeModule } from 'nestjs-braintree';
import * as braintree from 'braintree';
@Module({
imports: [
BraintreeModule.forRoot({
environment: braintree.Environment.Sandbox,
merchantId: '',
publicKey: '',
privateKey: '',
}),
],
})
export default class AppModule {}
import { Module } from '@nestjs/common';
import { BraintreeModule } from 'nestjs-braintree';
@Module({
imports: [
BraintreeModule.forFeature(),
],
})
export default class SubModule {}
import { Module } from '@nestjs/common';
import { BraintreeModule } from 'nestjs-braintree';
import { ConfigModule, ConfigService } from 'nestjs-config';
@Module({
imports: [
ConfigModule.load('root/to/config/*/**.{ts,js}'),
BraintreeModule.forRootAsync({
useFactory: async (config: ConfigService) => config.get('braintree'),
inject: [ConfigService],
}),
],
})
export default class AppModule {}
//config/braintree.ts
import * as braintree from 'braintree';
export default {
environment:
process.env.NODE_ENV == 'development'
? braintree.Environment.Sandbox
: braintree.Environment.Live,
merchantId: process.env.BRAINTREE_MERCHANT_ID,
publicKey: process.env.BRAINTREE_PUBLIC_KEY,
privateKey: process.env.BRAINTREE_PRIVATE_KEY,
};
Braintree is capable of making one off transactions
import { Module } from '@nestjs/common';
import { BraintreeModule, InjectBraintreeProvider } from 'nestjs-braintree';
import { ConfigModule, ConfigService } from 'nestjs-config';
class TransactionProvider {
constructor(
@InjectBraintreeProvider()
private readonly braintreeProvider: BraintreeProvider,
) {}
takePayment(amount: string, nonce: string) {
this.braintreeProvider.sale({
payment_method_nonce: nonce,
amount,
});
}
}
@Module({
imports: [
ConfigModule.load('root/to/config/*/**.{ts,js}'),
BraintreeModule.forRoot({
useFactory: async (config: ConfigService) => config.get('braintree'),
inject: [ConfigService],
}),
],
providers: [TransactionProvider],
})
export default class AppModule {}
Available methods relating to transactions are
braintreeProvider.sale(transaction: BraintreeTransactionInterface): Promise<BraintreeTransactionResultInterface>
braintreeProvider.refund(transactionId: string, amount?: string, orderId?: string): Promise<BraintreeTransactionResultInterface>
braintreeProvider.find(transactionId: string): Promise<BraintreeTransactionResultInterface>
The braintree SDK does offer additional methods. I will implement them soon hopefully
When using subscriptions with braintree, braintree will issue webhooks to your endpoint which you can use the decorators to handle those actions.
import { Module } from '@nestjs/common';
import {
BraintreeModule,
BraintreeWebhookModule,
BraintreeSubscriptionCanceled,
BraintreeSubscriptionExpired,
BraintreeWebhookHandler,
} from 'nestjs-braintree';
import { ConfigModule, ConfigService } from 'nestjs-config';
@BraintreeWebhookHandler()
class SubscriptionProvider {
@BraintreeSubscriptionCanceled()
canceled() {
console.log('subscription canceled');
}
@BraintreeSubscriptionExpired()
expired() {
console.log('subscription expired');
}
}
@Module({
imports: [
ConfigModule.load('root/to/config/*/**.{ts,js}'),
BraintreeModule.forRootAsync({
useFactory: async (config: ConfigService) => config.get('braintree'),
inject: [ConfigService],
}),
BraintreeWebhookModule,
],
providers: [SubscriptionProvider],
})
export default class AppModule {}
The idea of the Braintree Webhook Module is to make implementation of actions a lot easier. For example we can build a provider like this one to cancel canceled subscriptions.
@BraintreeWebhookHandler()
export class SubscriptionProvider {
constructor(@InjectRepository(Subscription) private readonly subscriptionRepository: Repository<Subscription>) {}
async findByBraintreeId(braintreeId: string): Promise<Subscription|null> {
return await this.subscriptionRepository.find({
where: {
braintreeId,
},
});
}
async update(subscription: Subscription): Promise<boolean> {
return await this.subscriptionRepository.update(subscription);
}
@BraintreeSubscriptionCanceled()
async canceled(webhook: BraintreeWebhook) {
const subscription = await this.findByBraintreeId(webhook.subscription.id);
if (!subscription) {
return;
}
subscription.active = false;
await this.update(subscription);
}
}
Shortname | Braintree webhook name/const/key | NestJS decorator |
---|---|---|
Subscription Canceled | subscription_canceled |
@BraintreeSubscriptionCanceled() |
Subscription Expired | subscription_expired |
@BraintreeSubscriptionExpired() |
Subscription Charged Successfully | subscription_charged_successfully |
@BraintreeSubscriptionChargedSuccessfully() |
Subscription Charged Unsuccessfully | subscription_charged_unsuccessfully |
@BraintreeSubscriptionChargedUnsuccessfully() |
Subscription Went Active | subscription_went_active |
@BraintreeSubscriptionWentActive() |
Subscription Went Past Due | subscription_went_past_due |
@BraintreeSubscriptionWentPastDue() |
Subscription Trial Ended | subscription_trial_ended |
@BraintreeSubscriptionTrialEnded() |
You can find out more about the webhooks here.
You may want to divert from the default routing of {your_domain}/braintree/webhook
for whatever reason. You can do so using the forRoot
method on the BraintreeWebhookModule
like so
@Module({
imports: [
ConfigModule.load('root/to/config/*/**.{ts,js}'),
BraintreeModule.forRootAsync({
useFactory: async (config: ConfigService) => config.get('braintree'),
inject: [ConfigService],
}),
BraintreeWebhookModule.forRoot({
root: 'replace-braintree',
handle: 'replace-webhook',
}),
],
providers: [SubscriptionProvider],
})
export default class AppModule {}
The above will result in your route for your braintree webhooks being {your_domain}/replace-braintree/replace-webhook