Skip to content

A module for braintree reoccurring payments and transactions 💳

License

Notifications You must be signed in to change notification settings

nestjsx/nestjs-braintree

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

99 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Coverage Status

Nestjs Braintree

A module for Braintree reoccurring payments and transactions built for the Nestjs framework.


Using the Braintree node SDK.

NOTE! Currently building

Install

$ yarn add nestjs-braintree

Use

Basic use

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 {}

In a subModule

import { Module } from '@nestjs/common';
import { BraintreeModule } from 'nestjs-braintree';

@Module({
  imports: [
    BraintreeModule.forFeature(),
  ],
})
export default class SubModule {}

Use with nestjs-config

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,
};

Transactions

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

Sale

braintreeProvider.sale(transaction: BraintreeTransactionInterface): Promise<BraintreeTransactionResultInterface>

Refund

braintreeProvider.refund(transactionId: string, amount?: string, orderId?: string): Promise<BraintreeTransactionResultInterface>

Find

braintreeProvider.find(transactionId: string): Promise<BraintreeTransactionResultInterface>

The braintree SDK does offer additional methods. I will implement them soon hopefully

Webhooks

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 {}

Use Example

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);
  }
}

Available webhooks

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.

Custom routing for webhooks

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