Skip to content

Commit 32c7f4c

Browse files
committed
cleanup checkout page + add documentation
1 parent 57f8748 commit 32c7f4c

14 files changed

+874
-35
lines changed

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,5 @@
22
node_modules/
33

44
.env
5+
6+
.DS_Store

api/confirm-payment.js

+16-2
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,35 @@
11
import axios from 'axios'
22

33
export default async (req, res) => {
4+
5+
// TODO: Validate the request was approved by your payment gateway (in this case Google Pay)
6+
7+
// Parse the gateway payment info to match Snipcart's schema
8+
// This will change depending on the payment gateway you choose
9+
const paymentSessionId = req.query.sessionId
410
const data = {
5-
paymentSessionId: req.requestId,
11+
paymentSessionId,
612
state: 'processed',
7-
transactionId: req.requestId,
13+
transactionId: req.body.requestId,
814
instructions: 'Your payment will appear on your statement in the coming days',
15+
links: {
16+
refunds: `${process.env.CHECKOUT_URL}/api/refund?transactionId=${req.body.requestId}`
17+
},
918
}
1019

20+
// Add authentification
21+
// This is the secret API key created in Snipcart's merchant dashboard
1122
const options = {
1223
headers: {
1324
Authorization: `Bearer ${process.env.SNIP_PAYMENT_API_KEY}`
1425
}
1526
}
1627

1728
try{
29+
// Confirm payment with Snipcart
1830
const resp = await axios.post(`${process.env.PAYMENT_URL}/api/private/custom-payment-gateway/payment`,data, options)
31+
32+
// ReturnUrl will redirect the user to the Order confirmation page of Snipcart
1933
return res.json({
2034
returnUrl: resp.data.returnUrl
2135
})

api/payment-methods.js

+15-3
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,30 @@
11
import axios from 'axios'
22

33
export default async (req, res) => {
4+
console.log(JSON.stringify(req.body))
45
if (req.body && req.body.publicToken) {
5-
console.log(req.body.publicToken)
66
try {
7+
// Validate the request was made by Snipcart
78
await axios.get(`${process.env.PAYMENT_URL}/api/public/custom-payment-gateway/validate?publicToken=${req.body.publicToken}`)
9+
10+
// Return the payment methods
811
return res.json([{
12+
id: 'sleeky_pay',
13+
name: 'SleekyPay',
14+
checkoutUrl: `https://sleeky-pay.netlify.app/index.html`,
15+
},{
916
id: 'paymentrequest-custom-gateway',
1017
name: 'Google pay',
11-
checkoutUrl: 'https://paymentrequest-custom-gateway-bpd1p4ouk.vercel.app'
18+
checkoutUrl: process.env.CHECKOUT_URL,
19+
iconUrl: `${process.env.CHECKOUT_URL}/google_pay.png`
1220
}])
1321
}catch(e){
22+
// Couldn't validate the request
1423
console.error(e)
24+
return res.status(401).send()
1525
}
1626
}
17-
return res.status(500).send()
27+
28+
// No publicToken provided. This means the request was NOT made by Snipcart
29+
return res.status(401).send()
1830
}

api/payment-session.js

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import axios from 'axios'
2+
3+
export default async (req, res) => {
4+
try {
5+
const {publicToken} = req.query
6+
const resp = await axios.get(`${process.env.PAYMENT_URL}/api/public/custom-payment-gateway/payment-session?publicToken=${publicToken}`)
7+
return res.json(resp.data)
8+
} catch (e) {
9+
console.error(e)
10+
return res.status(500).send()
11+
}
12+
}

api/refund.js

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
export default async (req, res) => {
2+
const { transactionId } = req.query
3+
try {
4+
// Validate the request was made by Snipcart
5+
await axios.get(`${process.env.PAYMENT_URL}/api/public/custom-payment-gateway/validate?publicToken=${req.body.publicToken}`)
6+
7+
// TODO: Refund the order via the gateway
8+
9+
return res.json({
10+
refundId: transactionId
11+
})
12+
} catch (e) {
13+
// Couldn't validate the request
14+
console.error(e)
15+
return res.status(401)
16+
}
17+
}

package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
"scripts": {
77
"deploy": "npx vercel deploy",
88
"start": "npx vercel dev",
9-
"tunnel": "npx ngrok http 3000 --subdomain=custompayment"
9+
"tunnel": "npx ngrok http 3000 --subdomain=custompayment",
10+
"deploy:prod": "npx vercel --prod"
1011
},
1112
"repository": {
1213
"type": "git",

recipe.md

+117
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
# Integrating a custom payment gateway
2+
Before you get started, make sure you [configured your merchant dashboard](/v3/custom-payment-gateway/merchant-configuration).
3+
4+
## 1. Creating the checkout page
5+
The first step to integrate a custom payment gateway, is to create the checkout page. This is the page the user will be redirected to when using the custom gateway. We will redirect them to `YOUR_CHECKOUT_URL.com?publicToken=<THE_TOKEN>`.
6+
7+
In the checkout page, you will want to retrieve information about the order. To do so, you will need to call our payment-session API endpoint. This will allow you to display order information on the checkout page.
8+
Learn how to [retrieve information about the payment session](/v3/custom-payment-gateway/definition#retrieve-a-payment-session).
9+
10+
This is how our demo checkout page looks. It uses the [Payment Request API](https://developer.mozilla.org/en-US/docs/Web/API/Payment_Request_API).
11+
![](https://cdn.sanity.io/images/zac9i1uu/dev/9d8f2f4a46457a05b81644166d890612bc172955-605x405.png?w=1000&h=1000&fit=max)
12+
[see Github repo for more details](https://github.com/snipcart/paymentrequest-custom-gateway)
13+
14+
## 2. Payment methods endpoint
15+
The second step to integrate a custom payment gateway, is to create the payment methods endpoint. The `publicToken` will be provided in the request body.
16+
17+
__Make sure you [validate](/v3/custom-payment-gateway/definition#validate-public-token) the request was made by our API.__
18+
19+
[Payment methods webhook reference](/v3/custom-payment-gateway/definition#payment-methods).
20+
```javascript
21+
async (req, res) => {
22+
if (req.body && req.body.publicToken) {
23+
try {
24+
// Validate the request was made by Snipcart
25+
await axios.get(`https://payment.snipcart.com/api/public/custom-payment-gateway/validate?publicToken=${req.body.publicToken}`)
26+
27+
// Return the payment methods
28+
return res.json([{
29+
id: 'paymentrequest-custom-gateway',
30+
name: 'Google pay',
31+
checkoutUrl: 'https://paymentrequest-custom-gateway.snipcart.vercel.app',
32+
iconUrl: `https://paymentrequest-custom-gateway.snipcart.vercel.app/google_pay.png`
33+
}])
34+
}catch(e){
35+
// Couldn't validate the request
36+
console.error(e)
37+
return res.status(401).send()
38+
}
39+
}
40+
41+
// No publicToken provided. This means the request was NOT made by Snipcart
42+
return res.status(401).send()
43+
}
44+
```
45+
46+
## 3. Confirm payment
47+
This endpoint is used to validate the Payment with Snipcart when the payment is approved by your payment gateway. It should be called with the payment information. This has to be done server-side since we don't want to leak our __secret__ API Key.
48+
49+
[Create payment reference](/v3/custom-payment-gateway/definition#create-payment).
50+
```javascript
51+
async (req, res) => {
52+
53+
// TODO: Validate the request was approved by your payment gateway (in this case Google Pay)
54+
55+
// Parse the gateway payment info to match Snipcart's schema
56+
// This will change depending on the payment gateway you choose
57+
const paymentSessionId = req.query.sessionId
58+
const data = {
59+
paymentSessionId,
60+
state: 'processed',
61+
transactionId: req.body.requestId,
62+
instructions: 'Your payment will appear on your statement in the coming days',
63+
links: {
64+
refunds: `https://paymentrequest-custom-gateway.snipcart.vercel.app/api/refund?transactionId=${req.body.requestId}`
65+
},
66+
}
67+
68+
// Add authentification
69+
// This is the secret API key created in Snipcart's merchant dashboar
70+
const options = {
71+
headers: {
72+
Authorization: 'Bearer <YOUR_SECRET_API_KEY>'
73+
}
74+
}
75+
76+
try{
77+
// Confirm payment with Snipcart
78+
const resp = await axios.post(`${process.env.PAYMENT_URL}/api/private/custom-payment-gateway/payment`,data, options)
79+
80+
// ReturnUrl will redirect the user to the Order confirmation page of Snipcart
81+
return res.json({
82+
returnUrl: resp.data.returnUrl
83+
})
84+
}catch(e){
85+
console.error(e)
86+
}
87+
88+
return res.status(500).send()
89+
}
90+
```
91+
92+
## 4. Refund (Optional)
93+
This will be called when refunding an order via the merchant dashboard.
94+
95+
__Make sure you [validate](/v3/custom-payment-gateway/definition#validate-public-token) the request was made by our API.__
96+
97+
[Refund webhook reference](/v3/custom-payment-gateway/definition#refund)
98+
```javascript
99+
async (req, res) => {
100+
const { transactionId } = req.query
101+
try {
102+
// Validate the request was made by Snipcart
103+
await axios.get(`https://payment.snipcart.com/api/public/custom-payment-gateway/validate?publicToken=${req.body.publicToken}`)
104+
105+
// TODO: Refund the order via the gateway
106+
107+
return res.json({
108+
refundId: transactionId
109+
})
110+
} catch (e) {
111+
// Couldn't validate the request
112+
console.error(e)
113+
return res.status(401)
114+
}
115+
}
116+
```
117+

src/google_pay.png

49.3 KB
Loading

src/index.html

+31-9
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
<!DOCTYPE html>
22
<html lang="en">
3+
34
<head>
45
<meta charset="UTF-8">
56
<meta name="viewport" content="width=device-width, initial-scale=1.0">
@@ -20,15 +21,36 @@
2021
</svg>
2122
<h1>Replica zone</h1>
2223
</div>
23-
<div class="divider"></div>
24-
<table>
25-
<tbody>
26-
</tbody>
27-
<tfoot>
28-
</tfoot>
29-
</table>
30-
<div class="divider"></div>
31-
<button class="center" id="pay">Pay now</button>
24+
<div id="loader">
25+
<img src="./loader.svg" width="75" />
26+
</div>
27+
<div id="compatibility" class="hidden content">
28+
Payment Request Api is only supported for Edge, Chrome, Safari, Firefox.
29+
<br/>
30+
<a href="https://developer.mozilla.org/en-US/docs/Web/API/Payment_Request_API">Read more</a>
31+
</div>
32+
<div id="invoice_not_found" class="hidden content">
33+
Failed to retrieve invoice, please try again later.
34+
</div>
35+
<div id="content" class="hidden">
36+
<div class="divider"></div>
37+
<table>
38+
<tbody>
39+
</tbody>
40+
<tfoot>
41+
</tfoot>
42+
</table>
43+
<div class="divider"></div>
44+
<button class="center" id="pay">
45+
46+
<span id="button_loader" class="hidden">
47+
<img src="./white_loader.svg" width="25" />
48+
</span>
49+
<span>
50+
Pay with Google Pay
51+
</span>
52+
</button>
53+
</div>
3254
</div>
3355
</body>
3456

0 commit comments

Comments
 (0)