mirror of
https://github.com/vale981/Vulcan
synced 2025-03-06 10:01:40 -05:00
192 lines
6.6 KiB
Markdown
192 lines
6.6 KiB
Markdown
# 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
|
|
<Components.Checkout
|
|
productKey="jobPosting"
|
|
associatedCollection={Jobs}
|
|
associatedId={job._id}
|
|
callback={setToPaid}
|
|
button={<Button className="buy-job-button" bsStyle="primary">Complete Payment</Button>}
|
|
/>
|
|
```
|
|
|
|
- `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);
|
|
```
|
|
|