forked from microsoft/BotBuilder-Samples
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcheckout.js
More file actions
217 lines (187 loc) · 8.3 KB
/
checkout.js
File metadata and controls
217 lines (187 loc) · 8.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
var _ = require('lodash');
var uuid = require('uuid');
var util = require('util');
var Promise = require('bluebird');
var Stripe = require('stripe')(process.env.PAYMENTS_STRIPE_API_KEY);
var PaymentToken = require('./payment-token');
var payments = require('./payments');
var catalog = require('./services/catalog');
function validateAndCalculateDetails(paymentRequest, shippingAddress, selectedShippingOption) {
// selectedShippingOption may not be set the first time
var productIds = [paymentRequest.id];
// get product items
return Promise
.all(productIds.map(id => catalog.getItemById(id)))
.then(products => {
if (products.length === 0) {
throw new Error('Product not available');
}
// get available shipping methods for address and validate
var shippingMethods = getShippingMethodsForAddress(shippingAddress);
var selectedShippingMethod = shippingMethods.find(o => o.id === selectedShippingOption);
if (!selectedShippingMethod && shippingMethods.length > 0) {
// preselect first option if none is found
selectedShippingMethod = shippingMethods[0];
}
if (selectedShippingMethod) {
selectedShippingMethod.selected = true;
}
// details
var paymentDetails = {
total: {
label: 'Total',
amount: { currency: 'USD', value: '0.00' }
},
displayItems: products.map(asDisplayItem)
.concat([
{
label: 'Shipping',
amount: { currency: 'USD', value: '0.00' },
pending: true
}, {
label: 'Sales Tax',
amount: { currency: 'USD', value: '0.00' },
pending: true
}]),
shippingOptions: shippingMethods
};
// calculate tax, shipping cost and total
recalculateTaxAndTotal(paymentDetails, shippingAddress, selectedShippingMethod);
return _.assign({}, paymentRequest, { details: paymentDetails })
});
}
function processPayment(paymentRequest, paymentResponse) {
try {
// sanity checks
checkParam(paymentRequest, 'paymentRequest');
checkParam(paymentResponse, 'paymentResponse');
if (paymentResponse.methodName !== payments.MicrosoftPayMethodName) {
throw new Error('Payment method is not supported.');
}
checkParam(paymentResponse.details, 'paymentResponse.details');
checkParam(paymentResponse.details.paymentToken, 'paymentResponse.details.paymentToken');
var paymentToken = PaymentToken.parse(paymentResponse.details.paymentToken);
checkParam(paymentToken, 'parsed paymentToken');
checkParam(paymentToken.source, 'Payment token source is empty.');
if (paymentToken.header.format !== PaymentToken.tokenFormat.Stripe) {
throw new Error('Payment token format is not Stripe.');
}
if (paymentToken.header.merchantId !== process.env.PAYMENTS_MERCHANT_ID) {
throw new Error('MerchantId is not supported.');
}
if (paymentToken.header.amount.currency !== paymentRequest.details.total.amount.currency ||
paymentToken.header.amount.value !== paymentRequest.details.total.amount.value) {
throw new Error('Payment token amount currency/amount mismatch.');
}
var paymentRecord = {
orderId: paymentRequest.id,
transactionId: uuid.v1(),
methodName: paymentResponse.methodName,
paymentProcessor: paymentToken.header.Format,
shippingAddress: paymentResponse.shippingAddress,
shippingOption: paymentResponse.shippingOption,
items: paymentRequest.details.displayItems,
total: paymentRequest.details.total,
liveMode: !paymentToken.isEmulated
};
// If the payment token is microsoft emulated do not charge (as it will fail)
if (paymentToken.isEmulated) {
return Promise.resolve(paymentRecord);
} else {
// Charge using Stripe
var chargeOptions = {
amount: Math.floor(parseFloat(paymentRequest.details.total.amount.value) * 100), // Amount in cents
currency: paymentRequest.details.total.amount.currency,
description: paymentRequest.id,
source: paymentToken.source
};
return Stripe.charges
.create(chargeOptions)
.then((charge) => {
console.log('Strige.charge.result', charge);
if (charge.status === 'succeeded' && charge.captured) {
// Charge succeeded, return payment paymentRecord
// Ideally, you should register the transaction using the paymentRecord and charge.id
return paymentRecord;
}
// Other statuses may include processing "pending" or "success" with non captured funds. It is up to the merchant how to handle these cases.
// If payment is captured but not charged this would be considered "unknown" (charge the captured amount after shipping scenario)
// Merchant might choose to handle "pending" and "failed" status or handle "success" status with funds captured null or false
// More information @ https://stripe.com/docs/api#charge_object-captured
throw new Error(util.format('Could not process charge using Stripe with charge.status: %s and charge.captured: %s', change.status, change.captured));
});
}
} catch (err) {
return Promise.reject(err);
}
}
module.exports = {
validateAndCalculateDetails: validateAndCalculateDetails,
processPayment: processPayment
};
// Available Shipping Methods
function getShippingMethodsForAddress(shippingAddress) {
console.log('getAvailableShippingMethods', shippingAddress.country);
if (shippingAddress.country.toLowerCase() === 'zw') {
throw new Error('ZW country not supported');
}
if (shippingAddress.country.toLowerCase() === 'us') {
return [{
id: 'STANDARD',
label: 'Standard - (5-6 Business days)',
amount: { currency: 'USD', value: '0.00' }
}, {
id: 'EXPEDITED',
label: 'Expedited - (2 Business days)',
amount: { currency: 'USD', value: '2.50' }
}];
} else {
return [{
id: 'INTERNATIONAL',
label: 'International',
amount: { currency: 'USD', value: '25.00' }
}];
}
}
function recalculateTaxAndTotal(details, shippingAddress, shippingOption) {
console.log('updateTotalWithShipping', details);
// shipping price
if (shippingOption) {
var shippingItem = details.displayItems.find(i => i.label === 'Shipping');
shippingItem.amount = shippingOption.amount;
shippingItem.pending = false;
}
// tax
var subTotal = details.displayItems
.filter(i => i.label !== 'Sales Tax')
.reduce((a, b) => a + parseFloat(b.amount.value), 0);
var taxItem = details.displayItems.find(i => i.label === 'Sales Tax');
// only for US
var tax = shippingAddress.country.toLowerCase() === 'us' ? subTotal * 0.085 : 0;
taxItem.amount.value = tax.toFixed(2);
taxItem.pending = false;
// new total
details.total.amount.value = details.displayItems
.reduce((a, b) => a + parseFloat(b.amount.value), 0)
.toFixed(2);
}
function asDisplayItem(product) {
return {
label: product.name,
amount: {
currency: product.currency,
value: product.price.toFixed(2)
}
};
}
// helpers
function checkParam(param, name) {
if (param === undefined) {
throw new Error('Mising Parameter: \'' + name + '\'');
}
}
function checkType(param, expectedType, name) {
if (!(param instanceof expectedType)) {
throw new Error('Expected type \'' + expectedType.name + '\' for parameter \'' + name + '\'');
}
}