Skip to content

Commit c017bff

Browse files
committed
Added PhonePe as a payment gateway and fixed payment gateway selection
1 parent a80f9a4 commit c017bff

3 files changed

Lines changed: 42 additions & 33 deletions

File tree

backend/.env.example

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,10 @@ RAZORPAY_KEY_SECRET=your_razorpay_key_secret
2222
STRIPE_SECRET_KEY=your_stripe_secret_key
2323
STRIPE_WEBHOOK_SECRET=your_stripe_webhook_secret
2424

25-
# Payment Gateway - PhonePe (Sandbox)
26-
PHONEPE_MERCHANT_ID=PGTESTPAYUAT
27-
PHONEPE_SALT_KEY=099eb0cd-02cf-4e2a-8aca-3e6c6aff0399
25+
# Payment Gateway - PhonePe
26+
# Get these from PhonePe Developer Dashboard: https://developer.phonepe.com
27+
PHONEPE_CLIENT_ID=your_client_id
28+
PHONEPE_CLIENT_SECRET=your_client_secret
2829
PHONEPE_SALT_INDEX=1
2930
PHONEPE_ENV=sandbox
3031

backend/src/services/payment.service.js

Lines changed: 30 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -48,12 +48,12 @@ export function verifyRazorpaySignature(body, signature) {
4848
const PHONEPE_CONFIG = {
4949
sandbox: {
5050
baseUrl: 'https://api-preprod.phonepe.com/apis/pg-sandbox',
51-
// Test credentials - use for sandbox testing
51+
// Test phone/OTP for sandbox testing
5252
testPhone: '9999999999',
5353
testOtp: '123456'
5454
},
5555
production: {
56-
baseUrl: 'https://api.phonepe.com/apis/hermes'
56+
baseUrl: 'https://api.phonepe.com/apis/pg'
5757
}
5858
};
5959

@@ -62,40 +62,45 @@ function getPhonePeBaseUrl() {
6262
return PHONEPE_CONFIG[env]?.baseUrl || PHONEPE_CONFIG.sandbox.baseUrl;
6363
}
6464

65-
function generatePhonePeChecksum(payload, endpoint) {
66-
const saltKey = process.env.PHONEPE_SALT_KEY;
65+
function generatePhonePeChecksum(base64Payload, endpoint) {
66+
const clientSecret = process.env.PHONEPE_CLIENT_SECRET;
6767
const saltIndex = process.env.PHONEPE_SALT_INDEX || '1';
6868

69-
const base64Payload = Buffer.from(JSON.stringify(payload)).toString('base64');
70-
const stringToHash = base64Payload + endpoint + saltKey;
69+
const stringToHash = base64Payload + endpoint + clientSecret;
7170
const sha256Hash = crypto.createHash('sha256').update(stringToHash).digest('hex');
7271

73-
return {
74-
base64Payload,
75-
checksum: sha256Hash + '###' + saltIndex
76-
};
72+
return sha256Hash + '###' + saltIndex;
7773
}
7874

7975
export async function createPhonePePayment(order, callbackUrl) {
8076
try {
81-
const merchantId = process.env.PHONEPE_MERCHANT_ID;
77+
const clientId = process.env.PHONEPE_CLIENT_ID;
78+
const clientSecret = process.env.PHONEPE_CLIENT_SECRET;
79+
80+
if (!clientId || !clientSecret) {
81+
throw new Error('PhonePe credentials not configured');
82+
}
83+
8284
const merchantTransactionId = order.id.replace(/-/g, '').slice(0, 35); // PhonePe limit: 35 chars
8385

8486
const payload = {
85-
merchantId,
87+
merchantId: clientId,
8688
merchantTransactionId,
87-
merchantUserId: order.registration?.userEmail || 'guest',
89+
merchantUserId: order.registration?.userEmail?.replace(/[^a-zA-Z0-9]/g, '') || 'guest',
8890
amount: order.amountCents, // Amount in paise
8991
redirectUrl: callbackUrl,
90-
redirectMode: 'POST',
91-
callbackUrl: `${process.env.FRONTEND_URL}/api/webhooks/phonepe`,
92+
redirectMode: 'REDIRECT',
93+
callbackUrl: callbackUrl,
9294
paymentInstrument: {
9395
type: 'PAY_PAGE'
9496
}
9597
};
9698

99+
const base64Payload = Buffer.from(JSON.stringify(payload)).toString('base64');
97100
const endpoint = '/pg/v1/pay';
98-
const { base64Payload, checksum } = generatePhonePeChecksum(payload, endpoint);
101+
const checksum = generatePhonePeChecksum(base64Payload, endpoint);
102+
103+
console.log('PhonePe payment request:', { clientId, merchantTransactionId, amount: order.amountCents });
99104

100105
const response = await fetch(`${getPhonePeBaseUrl()}${endpoint}`, {
101106
method: 'POST',
@@ -107,6 +112,7 @@ export async function createPhonePePayment(order, callbackUrl) {
107112
});
108113

109114
const data = await response.json();
115+
console.log('PhonePe payment initiation failed:', data);
110116

111117
if (data.success && data.data?.instrumentResponse?.redirectInfo?.url) {
112118
return {
@@ -126,12 +132,12 @@ export async function createPhonePePayment(order, callbackUrl) {
126132

127133
export async function checkPhonePePaymentStatus(merchantTransactionId) {
128134
try {
129-
const merchantId = process.env.PHONEPE_MERCHANT_ID;
130-
const saltKey = process.env.PHONEPE_SALT_KEY;
135+
const clientId = process.env.PHONEPE_CLIENT_ID;
136+
const clientSecret = process.env.PHONEPE_CLIENT_SECRET;
131137
const saltIndex = process.env.PHONEPE_SALT_INDEX || '1';
132138

133-
const endpoint = `/pg/v1/status/${merchantId}/${merchantTransactionId}`;
134-
const stringToHash = endpoint + saltKey;
139+
const endpoint = `/pg/v1/status/${clientId}/${merchantTransactionId}`;
140+
const stringToHash = endpoint + clientSecret;
135141
const sha256Hash = crypto.createHash('sha256').update(stringToHash).digest('hex');
136142
const checksum = sha256Hash + '###' + saltIndex;
137143

@@ -140,11 +146,12 @@ export async function checkPhonePePaymentStatus(merchantTransactionId) {
140146
headers: {
141147
'Content-Type': 'application/json',
142148
'X-VERIFY': checksum,
143-
'X-MERCHANT-ID': merchantId
149+
'X-MERCHANT-ID': clientId
144150
}
145151
});
146152

147153
const data = await response.json();
154+
console.log('PhonePe status check response:', data);
148155

149156
return {
150157
success: data.success,
@@ -163,10 +170,10 @@ export async function checkPhonePePaymentStatus(merchantTransactionId) {
163170

164171
export function verifyPhonePeCallback(xVerifyHeader, responseBody) {
165172
try {
166-
const saltKey = process.env.PHONEPE_SALT_KEY;
173+
const clientSecret = process.env.PHONEPE_CLIENT_SECRET;
167174
const saltIndex = process.env.PHONEPE_SALT_INDEX || '1';
168175

169-
const stringToHash = responseBody + '/pg/v1/status' + saltKey;
176+
const stringToHash = responseBody + '/pg/v1/status' + clientSecret;
170177
const sha256Hash = crypto.createHash('sha256').update(stringToHash).digest('hex');
171178
const expectedChecksum = sha256Hash + '###' + saltIndex;
172179

frontend/src/pages/public/RegistrationPage.jsx

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -379,9 +379,10 @@ export default function RegistrationPage() {
379379
{paymentGateway === 'RAZORPAY' && <div className="w-2 h-2 rounded-full bg-[#E23744]" />}
380380
</div>
381381
{/* Razorpay Logo */}
382-
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" className="flex-shrink-0">
383-
<rect width="24" height="24" rx="4" fill="#072654" />
384-
<path d="M7.5 6L10.5 18H13.5L16.5 6H13.5L12 12L10.5 6H7.5Z" fill="white" />
382+
<svg width="28" height="28" viewBox="0 0 28 28" fill="none" className="flex-shrink-0">
383+
<rect width="28" height="28" rx="6" fill="#072654" />
384+
<path d="M8 7L11.5 21H14.5L12 11L14 7H8Z" fill="#3395FF" />
385+
<path d="M14.5 7L12 17H15L18 11L20 7H14.5Z" fill="white" />
385386
</svg>
386387
<div>
387388
<div className="font-medium">Razorpay</div>
@@ -408,10 +409,10 @@ export default function RegistrationPage() {
408409
{paymentGateway === 'PHONEPE' && <div className="w-2 h-2 rounded-full bg-purple-500" />}
409410
</div>
410411
{/* PhonePe Logo */}
411-
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" className="flex-shrink-0">
412-
<rect width="24" height="24" rx="4" fill="#5F259F" />
413-
<path d="M12 4C14.5 4 16.5 6 16.5 8.5V12H14V8.5C14 7.12 12.88 6 11.5 6C10.12 6 9 7.12 9 8.5V16H6.5V8.5C6.5 6 8.5 4 11 4H12Z" fill="white" />
414-
<circle cx="12" cy="17" r="2" fill="white" />
412+
<svg width="28" height="28" viewBox="0 0 28 28" fill="none" className="flex-shrink-0">
413+
<rect width="28" height="28" rx="6" fill="#5F259F" />
414+
<path d="M10 8H14C16.2 8 18 9.8 18 12V13H15V12C15 11.45 14.55 11 14 11H13V20H10V8Z" fill="white" />
415+
<circle cx="14" cy="20" r="2" fill="#5CDB5C" />
415416
</svg>
416417
<div>
417418
<div className="font-medium">PhonePe</div>

0 commit comments

Comments
 (0)