Adding subscriptions (#95)
* Adding subscriptions * Adding in ability to use webhook for subscriptions * Add docs and ability to create subscriptions * Fixed populating value * Adding subscription tests * Language updatemaster
parent
c92194dd56
commit
a87d2fbf0a
|
@ -118,6 +118,13 @@ Note: An `Options` value is not required when `Type` is set to `Checkbox`.
|
|||
|
||||
Tags are used when indexing the products for search. It's advised to set tags (keywords) so that customers can easily find the products they are searching for.
|
||||
|
||||
## Subscriptions (Stripe only)
|
||||
|
||||
You are able to setup product subscriptions through Stripe. First setup the `Plan` in the [Stripe dashboard](https://dashboard.stripe.com/) then enter the Plan ID (Formatted: plan_XXXXXXXXXXXXXX) when creating or editing a product. When purchasing, a customer can only add a single subscription to their cart at one time. Subscriptions cannot be combined with other products in their cart. On Checkout/Payment the customer and subscription is created in Stripe and the billing cycle commences based on the plan setup.
|
||||
|
||||
##### Subscription Webhooks (Stripe only)
|
||||
You are able to configure a Webhook in Stripe to receive subscription updates on successful/failed payments [here](https://dashboard.stripe.com/webhooks). The `expressCart` Webhook endpoint should be set to: `https://<example.com>/stripe/subscription_update`. You will need to set the `Events to send` value to both: `invoice.payment_failed` and `invoice.payment_succeeded`.
|
||||
|
||||
## Database
|
||||
|
||||
`expressCart` uses a MongoDB for storing all the data. Setting of the database connection string is done through the `/config/settings.json` file. There are two properties relating to the database connection:
|
||||
|
|
|
@ -69,6 +69,17 @@
|
|||
"productTags" : "shirt",
|
||||
"productOptions" : "{\"Size\":{\"optName\":\"Size\",\"optLabel\":\"Select size\",\"optType\":\"select\",\"optOptions\":[\"S\",\"M\",\"L\"]}}",
|
||||
"productStock": 10
|
||||
},
|
||||
{
|
||||
"productPermalink" : "gym-membership",
|
||||
"productTitle" : "Gym membership",
|
||||
"productPrice" : "15",
|
||||
"productDescription" : "This a monthly recurring Gym membership subscription.",
|
||||
"productPublished": true,
|
||||
"productTags" : "subscription",
|
||||
"productOptions" : "",
|
||||
"productStock": null,
|
||||
"productSubscription": "plan_XXXXXXXXXXXXXX"
|
||||
}
|
||||
],
|
||||
"customers": [
|
||||
|
|
|
@ -131,6 +131,22 @@ const updateTotalCartAmount = (req, res) => {
|
|||
}
|
||||
};
|
||||
|
||||
const updateSubscriptionCheck = (req, res) => {
|
||||
// If cart is empty
|
||||
if(!req.session.cart || req.session.cart.length === 0){
|
||||
req.session.cartSubscription = null;
|
||||
return;
|
||||
}
|
||||
|
||||
req.session.cart.forEach((item) => {
|
||||
if(item.productSubscription){
|
||||
req.session.cartSubscription = item.productSubscription;
|
||||
}else{
|
||||
req.session.cartSubscription = null;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const checkDirectorySync = (directory) => {
|
||||
try{
|
||||
fs.statSync(directory);
|
||||
|
@ -551,6 +567,7 @@ module.exports = {
|
|||
addSitemapProducts,
|
||||
clearSessionValue,
|
||||
updateTotalCartAmount,
|
||||
updateSubscriptionCheck,
|
||||
checkDirectorySync,
|
||||
getThemes,
|
||||
getImages,
|
||||
|
|
|
@ -160,5 +160,6 @@
|
|||
"Cart contents": "Cart contents",
|
||||
"Shipping": "Shipping:",
|
||||
"Empty cart": "Empty cart",
|
||||
"List": "List"
|
||||
"List": "List",
|
||||
"Order type": "Order type"
|
||||
}
|
|
@ -289,6 +289,9 @@ $(document).ready(function (){
|
|||
image: $('#stripeButton').data('image'),
|
||||
locale: 'auto',
|
||||
token: function(token){
|
||||
if($('#stripeButton').data('subscription')){
|
||||
$('#shipping-form').append('<input type="hidden" name="stripePlan" value="' + $('#stripeButton').data('subscription') + '" />');
|
||||
}
|
||||
$('#shipping-form').append('<input type="hidden" name="stripeToken" value="' + token.id + '" />');
|
||||
$('#shipping-form').submit();
|
||||
}
|
||||
|
@ -301,7 +304,8 @@ $(document).ready(function (){
|
|||
description: $('#stripeButton').data('description'),
|
||||
zipCode: $('#stripeButton').data('zipCode'),
|
||||
amount: $('#stripeButton').data('amount'),
|
||||
currency: $('#stripeButton').data('currency')
|
||||
currency: $('#stripeButton').data('currency'),
|
||||
subscription: $('#stripeButton').data('subscription')
|
||||
});
|
||||
}
|
||||
});
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -13,6 +13,7 @@ const {
|
|||
getPaymentConfig,
|
||||
getImages,
|
||||
updateTotalCartAmount,
|
||||
updateSubscriptionCheck,
|
||||
getData,
|
||||
addSitemapProducts
|
||||
} = require('../lib/common');
|
||||
|
@ -68,6 +69,10 @@ router.get('/payment/:orderId', async (req, res, next) => {
|
|||
});
|
||||
});
|
||||
|
||||
router.get('/emptycart', async (req, res, next) => {
|
||||
emptyCart(req, res, '');
|
||||
});
|
||||
|
||||
router.get('/checkout', async (req, res, next) => {
|
||||
const config = req.app.config;
|
||||
|
||||
|
@ -105,6 +110,11 @@ router.get('/pay', async (req, res, next) => {
|
|||
return;
|
||||
}
|
||||
|
||||
let paymentType = '';
|
||||
if(req.session.cartSubscription){
|
||||
paymentType = '_subscription';
|
||||
}
|
||||
|
||||
// render the payment page
|
||||
res.render(`${config.themeViews}pay`, {
|
||||
title: 'Pay',
|
||||
|
@ -113,6 +123,7 @@ router.get('/pay', async (req, res, next) => {
|
|||
pageCloseBtn: showCartCloseBtn('pay'),
|
||||
session: req.session,
|
||||
paymentPage: true,
|
||||
paymentType,
|
||||
page: 'pay',
|
||||
message: clearSessionValue(req.session, 'message'),
|
||||
messageType: clearSessionValue(req.session, 'messageType'),
|
||||
|
@ -220,6 +231,9 @@ router.post('/product/updatecart', (req, res, next) => {
|
|||
// update total cart amount
|
||||
updateTotalCartAmount(req, res);
|
||||
|
||||
// Update checking cart for subscription
|
||||
updateSubscriptionCheck(req, res);
|
||||
|
||||
// Update cart to the DB
|
||||
await db.cart.updateOne({ sessionId: req.session.id }, {
|
||||
$set: { cart: req.session.cart }
|
||||
|
@ -239,36 +253,42 @@ router.post('/product/updatecart', (req, res, next) => {
|
|||
});
|
||||
|
||||
// Remove single product from cart
|
||||
router.post('/product/removefromcart', (req, res, next) => {
|
||||
router.post('/product/removefromcart', async (req, res, next) => {
|
||||
const db = req.app.db;
|
||||
let itemRemoved = false;
|
||||
|
||||
// remove item from cart
|
||||
async.each(req.session.cart, (item, callback) => {
|
||||
req.session.cart.forEach((item) => {
|
||||
if(item){
|
||||
if(item.productId === req.body.cartId){
|
||||
itemRemoved = true;
|
||||
req.session.cart = _.pull(req.session.cart, item);
|
||||
}
|
||||
}
|
||||
callback();
|
||||
}, async () => {
|
||||
// Update cart in DB
|
||||
await db.cart.updateOne({ sessionId: req.session.id }, {
|
||||
$set: { cart: req.session.cart }
|
||||
});
|
||||
// update total cart amount
|
||||
updateTotalCartAmount(req, res);
|
||||
|
||||
if(itemRemoved === false){
|
||||
return res.status(400).json({ message: 'Product not found in cart' });
|
||||
}
|
||||
return res.status(200).json({ message: 'Product successfully removed', totalCartItems: Object.keys(req.session.cart).length });
|
||||
});
|
||||
|
||||
// Update cart in DB
|
||||
await db.cart.updateOne({ sessionId: req.session.id }, {
|
||||
$set: { cart: req.session.cart }
|
||||
});
|
||||
// update total cart amount
|
||||
updateTotalCartAmount(req, res);
|
||||
|
||||
// Update checking cart for subscription
|
||||
updateSubscriptionCheck(req, res);
|
||||
|
||||
if(itemRemoved === false){
|
||||
return res.status(400).json({ message: 'Product not found in cart' });
|
||||
}
|
||||
return res.status(200).json({ message: 'Product successfully removed', totalCartItems: Object.keys(req.session.cart).length });
|
||||
});
|
||||
|
||||
// Totally empty the cart
|
||||
router.post('/product/emptycart', async (req, res, next) => {
|
||||
emptyCart(req, res, 'json');
|
||||
});
|
||||
|
||||
const emptyCart = async (req, res, type) => {
|
||||
const db = req.app.db;
|
||||
|
||||
// Remove from session
|
||||
|
@ -280,8 +300,20 @@ router.post('/product/emptycart', async (req, res, next) => {
|
|||
|
||||
// update total cart amount
|
||||
updateTotalCartAmount(req, res);
|
||||
res.status(200).json({ message: 'Cart successfully emptied', totalCartItems: 0 });
|
||||
});
|
||||
|
||||
// Update checking cart for subscription
|
||||
updateSubscriptionCheck(req, res);
|
||||
|
||||
// If POST, return JSON else redirect nome
|
||||
if(type === 'json'){
|
||||
res.status(200).json({ message: 'Cart successfully emptied', totalCartItems: 0 });
|
||||
return;
|
||||
}
|
||||
|
||||
req.session.message = 'Cart successfully emptied.';
|
||||
req.session.messageType = 'success';
|
||||
res.redirect('/');
|
||||
};
|
||||
|
||||
// Add item to cart
|
||||
router.post('/product/addtocart', async (req, res, next) => {
|
||||
|
@ -300,13 +332,25 @@ router.post('/product/addtocart', async (req, res, next) => {
|
|||
req.session.cart = [];
|
||||
}
|
||||
|
||||
// Get the item from the DB
|
||||
// Get the product from the DB
|
||||
const product = await db.products.findOne({ _id: getId(req.body.productId) });
|
||||
// No product found
|
||||
if(!product){
|
||||
return res.status(400).json({ message: 'Error updating cart. Please try again.' });
|
||||
}
|
||||
|
||||
// If cart already has a subscription you cannot add anything else
|
||||
if(req.session.cartSubscription){
|
||||
return res.status(400).json({ message: 'Subscription already existing in cart. You cannot add more.' });
|
||||
}
|
||||
|
||||
// If existing cart isn't empty check if product is a subscription
|
||||
if(req.session.cart.length !== 0){
|
||||
if(product.productSubscription){
|
||||
return res.status(400).json({ message: 'You cannot combine scubscription products with existing in your cart. Empty your cart and try again.' });
|
||||
}
|
||||
}
|
||||
|
||||
// If stock management on check there is sufficient stock for this product
|
||||
if(config.trackStock && product.productStock){
|
||||
const stockHeld = await db.cart.aggregate(
|
||||
|
@ -346,7 +390,13 @@ router.post('/product/addtocart', async (req, res, next) => {
|
|||
// Doc used to test if existing in the cart with the options. If not found, we add new.
|
||||
let options = {};
|
||||
if(req.body.productOptions){
|
||||
options = JSON.parse(req.body.productOptions);
|
||||
try{
|
||||
if(typeof req.body.productOptions === 'object'){
|
||||
options = req.body.productOptions;
|
||||
}else{
|
||||
options = JSON.parse(req.body.productOptions);
|
||||
}
|
||||
}catch(ex){}
|
||||
}
|
||||
const findDoc = {
|
||||
productId: req.body.productId,
|
||||
|
@ -376,6 +426,7 @@ router.post('/product/addtocart', async (req, res, next) => {
|
|||
productObj.options = options;
|
||||
productObj.productImage = product.productImage;
|
||||
productObj.productComment = productComment;
|
||||
productObj.productSubscription = product.productSubscription;
|
||||
if(product.productPermalink){
|
||||
productObj.link = product.productPermalink;
|
||||
}else{
|
||||
|
@ -394,6 +445,13 @@ router.post('/product/addtocart', async (req, res, next) => {
|
|||
// update total cart amount
|
||||
updateTotalCartAmount(req, res);
|
||||
|
||||
// Update checking cart for subscription
|
||||
updateSubscriptionCheck(req, res);
|
||||
|
||||
if(product.productSubscription){
|
||||
req.session.cartSubscription = product.productSubscription;
|
||||
}
|
||||
|
||||
// update how many products in the shopping cart
|
||||
req.session.cartTotalItems = req.session.cart.reduce((a, b) => +a + +b.quantity, 0);
|
||||
return res.status(200).json({ message: 'Cart successfully updated', totalCartItems: req.session.cartTotalItems });
|
||||
|
|
|
@ -52,7 +52,8 @@ router.post('/checkout_action', (req, res, next) => {
|
|||
orderComment: req.body.orderComment,
|
||||
orderStatus: paymentStatus,
|
||||
orderDate: new Date(),
|
||||
orderProducts: req.session.cart
|
||||
orderProducts: req.session.cart,
|
||||
orderType: 'Single'
|
||||
};
|
||||
|
||||
// insert order into DB
|
||||
|
@ -111,4 +112,149 @@ router.post('/checkout_action', (req, res, next) => {
|
|||
});
|
||||
});
|
||||
|
||||
// Subscription hook from Stripe
|
||||
router.all('/subscription_update', async (req, res, next) => {
|
||||
const db = req.app.db;
|
||||
|
||||
if(!req.body.data.object.customer){
|
||||
return res.status(400).json({ message: 'Customer not found' });
|
||||
}
|
||||
|
||||
const order = await db.orders.findOne({
|
||||
orderCustomer: req.body.data.object.customer,
|
||||
orderType: 'Subscription'
|
||||
});
|
||||
|
||||
if(!order){
|
||||
return res.status(400).json({ message: 'Order not found' });
|
||||
}
|
||||
|
||||
let orderStatus = 'Paid';
|
||||
if(req.body.type === 'invoice.payment_failed'){
|
||||
orderStatus = 'Declined';
|
||||
}
|
||||
|
||||
// Update order status
|
||||
await db.orders.updateOne({
|
||||
_id: common.getId(order._id),
|
||||
orderType: 'Subscription'
|
||||
}, {
|
||||
$set: {
|
||||
orderStatus: orderStatus
|
||||
}
|
||||
});
|
||||
|
||||
return res.status(200).json({ message: 'Status successfully updated' });
|
||||
});
|
||||
|
||||
router.post('/checkout_action_subscription', async (req, res, next) => {
|
||||
const db = req.app.db;
|
||||
const config = req.app.config;
|
||||
|
||||
try{
|
||||
const plan = await stripe.plans.retrieve(req.body.stripePlan);
|
||||
if(!plan){
|
||||
req.session.messageType = 'danger';
|
||||
req.session.message = 'The plan connected to this product doesn\'t exist';
|
||||
res.redirect('/pay/');
|
||||
return;
|
||||
}
|
||||
}catch(ex){
|
||||
req.session.messageType = 'danger';
|
||||
req.session.message = 'The plan connected to this product doesn\'t exist';
|
||||
res.redirect('/pay/');
|
||||
return;
|
||||
}
|
||||
|
||||
// Create customer
|
||||
const customer = await stripe.customers.create({
|
||||
source: req.body.stripeToken,
|
||||
plan: req.body.stripePlan,
|
||||
email: req.body.shipEmail,
|
||||
name: `${req.body.shipFirstname} ${req.body.shipLastname}`,
|
||||
phone: req.body.shipPhoneNumber
|
||||
});
|
||||
|
||||
if(!customer){
|
||||
req.session.messageType = 'danger';
|
||||
req.session.message = 'Your subscripton has declined. Please try again';
|
||||
req.session.paymentApproved = false;
|
||||
req.session.paymentDetails = '';
|
||||
res.redirect('/pay');
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for a subscription
|
||||
if(customer.subscriptions.data && customer.subscriptions.data.length === 0){
|
||||
req.session.messageType = 'danger';
|
||||
req.session.message = 'Your subscripton has declined. Please try again';
|
||||
req.session.paymentApproved = false;
|
||||
req.session.paymentDetails = '';
|
||||
res.redirect('/pay');
|
||||
return;
|
||||
}
|
||||
|
||||
const subscription = customer.subscriptions.data[0];
|
||||
|
||||
// Create the new order document
|
||||
const orderDoc = {
|
||||
orderPaymentId: subscription.id,
|
||||
orderPaymentGateway: 'Stripe',
|
||||
orderPaymentMessage: subscription.collection_method,
|
||||
orderTotal: req.session.totalCartAmount,
|
||||
orderEmail: req.body.shipEmail,
|
||||
orderFirstname: req.body.shipFirstname,
|
||||
orderLastname: req.body.shipLastname,
|
||||
orderAddr1: req.body.shipAddr1,
|
||||
orderAddr2: req.body.shipAddr2,
|
||||
orderCountry: req.body.shipCountry,
|
||||
orderState: req.body.shipState,
|
||||
orderPostcode: req.body.shipPostcode,
|
||||
orderPhoneNumber: req.body.shipPhoneNumber,
|
||||
orderComment: req.body.orderComment,
|
||||
orderStatus: 'Pending',
|
||||
orderDate: new Date(),
|
||||
orderProducts: req.session.cart,
|
||||
orderType: 'Subscription',
|
||||
orderCustomer: customer.id
|
||||
};
|
||||
|
||||
// insert order into DB
|
||||
const order = await db.orders.insertOne(orderDoc);
|
||||
const orderId = order.insertedId;
|
||||
|
||||
indexOrders(req.app)
|
||||
.then(() => {
|
||||
// set the results
|
||||
req.session.messageType = 'success';
|
||||
req.session.message = 'Your subscription was successfully created';
|
||||
req.session.paymentEmailAddr = req.body.shipEmail;
|
||||
req.session.paymentApproved = true;
|
||||
req.session.paymentDetails = '<p><strong>Order ID: </strong>' + orderId + '</p><p><strong>Subscription ID: </strong>' + subscription.id + '</p>';
|
||||
|
||||
// set payment results for email
|
||||
const paymentResults = {
|
||||
message: req.session.message,
|
||||
messageType: req.session.messageType,
|
||||
paymentEmailAddr: req.session.paymentEmailAddr,
|
||||
paymentApproved: true,
|
||||
paymentDetails: req.session.paymentDetails
|
||||
};
|
||||
|
||||
// clear the cart
|
||||
if(req.session.cart){
|
||||
req.session.cartSubscription = null;
|
||||
req.session.cart = null;
|
||||
req.session.orderId = null;
|
||||
req.session.totalCartAmount = 0;
|
||||
}
|
||||
|
||||
// send the email with the response
|
||||
common.sendEmail(req.session.paymentEmailAddr, 'Your payment with ' + config.cartTitle, common.getEmailTemplate(paymentResults));
|
||||
|
||||
// redirect to outcome
|
||||
res.redirect('/payment/' + orderId);
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
|
|
47
test/test.js
47
test/test.js
|
@ -141,6 +141,41 @@ test.serial('[Success] Customer login with correct email', async t => {
|
|||
t.deepEqual(res.body.message, 'Successfully logged in');
|
||||
});
|
||||
|
||||
test.serial('[Success] Add subscripton product to cart', async t => {
|
||||
const res = await request
|
||||
.post('/product/addtocart')
|
||||
.send({
|
||||
productId: products[7]._id,
|
||||
productQuantity: 1,
|
||||
productOptions: {}
|
||||
})
|
||||
.expect(200);
|
||||
const sessions = await db.cart.find({}).toArray();
|
||||
if(!sessions || sessions.length === 0){
|
||||
t.fail();
|
||||
}
|
||||
t.deepEqual(res.body.message, 'Cart successfully updated');
|
||||
});
|
||||
|
||||
test.serial('[Fail] Add product to cart when subscription already added', async t => {
|
||||
const res = await request
|
||||
.post('/product/addtocart')
|
||||
.send({
|
||||
productId: products[1]._id,
|
||||
productQuantity: 100,
|
||||
productOptions: JSON.stringify(products[1].productOptions)
|
||||
})
|
||||
.expect(400);
|
||||
t.deepEqual(res.body.message, 'Subscription already existing in cart. You cannot add more.');
|
||||
});
|
||||
|
||||
test.serial('[Success] Empty cart', async t => {
|
||||
const res = await request
|
||||
.post('/product/emptycart')
|
||||
.expect(200);
|
||||
t.deepEqual(res.body.message, 'Cart successfully emptied');
|
||||
});
|
||||
|
||||
test.serial('[Success] Add product to cart', async t => {
|
||||
const res = await request
|
||||
.post('/product/addtocart')
|
||||
|
@ -157,6 +192,18 @@ test.serial('[Success] Add product to cart', async t => {
|
|||
t.deepEqual(res.body.message, 'Cart successfully updated');
|
||||
});
|
||||
|
||||
test.serial('[Fail] Cannot add subscripton when other product in cart', async t => {
|
||||
const res = await request
|
||||
.post('/product/addtocart')
|
||||
.send({
|
||||
productId: products[7]._id,
|
||||
productQuantity: 1,
|
||||
productOptions: {}
|
||||
})
|
||||
.expect(400);
|
||||
t.deepEqual(res.body.message, 'You cannot combine scubscription products with existing in your cart. Empty your cart and try again.');
|
||||
});
|
||||
|
||||
test.serial('[Fail] Add product to cart with not enough stock', async t => {
|
||||
const res = await request
|
||||
.post('/product/addtocart')
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
<div class="pull-right col-md-2">
|
||||
<select class="form-control input-sm" id="orderStatus">
|
||||
<option>{{ @root.__ "Completed" }}</option>
|
||||
<option>{{ @root.__ "Paid" }}</option>
|
||||
<option>{{ @root.__ "Pending" }}</option>
|
||||
<option>{{ @root.__ "Cancelled" }}</option>
|
||||
<option>{{ @root.__ "Declined" }}</option>
|
||||
|
@ -37,6 +38,7 @@
|
|||
<li class="list-group-item"><strong> {{ @root.__ "State" }}: </strong><span class="pull-right">{{result.orderState}}</span></li>
|
||||
<li class="list-group-item"><strong> {{ @root.__ "Postcode" }}: </strong><span class="pull-right">{{result.orderPostcode}}</span></li>
|
||||
<li class="list-group-item"><strong> {{ @root.__ "Phone number" }}: </strong><span class="pull-right">{{result.orderPhoneNumber}}</span></li>
|
||||
<li class="list-group-item"><strong> {{ @root.__ "Order type" }}: </strong><span class="pull-right">{{result.orderType}}</span></li>
|
||||
<li class="list-group-item"><strong> {{ @root.__ "Order comment" }}: </strong><span class="pull-right">{{result.orderComment}}</span></li>
|
||||
|
||||
<li class="list-group-item"> </li>
|
||||
|
@ -57,7 +59,7 @@
|
|||
{{/each}}
|
||||
)
|
||||
{{/if}}
|
||||
<div class="pull-right">{{currencySymbol config.currencySymbol}}{{formatAmount this.totalItemPrice}}</div>
|
||||
<div class="pull-right">{{currencySymbol @root.config.currencySymbol}}{{formatAmount this.totalItemPrice}}</div>
|
||||
{{#if productComment}}
|
||||
<h4><span class="text-danger">Comment:</span> {{this.productComment}}</h4>
|
||||
{{/if}}
|
||||
|
|
|
@ -12,6 +12,9 @@
|
|||
data-description="{{@root.config.cartTitle}} Payment"
|
||||
data-image="{{@root.paymentConfig.stripeLogoURL}}"
|
||||
data-email="{{@root.session.customer.email}}"
|
||||
{{#if @root.session.cartSubscription}}
|
||||
data-subscription="{{@root.session.cartSubscription}}"
|
||||
{{/if}}
|
||||
data-locale="auto"
|
||||
data-zip-code="false"
|
||||
data-currency="{{@root.paymentConfig.stripeCurrency}}">
|
||||
|
|
|
@ -107,6 +107,15 @@
|
|||
<p class="help-block">{{ @root.__ "Here you can set options for your product. Eg: Size, color, style" }}</p>
|
||||
</div>
|
||||
</div>
|
||||
{{#ifCond config.paymentGateway '==' 'stripe'}}
|
||||
<div class="form-group">
|
||||
<label class="col-sm-2 control-label">Subscription plan</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="text" class="form-control" name="productSubscription" id="productSubscription" placeholder="plan_XXXXXXXXXXXXXX" value={{@root.result.productSubscription}}>
|
||||
<p class="help-block">First setup the plan in <strong>Stripe</strong> dashboard and enter the Plan ID. Format: plan_XXXXXXXXXXXXXX</p>
|
||||
</div>
|
||||
</div>
|
||||
{{/ifCond}}
|
||||
<div class="form-group">
|
||||
<label for="productComment" class="col-sm-2 control-label">{{ @root.__ "Allow comment" }}</label>
|
||||
<div class="col-sm-10">
|
||||
|
|
|
@ -106,6 +106,15 @@
|
|||
<p class="help-block">{{ @root.__ "Here you can set options for your product. Eg: Size, color, style" }}</p>
|
||||
</div>
|
||||
</div>
|
||||
{{#ifCond config.paymentGateway '==' 'stripe'}}
|
||||
<div class="form-group">
|
||||
<label class="col-sm-2 control-label">Subscription plan</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="text" class="form-control" name="productSubscription" id="productSubscription" placeholder="plan_XXXXXXXXXXXXXX">
|
||||
<p class="help-block">First setup the plan in <strong>Stripe</strong> dashboard and enter the Plan ID. Format: plan_XXXXXXXXXXXXXX</p>
|
||||
</div>
|
||||
</div>
|
||||
{{/ifCond}}
|
||||
<div class="form-group">
|
||||
<label for="productComment" class="col-sm-2 control-label">{{ @root.__ "Allow comment" }}</label>
|
||||
<div class="col-sm-10">
|
||||
|
|
|
@ -36,7 +36,7 @@
|
|||
<button id="customerLogout" class="btn btn-sm btn-success pull-right">{{ @root.__ "Change customer" }}</button>
|
||||
</div>
|
||||
{{/if}}
|
||||
<form id="shipping-form" class="shipping-form" action="/{{config.paymentGateway}}/checkout_action" method="post" role="form" data-toggle="validator" novalidate="false">
|
||||
<form id="shipping-form" class="shipping-form" action="/{{config.paymentGateway}}/checkout_action{{@root.paymentType}}" method="post" role="form" data-toggle="validator" novalidate="false">
|
||||
{{> partials/payments/shipping-form}}
|
||||
{{#if session.customer}}
|
||||
{{#ifCond config.paymentGateway '==' 'paypal'}}
|
||||
|
|
|
@ -28,7 +28,7 @@
|
|||
<button id="customerLogout" class="btn waves-effect waves-light blue darken-3 pull-right">{{ @root.__ "Change customer" }}</button>
|
||||
</div>
|
||||
{{/if}}
|
||||
<form id="shipping-form" class="shipping-form" action="/{{config.paymentGateway}}/checkout_action" method="post" role="form" data-toggle="validator" novalidate="false">
|
||||
<form id="shipping-form" class="shipping-form" action="/{{config.paymentGateway}}/checkout_action{{@root.paymentType}}" method="post" role="form" data-toggle="validator" novalidate="false">
|
||||
{{> themes/Material/shipping-form}}
|
||||
{{#if session.customer}}
|
||||
{{#ifCond config.paymentGateway '==' 'paypal'}}
|
||||
|
|
|
@ -28,7 +28,7 @@
|
|||
<button id="customerLogout" class="btn waves-effect waves-light black pull-right">{{ @root.__ "Change customer" }}</button>
|
||||
</div>
|
||||
{{/if}}
|
||||
<form id="shipping-form" class="shipping-form" action="/{{config.paymentGateway}}/checkout_action" method="post" role="form" data-toggle="validator" novalidate="false">
|
||||
<form id="shipping-form" class="shipping-form" action="/{{config.paymentGateway}}/checkout_action{{@root.paymentType}}" method="post" role="form" data-toggle="validator" novalidate="false">
|
||||
{{> themes/Mono/shipping-form}}
|
||||
{{#if session.customer}}
|
||||
{{#ifCond config.paymentGateway '==' 'paypal'}}
|
||||
|
|
Loading…
Reference in New Issue