# Vulcan Payments This package helps you process charges with Vulcan. It currently only supports Stripe, but other payment processors may be supported in the future (PRs welcome!). ## Overview This package does the following things: - Provide a button that triggers the [Stripe Checkout](https://stripe.com/checkout) form. - Once the form is submitted, trigger a GraphQL mutation that will: - Perform the charge. - Create a new Charge. - Modify a document associated with the charge. - The mutation then returns a document associated with the charge to the client. ## Settings Stripe requires the following public setting in your `settings.json`. - `public.stripe.publishableKey`: your publishable Stripe key. As well as the following private setting (can be stored in the setting's root or on `private`): - `stripe.secretKey`: your Stripe secret key. ``` { "public": { "stripe": { "publishableKey": "pk_test_K0rkFDrT0jj4NqG5Dumr3RaU" } } }, "stripe": { "secretKey": "sk_test_sfdhj34jdsfxhjs234sd0K", "createPlans": false }, } ``` ## Charges Charges are stored in the database with the following fields: - `_id`: the charge's id. - `createdAt`: the charge's timestamp. - `userId`: the Vulcan `_id` of the user performing the purchase. - `tokenId`: the charge token's id. - `productKey`: the key corresponding to the product being purchased, as defined with `addProduct`. - `type`: the type of charge (currently only `stripe` is supported). - `test`: whether the operation is a test or not. - `data`: a JSON object containing all charge data generated by the payment processor. - `properties`: a JSON object containing any custom properties passed by the client. - `ip`: the IP address of the client performing the purchase. ## Products A product is a type of purchase a user can make. It has a `name`, `amount` (in cents), `currency`, and `description`. New products are defined using the `addProduct` function, which takes two arguments. The first argument is a unique **product key** used to identify the product. The second argument can be an object (for "static" products like a subscription): ``` import { addProduct } from 'meteor/vulcan:payments'; addProduct('membership', { 'amount': 25000, 'currency': 'USD', 'description': 'Become a paid member.' }); ``` Or it can be a function (for "dynamic" products like in an e-commerce site) that takes the associated document (i.e. the product being sold) as argument and returns an object: ``` import { addProduct } from 'meteor/vulcan:payments'; addProduct('book', book => ({ 'name': book.title, 'amount': book.price, 'currency': 'USD', 'description': book.description })); ``` Make sure you define your products in a location accessible to both client and server, in order to access them both on the front-end to configure Stripe Checkout, and in the back-end to perform the actual charge. ## Checkout Component ```js Complete Payment} /> ``` - `productKey`: The key of the product to buy. - `button`: The button that triggers the Stripe Checkout overlay. - `associatedCollection`: the associated collection. - `associatedDocument`: the associated `document`. - `callback`: a callback function that runs once the charge is successful (takes the `charge` as result argument). - `fragment`: a GraphQL fragment specifying the fields expected in return after the charge. - `fragmentName`: a registeredGraphQL fragment name. - `properties`: any other properties you want to pass on to `createChargeMutation` on the server. ## Associating a Collection Document The Vulcan Charge package requires associating a document with a purchase, typically the item being paid for. For example, maybe you want people to buy access to a file hosted on your servers, and give them download access once the transaction is complete. The `associatedCollection` and `associatedId` props give you an easy way to implement this by automatically setting a `chargeIds` field on the document once the charge succeeds. For example, if you pass `associatedCollection={Jobs}` and `associatedId="foo123"` to the Checkout component, the resulting charge's `_id` will automatically be added to a `chargeIds` array on job `foo123`. The `createChargeMutation` GraphQL mutation will then return that job according to the `fragment` property specified. Note: you will need to make sure that your collection accepts this `chargeIds` field. For example: ```js Jobs.addField([ { fieldName: 'chargeIds', fieldSchema: { type: Array, optional: true, } }, { fieldName: 'chargeIds.$', fieldSchema: { type: String, optional: true, } } ]); ``` #### The "Chargeable" Type In order to be able to return any associated document, the package creates a new `Chargeable` GraphQL type that is an union of every collection's types. ## Post-Charge Updates The best way to update a document based on a successful charge is by using the `collection.charge.sync` callback. Callback functions on this hook will run with a MongoDB `modifier` as the first argument (although note that only `$set` and `$unset` operations are supported here), the `document` associated with the charge as their second argument, and the `charge` object as their third argument. Because the callback is added in a **sync** manner, the final document returned by the `createChargeMutation` mutation will include any new values set by the callback hook. #### Example 1: Setting a job offer as paid ```js import { addCallback } from 'meteor/vulcan:core'; function setToPaidOnCharge (modifier, job, charge) { modifier.$set.status = 'paid'; return modifier; } addCallback('jobs.charge.sync', setToPaidOnCharge); ``` #### Example 2: Adding a user to a group ```js import { addCallback } from 'meteor/vulcan:core'; function makePaidMember (modifier, user, charge) { modifier.$set.groups = [...user.groups, 'paidMembers']; return modifier; } addCallback('users.charge.sync', makePaidMember); ``` #### Example 3: Giving a user access to a specific document We'll pass the `videoId` property to our `Checkout` component (`property={{videoId: video._id}}`) to make it accessible as `charge.properties.videoId` inside the callback: ```js import { addCallback } from 'meteor/vulcan:core'; function giveAccessToVideo (modifier, user, charge) { const videoId = charge.properties.videoId; modifier.$set.accessibleVideos = [...user.accessibleVideos, videoId]; return modifier; } addCallback('users.charge.sync', giveAccessToVideo); ```