.. | ||
lib | ||
package.js | ||
README.md |
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 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 withaddProduct
.type
: the type of charge (currently onlystripe
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
<Components.Checkout
productKey="jobPosting"
associatedCollection={Jobs}
associatedId={job._id}
callback={setToPaid}
button={<Button className="buy-job-button" variant="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 associateddocument
.callback
: a callback function that runs once the charge is successful (takes thecharge
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 tocreateChargeMutation
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:
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
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
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:
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);