-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* feat: Add Recurring Charge API * tests: Improve test coverage
- Loading branch information
1 parent
38b59dd
commit 4d48e9f
Showing
16 changed files
with
927 additions
and
106 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
import "https://deno.land/std@0.209.0/dotenv/load.ts"; | ||
import { Client } from "../src/mod.ts"; | ||
|
||
// First, get your API keys from https://portal.vipps.no/ | ||
// Here we assume they are stored in a .env file, see .env.example | ||
const clientId = Deno.env.get("CLIENT_ID") || ""; | ||
const clientSecret = Deno.env.get("CLIENT_SECRET") || ""; | ||
|
||
const merchantSerialNumber = Deno.env.get("MERCHANT_SERIAL_NUMBER") || ""; | ||
const subscriptionKey = Deno.env.get("SUBSCRIPTION_KEY") || ""; | ||
|
||
const customerPhoneNumber = "4791234567"; | ||
|
||
// Create a client | ||
const client = Client({ | ||
merchantSerialNumber, | ||
subscriptionKey, | ||
useTestMode: true, | ||
retryRequests: false, | ||
}); | ||
|
||
// Grab a token | ||
const accessToken = await client.auth.getToken({ | ||
clientId, | ||
clientSecret, | ||
subscriptionKey, | ||
}); | ||
|
||
// Check if the token was retrieved successfully | ||
if (!accessToken.ok) { | ||
console.error("😟 Error retrieving token 😟"); | ||
console.error(accessToken.message); | ||
Deno.exit(1); | ||
} | ||
|
||
const token = accessToken.data.access_token; | ||
|
||
const agreement = await client.agreement.create(token, { | ||
pricing: { | ||
type: "LEGACY", | ||
amount: 2500, | ||
currency: "NOK", | ||
}, | ||
interval: { | ||
unit: "MONTH", | ||
count: 1, | ||
}, | ||
merchantRedirectUrl: "https://example.com/redirect", | ||
merchantAgreementUrl: "https://example.com/agreement", | ||
phoneNumber: customerPhoneNumber, | ||
productName: "MyNews Digital", | ||
}); | ||
|
||
// Check if the agreement was created successfully | ||
if (!agreement.ok) { | ||
console.error("😟 Error creating agreement 😟"); | ||
console.error(agreement.error); | ||
Deno.exit(1); | ||
} | ||
|
||
const agreementId = agreement.data.agreementId; | ||
|
||
const acceptedAgreement = await client.agreement.forceAccept( | ||
token, | ||
agreementId, | ||
{ phoneNumber: customerPhoneNumber }, | ||
); | ||
|
||
// Check if the agreement was accepted successfully | ||
if (!acceptedAgreement.ok) { | ||
console.error("😟 Error accepting agreement 😟"); | ||
console.error(acceptedAgreement.error); | ||
Deno.exit(1); | ||
} | ||
|
||
// 10 days from now in YYYY-MM-DD format | ||
const tenDaysFromToday = new Date(Date.now() + 10 * 24 * 60 * 60 * 1000) | ||
.toISOString().split("T")[0]; | ||
|
||
const charge = await client.charge.create(token, agreementId, { | ||
amount: 2500, | ||
description: "MyNews Digital", | ||
orderId: crypto.randomUUID(), | ||
due: tenDaysFromToday, | ||
retryDays: 5, | ||
transactionType: "DIRECT_CAPTURE", | ||
}); | ||
|
||
// Check if the charge was created successfully | ||
if (!charge.ok) { | ||
console.error("😟 Error creating charge 😟"); | ||
console.error(charge.error); | ||
Deno.exit(1); | ||
} | ||
|
||
const chargeId = charge.data.chargeId; | ||
|
||
const chargeInfo = await client.charge.info(token, agreementId, chargeId); | ||
|
||
// Check if the charge info was fetched successfully | ||
if (!chargeInfo.ok) { | ||
console.error("😟 Error retrieving charge 😟"); | ||
console.error(chargeInfo.error); | ||
Deno.exit(1); | ||
} | ||
|
||
console.log(chargeInfo.data); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,167 @@ | ||
import { RequestData } from "../types.ts"; | ||
import { | ||
ChargeReference, | ||
ChargeResponseV3, | ||
ChargeStatus, | ||
CreateChargeV3, | ||
ModifyCharge, | ||
} from "./types/charge_types.ts"; | ||
import { RecurringErrorResponse } from "./types/recurring_types.ts"; | ||
|
||
/** | ||
* Factory object for managing charge API requests. | ||
*/ | ||
export const chargeRequestFactory = { | ||
/** | ||
* Creates a new recurring charge (payment) that will charge the user | ||
* on the date specified. If the payment fails, | ||
* the charge will be retried based on retryDays | ||
* | ||
* @param token - The authentication token. | ||
* @param body - The request body containing the charge details. | ||
* @returns A RequestData object containing the URL, method, and token for the API request. | ||
*/ | ||
create( | ||
token: string, | ||
agreementId: string, | ||
body: CreateChargeV3, | ||
): RequestData<ChargeReference, RecurringErrorResponse> { | ||
return { | ||
url: `/recurring/v3/agreements/${agreementId}/charges`, | ||
method: "POST", | ||
body, | ||
token, | ||
}; | ||
}, | ||
/** | ||
* Fetches a single charge for a user. | ||
* | ||
* @param token - The authentication token. | ||
* @param chargeId - The ID of the charge to retrieve. | ||
* @param agreementId - The ID of the agreement. | ||
* @returns A RequestData object containing the URL, method, and token for the API request. | ||
*/ | ||
info( | ||
token: string, | ||
agreementId: string, | ||
chargeId: string, | ||
): RequestData<ChargeResponseV3, RecurringErrorResponse> { | ||
return { | ||
url: `/recurring/v3/agreements/${agreementId}/charges/${chargeId}`, | ||
method: "GET", | ||
token, | ||
}; | ||
}, | ||
/** | ||
* Retrieves information about a charge by its ID. A "special case" | ||
* endpoint to fetch a single charge just by chargeId, when the | ||
* agreementId is unknown. This is useful for investigating | ||
* claims from customers, but not intended for automation. | ||
* | ||
* Please note: This is not a replacement for the normal endpoint | ||
* for fetching charges | ||
* | ||
* @param token - The access token. | ||
* @param chargeId - The ID of the charge. | ||
* @returns A `RequestData` object containing the URL, method, and token. | ||
*/ | ||
infoById( | ||
token: string, | ||
chargeId: string, | ||
): RequestData<ChargeResponseV3, RecurringErrorResponse> { | ||
return { | ||
url: `/recurring/v3/agreements/charges/${chargeId}`, | ||
method: "GET", | ||
token, | ||
}; | ||
}, | ||
/** | ||
* Fetches all charges for a single agreement, including the optional | ||
* initial charge. Supports filtering on status using query parameter. | ||
* | ||
* @param token - The authentication token. | ||
* @param agreementId - The ID of the agreement. | ||
* @returns A RequestData object containing the URL, method, and token for the API request. | ||
*/ | ||
list( | ||
token: string, | ||
agreementId: string, | ||
status?: ChargeStatus, | ||
): RequestData<ChargeResponseV3[], RecurringErrorResponse> { | ||
const url = status | ||
? `/recurring/v3/agreements/${agreementId}/charges?status=${status}` | ||
: `/recurring/v3/agreements/${agreementId}/charges`; | ||
|
||
return { url, method: "GET", token }; | ||
}, | ||
/** | ||
* Cancels a pending, due or reserved charge. When cancelling a charge | ||
* that is PARTIALLY_CAPTURED, the remaining funds on the charge | ||
* will be released back to the customer. | ||
* | ||
* Note if you cancel an agreement, there is no need to cancel the | ||
* charges that belongs to the agreement. This will be done automatically. | ||
* | ||
* @param token - The authentication token. | ||
* @param agreementId - The ID of the agreement. | ||
* @param chargeId - The ID of the charge. | ||
* @returns A RequestData object containing the URL, method, and token for the API request. | ||
*/ | ||
cancel( | ||
token: string, | ||
agreementId: string, | ||
chargeId: string, | ||
): RequestData<void, RecurringErrorResponse> { | ||
return { | ||
url: `/recurring/v3/agreements/${agreementId}/charges/${chargeId}`, | ||
method: "DELETE", | ||
token, | ||
}; | ||
}, | ||
/** | ||
* Captures a reserved charge. Only charges with transactionType | ||
* RESERVE_CAPTURE can be captured. Can also do partial captures | ||
* (captures a smaller part of the payment). | ||
* | ||
* @param token - The authentication token. | ||
* @param agreementId - The ID of the agreement. | ||
* @param chargeId - The ID of the charge. | ||
* @returns A RequestData object containing the URL, method, and token for the API request. | ||
*/ | ||
capture( | ||
token: string, | ||
agreementId: string, | ||
chargeId: string, | ||
body: ModifyCharge, | ||
): RequestData<void, RecurringErrorResponse> { | ||
return { | ||
url: | ||
`/recurring/v3/agreements/${agreementId}/charges/${chargeId}/capture`, | ||
method: "POST", | ||
body, | ||
token, | ||
}; | ||
}, | ||
/** | ||
* Refunds a charge, can also do a partial refund | ||
* (refunding a smaller part of the payment). | ||
* | ||
* @param token - The authentication token. | ||
* @param agreementId - The ID of the agreement. | ||
* @param chargeId - The ID of the charge. | ||
* @returns A RequestData object containing the URL, method, and token for the API request. | ||
*/ | ||
refund( | ||
token: string, | ||
agreementId: string, | ||
chargeId: string, | ||
body: ModifyCharge, | ||
): RequestData<void, RecurringErrorResponse> { | ||
return { | ||
url: `/recurring/v3/agreements/${agreementId}/charges/${chargeId}/refund`, | ||
method: "POST", | ||
body, | ||
token, | ||
}; | ||
}, | ||
} as const; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.