Skip to content

Commit b7e4974

Browse files
committed
CDP: include vat and other amount details with payment intent and in post payment finalization #43
1 parent c67c25d commit b7e4974

File tree

5 files changed

+128
-42
lines changed

5 files changed

+128
-42
lines changed

src/cdp/handleCalcCDPPaymentAmount.js

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import { TYPES } from '../utils/validator'
2+
import { dbCompanies } from './couchdb'
3+
import { defs, messages } from './validation'
4+
5+
export async function calcCDPPaymentAmount(company = {}) {
6+
const {
7+
countryCode = '',
8+
regAddress: {
9+
county,
10+
country,
11+
} = {}
12+
} = company
13+
14+
let amount,
15+
amountVAT,
16+
currency,
17+
amountTotal,
18+
vatPercentage,
19+
vatRegion
20+
switch (`${countryCode || ''}`.toLowerCase()) {
21+
case 'gb':
22+
amount = 8250
23+
currency = 'gbp'
24+
vatPercentage = 20 // 20%
25+
vatRegion = [countryCode] // for state/county based vat use [countryCode, state]
26+
break
27+
default:
28+
throw new Error('Failed to set payment amount. Invalid or unsupported country!')
29+
}
30+
31+
amountVAT = parseInt(amount * vatPercentage / 100)
32+
amountTotal = parseInt(amount + amountVAT)
33+
34+
return {
35+
amount,
36+
amountVAT,
37+
currency,
38+
amountTotal,
39+
vatPercentage, // 0 to 100
40+
vatRegion,
41+
}
42+
}
43+
44+
export default async function handleCalcCDPPaymentAmount(companyId, callback) {
45+
const company = await dbCompanies.get(companyId)
46+
if (!company) return callback(messages.invalidCompany)
47+
48+
const result = await calcCDPPaymentAmount(company)
49+
callback(null, result)
50+
}
51+
handleCalcCDPPaymentAmount.params = [
52+
defs.companyId,
53+
defs.callback,
54+
]
55+
handleCalcCDPPaymentAmount.result = {
56+
properties: [
57+
{
58+
description: 'Amount before VAT in pennies/cents',
59+
name: 'amount',
60+
type: TYPES.integer,
61+
},
62+
{
63+
description: 'Amount of VAT in pennies/cents',
64+
name: 'amountVAT',
65+
type: TYPES.integer,
66+
},
67+
{
68+
description: 'Payment currency',
69+
name: 'currency',
70+
type: TYPES.string,
71+
},
72+
{
73+
description: 'Amount after VAT in pennies/cents. The final/payment amount.',
74+
name: 'amountTotal',
75+
type: TYPES.integer,
76+
},
77+
{
78+
description: 'VAT percentage.',
79+
max: 100,
80+
min: 0,
81+
name: 'vatPercentage',
82+
type: TYPES.integer,
83+
},
84+
{
85+
description: 'The country/region VAT percentage is based on. If VAT is applied on the country-level, state/county will not be included. Eg: ["GB"], ["US", "New York"]',
86+
maxLength: 2,
87+
minLength: 1,
88+
name: 'vatRegion',
89+
type: TYPES.array,
90+
},
91+
92+
],
93+
type: TYPES.object,
94+
}

src/cdp/handleFinalizePayment.js

Lines changed: 7 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ export default async function handleFinalizePayment(
6969
companyId,
7070
callback
7171
) {
72+
console.log('finalize')
7273
const [paid, intentLog] = await stripeCheckPaid(intentId, companyId)
7374
if (!paid) return callback(texts.paymentIncomplete)
7475
const {
@@ -242,37 +243,18 @@ export default async function handleFinalizePayment(
242243
ubos,
243244
relatedCompanies,
244245
contactDetails,
245-
// ToDo: validate
246-
payment: objClean({
247-
...draft.payment?.values || {},
248-
amount: intentLog.amount,
249-
currency: intentLog.currency,
246+
payment: {
247+
amountDetails: intentLog?.amountDetails || {},
248+
billingDetails: intentLog?.stipping,
250249
invoiceNumber: generateInvoiceNumber(
251250
company.countryCode,
252251
cdp,
253252
cdpIssueCount
254253
),
254+
paymentId: intentLog._id,
255+
paymentProvider: 'stripe',
255256
status: 'paid',
256-
vat: 0 // in pennies
257-
}, [
258-
'amount',
259-
'addressLine1',
260-
'addressLine2',
261-
'addressLine3',
262-
'countryCode',
263-
'county',
264-
'currency',
265-
'email',
266-
'invoiceNumber',
267-
'nameOnCard',
268-
'paymentIntentId',
269-
'postCode',
270-
'postTown',
271-
'standardCountry',
272-
'status',
273-
'town',
274-
'vat',
275-
]),
257+
}
276258
}
277259

278260
try {

src/cdp/index.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
import { setup as setupCouchDB } from './couchdb'
2+
import handleCalcCDPPaymentAmount from './handleCalcCDPPaymentAmount'
23
import handleCalcValidityPeriod from './handleCalcValidityPeriod'
34
import handleCheckCreate from './handleCheckCreate'
45
import handleCompanySearch from './handleCompanySearch'
5-
import handleSetAccessCode from './handleSetAccessCode'
66
import { handleDraft } from './handleDraft'
77
import handleFinalizePayment from './handleFinalizePayment'
88
import handleGetPublicKeys from './handleGetPublicKeys'
99
import handleLogProgress from './handleLogProgress'
1010
import handleReport from './handleReport'
11+
import handleSetAccessCode from './handleSetAccessCode'
1112
import handleValidateAccessCode from './handleValidateAccessCode'
1213
import handleVerify from './handleVerify'
1314
import { setup as setupNacl } from './nacl'
@@ -27,6 +28,7 @@ export const setup = async (expressApp) => {
2728
}
2829

2930
const handlers = {
31+
'cdp-calc-cdp-payment-amount': handleCalcCDPPaymentAmount,
3032
'cdp-calc-validity-period': handleCalcValidityPeriod,
3133
'cdp-check-create': handleCheckCreate,
3234
'cdp-company-search': handleCompanySearch,

src/cdp/stripe/handleCheckPaid.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,15 @@ export const checkPaid = async (intentId, companyId) => {
2121

2222
const intentLog = await dbCdpStripeIntents.get(intentId)
2323
const {
24-
amount,
24+
amountDetails: {
25+
amountTotal
26+
} = {},
2527
metadata: md2 = {},
2628
} = intentLog || {}
2729

2830
if (!intentLog || md2?.companyId !== companyId) return [false]
2931

30-
const paymentValid = amount_received === amount
32+
const paymentValid = amount_received === amountTotal
3133
&& Object // check all metatadata matches
3234
.keys(md2)
3335
.every(key => md1[key] === md2[key])

src/cdp/stripe/handleCreateIntent.js

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ import { defs, messages } from '../validation'
1717
import verifyGeneratedData from '../verifyGeneratedData'
1818
import { accessCodeHashed } from '../utils'
1919
import getStripe from './getStripe'
20+
import { calcCDPPaymentAmount } from '../handleCalcCDPPaymentAmount'
21+
import { checkPaid } from './handleCheckPaid'
2022

2123
export const AMOUNT_GBP_PENNIES = 9900 // 99 Pounds Sterling in Pennies
2224
const CURRENCY = 'gbp'
@@ -31,8 +33,8 @@ const stripeAddressDef = {
3133
},
3234
{
3335
description: 'Two-letter country code (ISO 3166-1 alpha-2). Mode details: https://stripe.com/docs/api/payment_methods/object#payment_method_object-billing_details-address-country',
34-
maxLength: 2,
35-
minLength: 2,
36+
// maxLength: 2,
37+
// minLength: 2,
3638
name: 'country',
3739
required: true,
3840
type: TYPES.string,
@@ -138,8 +140,11 @@ export default async function handleCreateIntent(
138140
.map(x => x.name),
139141
)
140142
generatedData.serverIdentity = getIdentity()
141-
const amount = AMOUNT_GBP_PENNIES
142-
const currency = CURRENCY
143+
const amountDetails = await calcCDPPaymentAmount(company)
144+
const {
145+
amountTotal,
146+
currency,
147+
} = amountDetails
143148
// remove any unintentional/unwanted properties
144149
const address = objClean(
145150
billingDetails.address || {},
@@ -163,51 +168,52 @@ export default async function handleCreateIntent(
163168
name,
164169
email,
165170
phone,
171+
JSON.stringify(amountDetails),
166172
].join('__'))
173+
167174
const metadata = {
168175
cdpIssueCount: `${cdpIssueCount}`,
169176
companyId,
170177
registrationNumber,
171178
tsValidTo,
172179
}
180+
// values to be supplied to stripe.js
173181
const intentParams = {
174-
amount,
182+
amount: amountTotal,
175183
// In the latest version of the API, specifying the `automatic_payment_methods` parameter is optional because Stripe enables its functionality by default.
176184
automatic_payment_methods: {
177185
enabled: true,
178186
allow_redirects: 'always' //default
179187
},
180-
currency: CURRENCY, // pounds sterling
188+
currency,
181189
description: !cdpEntry?.cdp
182190
? 'CDP: Application'
183191
: `CDP: Renewal - ${monthYear}`,
184192
metadata,
185-
shipping: {
186-
address,
187-
name,
188-
},
189-
receipt_email: email
193+
receipt_email: email,
194+
shipping: { address, name },
190195
}
191196
const intent = await stripe
192197
.paymentIntents
193198
.create(intentParams, { idempotencyKey })
194199
.catch(err => new Error(err))
195200
// stripe threw an error
196201
if (isError(intent)) return callback(intent.message)
197-
const existingLogEntry = await dbCdpStripeIntents.get(intent.id)
202+
const existingLogEntry = await dbCdpStripeIntents
203+
.get(intent.id)
198204
const intentLogEntry = {
199-
amount,
205+
amountDetails,
200206
billingDetails: {
201207
address,
202208
email,
203209
name,
204210
phone,
205211
},
206212
createAccessCode: !code,
207-
currency,
208213
generatedData,
209214
intentId: intent.id,
210215
metadata,
216+
provider: 'stripe',
211217
status: 'created', // to be updated after payment is completed
212218
}
213219
await dbCdpStripeIntents.set(intent.id, intentLogEntry, true)

0 commit comments

Comments
 (0)