Skip to content
This repository has been archived by the owner on Jan 7, 2025. It is now read-only.

Commit

Permalink
Payment and Refund Simplify (#443)
Browse files Browse the repository at this point in the history
* Redoing Dev Env

* simplified request to trs

* simplied payment-transaction

* 2 more lines

* clean refund-transaction

* Error response clean

* payment-refund-transaction clean

* modifying mock-shopify-serverless

* some more mock-shopify-updates

* got local dev working

* .npmrcs and package changes

* pr cleanups

* pnpm lock update
  • Loading branch information
harshasomisetty authored Jul 18, 2023
1 parent 466f906 commit b811f9c
Show file tree
Hide file tree
Showing 30 changed files with 469 additions and 999 deletions.
2 changes: 2 additions & 0 deletions apps/backend-serverless/.npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
node-linker=hoisted
shared-workspace-lockfile=false
5 changes: 0 additions & 5 deletions apps/backend-serverless/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,6 @@
"private": true,
"description": "",
"main": "handler.js",
"workspaces": {
"nohoist": [
"**"
]
},
"type": "module",
"prisma": {
"seed": "tsx prisma/seed.ts"
Expand Down
2 changes: 2 additions & 0 deletions apps/backend-serverless/serverless.green.yml
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ plugins:
- serverless-step-functions
- serverless-iam-roles-per-function
- serverless-offline
- serverless-plugin-common-excludes
- serverless-plugin-include-dependencies

custom:
serverless-offline:
Expand Down
6 changes: 3 additions & 3 deletions apps/backend-serverless/serverless.purple.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,8 @@ provider:
# USE_AUTH_MOCK: ${env:USE_AUTH_MOCK}

package:
excludeDevDependencies: true
patterns:
- 'node_modules/.prisma/**'
- node_modules/**
functions:
install:
handler: src/handlers/shopify-handlers/install.install
Expand Down Expand Up @@ -471,7 +470,8 @@ plugins:
- serverless-step-functions
- serverless-iam-roles-per-function
- serverless-offline

- serverless-plugin-common-excludes
- serverless-plugin-include-dependencies
custom:
serverless-offline:
httpPort: 4000
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,5 +65,5 @@ export const install = Sentry.AWSLambda.wrapHandler(
},
{
rethrowAfterCapture: false,
},
}
);
Original file line number Diff line number Diff line change
Expand Up @@ -99,5 +99,5 @@ export const redirect = Sentry.AWSLambda.wrapHandler(
},
{
rethrowAfterCapture: false,
},
}
);
212 changes: 73 additions & 139 deletions apps/backend-serverless/src/handlers/transactions/payment-transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,14 @@ import * as Sentry from '@sentry/serverless';
import * as web3 from '@solana/web3.js';
import { APIGatewayProxyEventV2, APIGatewayProxyResultV2 } from 'aws-lambda';
import axios from 'axios';
import { DatabaseAccessError } from '../../errors/database-access.error.js';
import { DependencyError } from '../../errors/dependency.error.js';
import { InvalidInputError } from '../../errors/invalid-input.error.js';
import { MissingEnvError } from '../../errors/missing-env.error.js';
import { MissingExpectedDatabaseRecordError } from '../../errors/missing-expected-database-record.error.js';
import { RiskyWalletError } from '../../errors/risky-wallet.error.js';
import { PaymentSessionStateRejectedReason } from '../../models/shopify-graphql-responses/shared.model.js';
import {
PaymentRequestParameters,
parseAndValidatePaymentRequest,
} from '../../models/transaction-requests/payment-request-parameters.model.js';
import {
TransactionRequestBody,
parseAndValidateTransactionRequestBody,
} from '../../models/transaction-requests/transaction-request-body.model.js';
import { TransactionRequestResponse } from '../../models/transaction-requests/transaction-request-response.model.js';
import { parseAndValidatePaymentRequest } from '../../models/transaction-requests/payment-request-parameters.model.js';
import { parseAndValidateTransactionRequestBody } from '../../models/transaction-requests/transaction-request-body.model.js';
import { MerchantService } from '../../services/database/merchant-service.database.service.js';
import { PaymentRecordService } from '../../services/database/payment-record-service.database.service.js';
import { TransactionRecordService } from '../../services/database/transaction-record-service.database.service.js';
Expand Down Expand Up @@ -66,10 +59,6 @@ export const paymentTransaction = Sentry.AWSLambda.wrapHandler(
},
});

let paymentTransaction: TransactionRequestResponse;
let paymentRequest: PaymentRequestParameters;
let transaction: web3.Transaction;

const transactionRecordService = new TransactionRecordService(prisma);
const paymentRecordService = new PaymentRecordService(prisma);
const merchantService = new MerchantService(prisma);
Expand All @@ -78,109 +67,57 @@ export const paymentTransaction = Sentry.AWSLambda.wrapHandler(
if (event.body == null) {
return createErrorResponse(new InvalidInputError('request body'));
}
const websocketUrl = process.env.WEBSOCKET_URL;

let transactionRequestBody: TransactionRequestBody;

try {
transactionRequestBody = parseAndValidateTransactionRequestBody(JSON.parse(event.body));
} catch (error) {
return createErrorResponse(error);
}
const account = transactionRequestBody.account;

if (account == null) {
return createErrorResponse(new InvalidInputError('missing account in body'));
if (websocketUrl == null) {
return createErrorResponse(new MissingEnvError('websocket url'));
}

if (account != null) {
try {
new web3.PublicKey(account);
} catch (error) {
return createErrorResponse(new InvalidInputError('invalid account in body. needs to be a pubkey'));
}
}
let paymentRecord: PaymentRecord;
let account: string;
let merchant: Merchant;
let websocketService;
let gasKeypair: web3.Keypair;
let singleUseKeypair: web3.Keypair;

try {
paymentRequest = parseAndValidatePaymentRequest(event.queryStringParameters);
} catch (error) {
return createErrorResponse(error);
}
let transactionRequestBody = parseAndValidateTransactionRequestBody(JSON.parse(event.body));
account = transactionRequestBody.account;

let gasKeypair: web3.Keypair;
if (account == null) {
throw new InvalidInputError('missing account in body');
} else {
try {
new web3.PublicKey(account);
} catch (error) {
throw new InvalidInputError('invalid account in body. needs to be a pubkey');
}
}

let paymentRecord: PaymentRecord | null;
let paymentRequest = parseAndValidatePaymentRequest(event.queryStringParameters);

try {
paymentRecord = await paymentRecordService.getPaymentRecord({
id: paymentRequest.paymentId,
});
} catch (error) {
return createErrorResponse(error);
}

const websocketUrl = process.env.WEBSOCKET_URL;

if (websocketUrl == null) {
return createErrorResponse(new MissingEnvError('websocket url'));
}

const websocketService = new WebSocketService(
websocketUrl,
{
paymentRecordId: paymentRecord.id,
},
websocketSessionService,
);

await websocketService.sendTransacationRequestStartedMessage();

await sendSolanaPayInfoMessage(account, paymentRecord.id);

try {
gasKeypair = await fetchGasKeypair();
} catch (error) {
await websocketService.sendTransactionRequestFailedMessage();
return createErrorResponse(error);
}

let merchant: Merchant | null;

try {
merchant = await merchantService.getMerchant({
id: paymentRecord.merchantId,
});
} catch (error) {
await websocketService.sendTransactionRequestFailedMessage();
return createErrorResponse(error);
}

if (merchant.accessToken == null) {
await websocketService.sendTransactionRequestFailedMessage();
return createErrorResponse(new MissingExpectedDatabaseRecordError('merchant access token'));
}

const singleUseKeypair = await generateSingleUseKeypairFromRecord(paymentRecord);

try {
await uploadSingleUseKeypair(singleUseKeypair, paymentRecord);
} catch (error) {
Sentry.captureException(error);
// CRITIAL: This should work, but losing the rent here isn't the end of the world but we want to know
}
if (merchant.accessToken == null) {
throw new DatabaseAccessError('missing access token');
}

try {
paymentTransaction = await fetchPaymentTransaction(
paymentRecord,
merchant,
account,
gasKeypair.publicKey.toBase58(),
singleUseKeypair.publicKey.toBase58(),
gasKeypair.publicKey.toBase58(),
axios,
websocketService = new WebSocketService(
websocketUrl,
{
paymentRecordId: paymentRecord.id,
},
websocketSessionService,
);

await websocketService.sendTransacationRequestStartedMessage();
await sendSolanaPayInfoMessage(account, paymentRecord.id);
} catch (error) {
console.log('failed fetching payment transaction, prob lol');
await websocketService.sendTransactionRequestFailedMessage();
return createErrorResponse(error);
}

Expand Down Expand Up @@ -245,60 +182,57 @@ export const paymentTransaction = Sentry.AWSLambda.wrapHandler(
}

try {
transaction = encodeTransaction(paymentTransaction.transaction);
} catch (error) {
await websocketService.sendTransactionRequestFailedMessage();
return createErrorResponse(error);
}
singleUseKeypair = await generateSingleUseKeypairFromRecord(paymentRecord);
try {
await uploadSingleUseKeypair(singleUseKeypair, paymentRecord);
} catch (error) {
Sentry.captureException(error);
}
gasKeypair = await fetchGasKeypair();

transaction.partialSign(gasKeypair);
// transaction.partialSign(singleUseKeypair);
let paymentTransaction = await fetchPaymentTransaction(
paymentRecord,
merchant,
account,
gasKeypair.publicKey.toBase58(),
singleUseKeypair.publicKey.toBase58(),
gasKeypair.publicKey.toBase58(),
axios,
);

try {
let transaction = encodeTransaction(paymentTransaction.transaction);
transaction.partialSign(gasKeypair);
verifyTransactionWithRecord(paymentRecord, transaction, true);
} catch (error) {
console.log(error);
return createErrorResponse(error);
}

const transactionSignature = transaction.signature;

if (transactionSignature == null) {
await websocketService.sendTransactionRequestFailedMessage();
return createErrorResponse(new DependencyError('transaction signature'));
}

const signatureBuffer = transactionSignature;

const signatureString = encodeBufferToBase58(signatureBuffer);
const transactionSignature = transaction.signature;
if (transactionSignature == null) {
throw new DependencyError('transaction signature');
}

try {
await transactionRecordService.createTransactionRecord(
signatureString,
encodeBufferToBase58(transactionSignature),
TransactionType.payment,
paymentRecord.id,
null,
);
const transactionBuffer = transaction.serialize({
verifySignatures: false,
requireAllSignatures: false,
});

await websocketService.sendTransactionDeliveredMessage();

return {
statusCode: 200,
body: JSON.stringify({
transaction: transactionBuffer.toString('base64'),
message: `Paying ${merchant.name} ${paymentRecord.usdcAmount.toFixed(6)} USDC`,
}),
};
} catch (error) {
await websocketService.sendTransactionRequestFailedMessage();
return createErrorResponse(error);
}

const transactionBuffer = transaction.serialize({
verifySignatures: false,
requireAllSignatures: false,
});
const transactionString = transactionBuffer.toString('base64');

await websocketService.sendTransactionDeliveredMessage();

return {
statusCode: 200,
body: JSON.stringify({
transaction: transactionString,
message: `Paying ${merchant.name} ${paymentRecord.usdcAmount.toFixed(6)} USDC`,
}),
};
},
{
captureTimeoutWarning: false,
Expand Down
Loading

1 comment on commit b811f9c

@vercel
Copy link

@vercel vercel bot commented on b811f9c Jul 18, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.